diff --git a/Makefile b/Makefile
index 7141207fe41361eb698e041003179e28479aa5f4..2a9fcdb608b2a86bb2acd68dbf824b4d6066c34c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,68 +1,13 @@
+KERNEL_DIR = ./kernel
 
-OBJCOPY := $(GCCPREFIX)objcopy
-OBJDUMP := $(GCCPREFIX)objdump
-	
-objects_c := $(wildcard *.c)
-objects_s := $(wildcard *.s)
-
-GCC  := riscv64-unknown-elf-gcc
-GCCFLAGS 	= -Wall -O0 -fno-omit-frame-pointer -ggdb -g 
-GCCFLAGS 	+= -MD
-GCCFLAGS 	+= -mcmodel=medany
-GCCFLAGS 	+= -ffreestanding -fno-common -nostdlib -mno-relax
-GCCFLAGS 	+= -I.
-GCCFLAGS 	+= -Werror
-
-LD	 := riscv64-unknown-elf-ld
-LDFLAGS :=  -z max-page-size=4096 -T 
-OBJCOPY := riscv64-unknown-elf-objcopy
-QEMU    := qemu-system-riscv64
-QEMUFLAGS := -machine virt -m 128M -nographic -bios default -smp 1 -drive file=fat32.img,if=none,format=raw,id=x0  -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
-HEAD := *.h
-KERNEL_FILE := core.img
+modules  :=  $(KERNEL_DIR) 
 
-user/usys.S : user/usys.pl
-	perl user/usys.pl > user/usys.S
-
-default: user/usys.S
-	
-	${GCC} ${GCCFLAGS} -c start.S -o start.o
-	${GCC} ${GCCFLAGS} -c trampoline.S -o trampoline.o
-	${GCC} ${GCCFLAGS} -c kernel.c -o kernel.o
-	${GCC} ${GCCFLAGS} -c console.c -o console.o
-	${GCC} ${GCCFLAGS} -c sbi.c -o sbi.o
-	${GCC} ${GCCFLAGS} -c stdio.c -o stdio.o
-	${GCC} ${GCCFLAGS} -c trap.c -o trap.o
-	${GCC} ${GCCFLAGS} -c kernel_trapentry.S -o kernel_trapentry.o
-	${GCC} ${GCCFLAGS} -c string.c -o string.o
-	${GCC} ${GCCFLAGS} -c memory.c -o memory.o
-	${GCC} ${GCCFLAGS} -c vm.c -o vm.o
-	${GCC} ${GCCFLAGS} -c process.c -o process.o
-	${GCC} ${GCCFLAGS} -c spinlock.c -o spinlock.o
-	${GCC} ${GCCFLAGS} -c hart.c -o hart.o
-	${GCC} ${GCCFLAGS} -c switch.S -o switch.o
-	${GCC} ${GCCFLAGS} -c schedule.c -o schedule.o
-	${GCC} ${GCCFLAGS} -c virtio_disk.c -o virtio_disk.o
-	${GCC} ${GCCFLAGS} -c buf.c -o buf.o
-	${GCC} ${GCCFLAGS} -c fat32.c -o fat32.o
-	${GCC} ${GCCFLAGS} -c file.c -o file.o
-	${GCC} ${GCCFLAGS} -c sleeplock.c -o sleeplock.o
-	${GCC} ${GCCFLAGS} -c disk.c -o disk.o
-	${GCC} ${GCCFLAGS} -c execv.c -o execv.o
-	${GCC} ${GCCFLAGS} -c syscall.c -o syscall.o
-	${GCC} ${GCCFLAGS} -c syscall_arg.c -o syscall_arg.o
-#	${GCC} ${GCCFLAGS} -c user/init.c -o user/init.o
-#	${GCC} ${GCCFLAGS} -c user/usys.S -o user/usys.o
 
-	${LD} ${LDFLAGS} linker.ld -o kernel.elf start.o \
-			kernel.o sbi.o stdio.o console.o \
-			trap.o kernel_trapentry.o string.o memory.o \
-			vm.o process.o spinlock.o hart.o trampoline.o \
-			switch.o schedule.o sleeplock.o virtio_disk.o buf.o fat32.o \
-			disk.o file.o execv.o syscall.o syscall_arg.o 
-# user/usys.o user/init.o
+build :
+	cd $(KERNEL_DIR) && $(MAKE) kernel
 
-	${OBJCOPY} kernel.elf --strip-all -O binary $(KERNEL_FILE)
+run: clean fat build
+	${QEMU} -kernel $(KERNEL_FILE) ${QEMUFLAGS}
 
 fat:
 	@-mkdir mnt
@@ -72,15 +17,7 @@ fat:
 	@cp -r  ./sdcard_file/* ./mnt
 	@umount ./mnt
 
-run: clean fat default 
-	${QEMU} -kernel $(KERNEL_FILE) ${QEMUFLAGS}
-
-
-debug: clean fat default 
-#	@tmux new-session -d \
-	${QEMU} -kernel $(KERNEL_FILE) ${QEMUFLAGS} -s -S ${QEMUGDB}&& \
-	tmux split-window "riscv64-unknown-elf-gdb -ex 'file kernel.elf' -ex 'set arch riscv:rv64' -ex 'target remote localhost:25000'" && \
-	tmux -2 attach-session -d
+debug: clean fat  build
 	@echo "*** Now run 'gdb' in another window."
 	${QEMU} -kernel $(KERNEL_FILE) ${QEMUFLAGS} -s -S 
 	
@@ -88,6 +25,11 @@ debug: clean fat default
 #	target remote localhost:1234
 
 clean:
-	@echo "*** Now run 'gdb' in another window." 1>&2
-	-rm -f *.o  *.img  *.elf *.d user/*.o user/*.d user/*.img user/*.elf 
-	-rm -r ./mnt
\ No newline at end of file
+	-rm -r ./mnt
+
+	for d in $(modules); \
+		do \
+			$(MAKE) --directory=$$d clean; \
+		done
+	-rm -rf *.o *.img
+	
\ No newline at end of file
diff --git a/kernel/Makefile b/kernel/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e3e735f1bf81df83b3f4d61ba03a89d7bc6ede54
--- /dev/null
+++ b/kernel/Makefile
@@ -0,0 +1,49 @@
+
+OBJCOPY := $(GCCPREFIX)objcopy
+OBJDUMP := $(GCCPREFIX)objdump
+	
+
+GCC  := riscv64-unknown-elf-gcc
+GCCFLAGS 	= -Wall -O0 -fno-omit-frame-pointer -ggdb -g 
+GCCFLAGS 	+= -MD
+GCCFLAGS 	+= -mcmodel=medany
+GCCFLAGS 	+= -ffreestanding -fno-common -nostdlib -mno-relax
+GCCFLAGS 	+= -I.
+GCCFLAGS 	+= -Werror
+
+LD	 := riscv64-unknown-elf-ld
+LDFLAGS :=  -z max-page-size=4096 -T 
+OBJCOPY := riscv64-unknown-elf-objcopy
+QEMU    := qemu-system-riscv64
+QEMUFLAGS := -machine virt -m 128M -nographic -bios default -smp 1 -drive file=fat32.img,if=none,format=raw,id=x0  -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
+HEAD := *.h
+KERNEL_FILE := core.img
+KERNEL_ELF  := kernel.elf
+
+OBJS   = start.o trampoline.o kernel.o console.o sbi.o stdio.o \
+		trap.o kernel_trapentry.o string.o memory.o vm.o \
+		process.o  spinlock.o hart.o switch.o schedule.o virtio_disk.o \
+		buf.o fat32.o file.o sleeplock.o disk.o execv.o syscall.o \
+		syscall_arg.o
+
+%.o: %.c
+	$(GCC) $(GCCFLAGS)  -c $<
+
+%.o: %.S
+	$(GCC) $(GCCFLAGS)  -c $<
+
+
+build: $(OBJS)
+
+$(KERNEL_ELF): build
+	${LD} ${LDFLAGS} linker.ld -o $(KERNEL_ELF) $(OBJS)
+
+kernel: $(KERNEL_ELF)
+	${OBJCOPY} $(KERNEL_ELF) --strip-all -O binary ../$(KERNEL_FILE)
+
+
+clean:
+	@echo "*** Now run 'gdb' in another window." 1>&2
+	-rm -f *.o  *.img  *.elf *.d  
+
+# -rm -r ./mnt
\ No newline at end of file
diff --git a/Type_defs.h b/kernel/Type_defs.h
similarity index 93%
rename from Type_defs.h
rename to kernel/Type_defs.h
index 292086e9fd8276f3d670f87d3e63f071667b5fb9..669bd9e07e9c69fcb6207746ab965031382d0ce2 100644
--- a/Type_defs.h
+++ b/kernel/Type_defs.h
@@ -17,5 +17,6 @@ typedef long int64_t;               //risc-v64 long = 64bit
 typedef unsigned long uint64_t;
 typedef uint64_t size_t;
 typedef int32_t pid_t;
+typedef unsigned int uint;
 
 #endif
diff --git a/buf.c b/kernel/buf.c
similarity index 100%
rename from buf.c
rename to kernel/buf.c
diff --git a/buf.h b/kernel/buf.h
similarity index 100%
rename from buf.h
rename to kernel/buf.h
diff --git a/console.c b/kernel/console.c
similarity index 100%
rename from console.c
rename to kernel/console.c
diff --git a/console.h b/kernel/console.h
similarity index 100%
rename from console.h
rename to kernel/console.h
diff --git a/defs.h b/kernel/defs.h
similarity index 100%
rename from defs.h
rename to kernel/defs.h
diff --git a/disk.c b/kernel/disk.c
similarity index 100%
rename from disk.c
rename to kernel/disk.c
diff --git a/disk.h b/kernel/disk.h
similarity index 100%
rename from disk.h
rename to kernel/disk.h
diff --git a/elf.h b/kernel/elf.h
similarity index 100%
rename from elf.h
rename to kernel/elf.h
diff --git a/execv.c b/kernel/execv.c
similarity index 100%
rename from execv.c
rename to kernel/execv.c
diff --git a/execv.h b/kernel/execv.h
similarity index 100%
rename from execv.h
rename to kernel/execv.h
diff --git a/fat32.c b/kernel/fat32.c
similarity index 100%
rename from fat32.c
rename to kernel/fat32.c
diff --git a/fat32.h b/kernel/fat32.h
similarity index 100%
rename from fat32.h
rename to kernel/fat32.h
diff --git a/fcntl.h b/kernel/fcntl.h
similarity index 100%
rename from fcntl.h
rename to kernel/fcntl.h
diff --git a/file.c b/kernel/file.c
similarity index 100%
rename from file.c
rename to kernel/file.c
diff --git a/file.h b/kernel/file.h
similarity index 100%
rename from file.h
rename to kernel/file.h
diff --git a/hart.c b/kernel/hart.c
similarity index 100%
rename from hart.c
rename to kernel/hart.c
diff --git a/hart.h b/kernel/hart.h
similarity index 100%
rename from hart.h
rename to kernel/hart.h
diff --git a/kernel.c b/kernel/kernel.c
similarity index 100%
rename from kernel.c
rename to kernel/kernel.c
diff --git a/kernel_trapentry.S b/kernel/kernel_trapentry.S
similarity index 100%
rename from kernel_trapentry.S
rename to kernel/kernel_trapentry.S
diff --git a/linker.ld b/kernel/linker.ld
similarity index 100%
rename from linker.ld
rename to kernel/linker.ld
diff --git a/memlayout.h b/kernel/memlayout.h
similarity index 100%
rename from memlayout.h
rename to kernel/memlayout.h
diff --git a/memory.c b/kernel/memory.c
similarity index 100%
rename from memory.c
rename to kernel/memory.c
diff --git a/memory.h b/kernel/memory.h
similarity index 100%
rename from memory.h
rename to kernel/memory.h
diff --git a/process.c b/kernel/process.c
similarity index 100%
rename from process.c
rename to kernel/process.c
diff --git a/process.h b/kernel/process.h
similarity index 100%
rename from process.h
rename to kernel/process.h
diff --git a/register.h b/kernel/register.h
similarity index 100%
rename from register.h
rename to kernel/register.h
diff --git a/riscv.h b/kernel/riscv.h
similarity index 100%
rename from riscv.h
rename to kernel/riscv.h
diff --git a/sbi.c b/kernel/sbi.c
similarity index 100%
rename from sbi.c
rename to kernel/sbi.c
diff --git a/sbi.h b/kernel/sbi.h
similarity index 100%
rename from sbi.h
rename to kernel/sbi.h
diff --git a/schedule.c b/kernel/schedule.c
similarity index 100%
rename from schedule.c
rename to kernel/schedule.c
diff --git a/schedule.h b/kernel/schedule.h
similarity index 100%
rename from schedule.h
rename to kernel/schedule.h
diff --git a/sleeplock.c b/kernel/sleeplock.c
similarity index 100%
rename from sleeplock.c
rename to kernel/sleeplock.c
diff --git a/sleeplock.h b/kernel/sleeplock.h
similarity index 100%
rename from sleeplock.h
rename to kernel/sleeplock.h
diff --git a/spinlock.c b/kernel/spinlock.c
similarity index 100%
rename from spinlock.c
rename to kernel/spinlock.c
diff --git a/spinlock.h b/kernel/spinlock.h
similarity index 100%
rename from spinlock.h
rename to kernel/spinlock.h
diff --git a/start.S b/kernel/start.S
similarity index 100%
rename from start.S
rename to kernel/start.S
diff --git a/stat.h b/kernel/stat.h
similarity index 100%
rename from stat.h
rename to kernel/stat.h
diff --git a/stdio.c b/kernel/stdio.c
similarity index 100%
rename from stdio.c
rename to kernel/stdio.c
diff --git a/stdio.h b/kernel/stdio.h
similarity index 100%
rename from stdio.h
rename to kernel/stdio.h
diff --git a/string.c b/kernel/string.c
similarity index 100%
rename from string.c
rename to kernel/string.c
diff --git a/string.h b/kernel/string.h
similarity index 100%
rename from string.h
rename to kernel/string.h
diff --git a/switch.S b/kernel/switch.S
similarity index 100%
rename from switch.S
rename to kernel/switch.S
diff --git a/syscall.c b/kernel/syscall.c
similarity index 100%
rename from syscall.c
rename to kernel/syscall.c
diff --git a/syscall.h b/kernel/syscall.h
similarity index 100%
rename from syscall.h
rename to kernel/syscall.h
diff --git a/syscall_arg.c b/kernel/syscall_arg.c
similarity index 100%
rename from syscall_arg.c
rename to kernel/syscall_arg.c
diff --git a/syscall_arg.h b/kernel/syscall_arg.h
similarity index 100%
rename from syscall_arg.h
rename to kernel/syscall_arg.h
diff --git a/trampoline.S b/kernel/trampoline.S
similarity index 100%
rename from trampoline.S
rename to kernel/trampoline.S
diff --git a/trap.c b/kernel/trap.c
similarity index 100%
rename from trap.c
rename to kernel/trap.c
diff --git a/trap.h b/kernel/trap.h
similarity index 100%
rename from trap.h
rename to kernel/trap.h
diff --git a/virtio.h b/kernel/virtio.h
similarity index 100%
rename from virtio.h
rename to kernel/virtio.h
diff --git a/virtio_disk.c b/kernel/virtio_disk.c
similarity index 100%
rename from virtio_disk.c
rename to kernel/virtio_disk.c
diff --git a/vm.c b/kernel/vm.c
similarity index 100%
rename from vm.c
rename to kernel/vm.c
diff --git a/vm.h b/kernel/vm.h
similarity index 100%
rename from vm.h
rename to kernel/vm.h
diff --git a/user/printf.c b/user/printf.c
index 7b705f4d5554269e0a6c32d34e801d0e313c1f28..1d38456fb44702898354a8ca0ab2d737e771cf75 100644
--- a/user/printf.c
+++ b/user/printf.c
@@ -39,12 +39,12 @@ printint(int fd, int xx, int base, int sgn)
 }
 
 static void
-printptr(int fd, uint64 x) {
+printptr(int fd, uint64_t x) {
   int i;
   putc(fd, '0');
   putc(fd, 'x');
-  for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4)
-    putc(fd, digits[x >> (sizeof(uint64) * 8 - 4)]);
+  for (i = 0; i < (sizeof(uint64_t) * 2); i++, x <<= 4)
+    putc(fd, digits[x >> (sizeof(uint64_t) * 8 - 4)]);
 }
 
 // Print to the given fd. Only understands %d, %x, %p, %s.
@@ -67,11 +67,11 @@ vprintf(int fd, const char *fmt, va_list ap)
       if(c == 'd'){
         printint(fd, va_arg(ap, int), 10, 1);
       } else if(c == 'l') {
-        printint(fd, va_arg(ap, uint64), 10, 0);
+        printint(fd, va_arg(ap, uint64_t), 10, 0);
       } else if(c == 'x') {
         printint(fd, va_arg(ap, int), 16, 0);
       } else if(c == 'p') {
-        printptr(fd, va_arg(ap, uint64));
+        printptr(fd, va_arg(ap, uint64_t));
       } else if(c == 's'){
         s = va_arg(ap, char*);
         if(s == 0)
diff --git a/xv6_user/Makefile b/xv6_user/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..7879717b0e8a513eb8d866446bcae9b032df7520
--- /dev/null
+++ b/xv6_user/Makefile
@@ -0,0 +1,41 @@
+BUILD=build
+FSIMG=$(ROOT)/fsimg
+$(shell mkdir -p $(BUILD))
+$(shell mkdir -p $(FSIMG))
+
+# SRCS=$(shell find src -name "*.c" -o -name "*.S")
+SRCS=src/sh.c src/init.c src/echo.c src/kalloctest.c src/mmaptest.c src/mkdir.c src/user_test.c src/ls.c src/rawcwd.c src/cat.c \
+	 src/kill.c src/wc.c src/rm.c src/shutdown.c
+OBJS=$(addprefix $(BUILD)/, $(addsuffix .o, $(basename $(SRCS))))
+TARGET=$(addprefix $(FSIMG)/, $(basename $(notdir $(SRCS))))
+ARCHIVE=lib/lib.a
+CFLAGS+=-I$(ROOT)/$(xv6U)/include
+
+all: $(OBJS) $(TARGET)
+
+export CFLAGS CC AS ASFLAGS OBJCOPY OBJDUMP LDFLAGS LD SCRIPTS
+$(ARCHIVE): lib
+lib:
+	@echo "$(YELLOW)build user lib:$(RESET)"
+	@make -C lib
+
+# forktest has less library code linked in - needs to be small
+# in order to be able to max out the proc table.
+# $(FSIMG)/forktest: $(BUILD)/src/forktest.o $(ARCHIVE)
+# 	@$(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $(FSIMG)/forktest $(BUILD)/src/forktest.o $(ARCHIVE)
+# 	@$(OBJDUMP) -S $(FSIMG)/forktest > $(BUILD)/src/forktest.asm
+
+include $(SCRIPTS)/rules.mk
+include $(SCRIPTS)/colors.mk
+
+
+$(FSIMG)/%: $(BUILD)/src/%.o $(ARCHIVE) 
+	@$(LD) $(LDFLAGS) -T src/user.ld -o $@ $^
+	@$(OBJDUMP) -S $@ > $(BUILD)/src/$*.asm
+	@$(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(BUILD)/src/$*.sym
+
+clean:
+	@-rm build/* -rf
+	-@make -C lib clean
+
+.PHONY: lib clean
diff --git a/xv6_user/include/stddef.h b/xv6_user/include/stddef.h
new file mode 100644
index 0000000000000000000000000000000000000000..8d7513b3c41b175e8719105e30c9eece74085ae6
--- /dev/null
+++ b/xv6_user/include/stddef.h
@@ -0,0 +1,135 @@
+#ifndef __STDDEF_H__
+#define __STDDEF_H__
+
+/* Represents true-or-false values */
+typedef int bool;
+
+/* Explicitly-sized versions of integer types */
+typedef char int8;
+typedef unsigned char uint8;
+typedef short int16;
+typedef unsigned short uint16;
+typedef int int32;
+typedef unsigned int uint32;
+typedef long long int64;
+typedef unsigned long long uint64;
+typedef unsigned int uint;
+
+#define ULONG_MAX (0xffffffffffffffffULL)
+#define LONG_MAX (0x7fffffffffffffffLL)
+#define INTMAX_MAX LONG_MAX
+#define UINT_MAX (0xffffffffU)
+#define INT_MAX (0x7fffffff)
+#define UCHAR_MAX (0xffU)
+#define CHAR_MAX (0x7f)
+
+/* *
+ * Pointers and addresses are 32 bits long.
+ * We use pointer types to represent addresses,
+ * uintptr_t to represent the numerical values of addresses.
+ * */
+#if __riscv_xlen == 64
+typedef int64 intptr_t;
+typedef uint64 uintptr_t;
+#elif __riscv_xlen == 32
+typedef int32_t intptr_t;
+typedef uint32 uintptr_t;
+#endif
+
+/* size_t is used for memory object sizes */
+typedef uintptr_t size_t;
+typedef intptr_t ssize_t;
+
+typedef int pid_t;
+
+#define NULL ((void *)0)
+
+#define SIGCHLD   17
+
+#define va_start(ap, last) (__builtin_va_start(ap, last))
+#define va_arg(ap, type) (__builtin_va_arg(ap, type))
+#define va_end(ap) (__builtin_va_end(ap))
+#define va_copy(d, s) (__builtin_va_copy(d, s))
+typedef __builtin_va_list va_list;
+
+#define O_RDONLY 0x000
+#define O_WRONLY 0x001
+#define O_RDWR 0x002 // 可读可写
+#define O_TRUNC 0x400
+//#define O_CREATE 0x200
+#define O_CREATE 0x40
+#define O_DIRECTORY 0x0200000
+
+#define DIR 0x040000
+#define FILE 0x100000
+
+#define AT_FDCWD -100
+
+typedef struct
+{
+    uint64 sec;  // 自 Unix 纪元起的秒数
+    uint64 usec; // 微秒数
+} TimeVal;
+
+typedef struct
+{
+    uint64 dev;    // 文件所在磁盘驱动器号,不考虑
+    uint64 ino;    // inode 文件所在 inode 编号
+    uint32 mode;   // 文件类型
+    uint32 nlink;  // 硬链接数量,初始为1
+    uint64 pad[7]; // 无需考虑,为了兼容性设计
+} Stat;
+
+typedef unsigned int mode_t;
+typedef long int off_t;
+
+struct kstat {
+        uint64 st_dev;
+        uint64 st_ino;
+        mode_t st_mode;
+        uint32 st_nlink;
+        uint32 st_uid;
+        uint32 st_gid;
+        uint64 st_rdev;
+        unsigned long __pad;
+        off_t st_size;
+        uint32 st_blksize;
+        int __pad2;
+        uint64 st_blocks;
+        long st_atime_sec;
+        long st_atime_nsec;
+        long st_mtime_sec;
+        long st_mtime_nsec;
+        long st_ctime_sec;
+        long st_ctime_nsec;
+        unsigned __unused[2];
+};
+
+
+
+struct linux_dirent64 {
+        uint64        d_ino;
+        int64         d_off;
+        unsigned short  d_reclen;
+        unsigned char   d_type;
+        char            d_name[];
+};
+
+// for mmap
+#define PROT_NONE 0
+#define PROT_READ 1
+#define PROT_WRITE 2
+#define PROT_EXEC 4
+#define PROT_GROWSDOWN 0X01000000
+#define PROT_GROWSUP 0X02000000
+
+#define MAP_FILE 0
+#define MAP_SHARED 0x01
+#define MAP_PRIVATE 0X02
+#define MAP_FAILED ((void *) -1)
+
+// add
+typedef unsigned int   uint;
+typedef unsigned short ushort;
+typedef unsigned char  uchar;
+#endif // __STDDEF_H__
diff --git a/xv6_user/include/stdio.h b/xv6_user/include/stdio.h
new file mode 100644
index 0000000000000000000000000000000000000000..487c55a42f3a946dd091b20a617e41f11bd85e34
--- /dev/null
+++ b/xv6_user/include/stdio.h
@@ -0,0 +1,40 @@
+#ifndef __STDIO_H__
+#define __STDIO_H__
+
+#define STDIN 0
+#define STDOUT 1
+#define STDERR 2
+
+// file type
+#define T_DIR 1    // Directory
+#define T_FILE 2   // File
+#define T_DEVICE 3 // Device
+
+//#define TEST_START(x) puts(x)
+#define TEST_START(x) puts("========== START ");puts(x);puts(" ==========\n");
+#define TEST_END(x) puts("========== END ");puts(x);puts(" ==========\n");
+
+#define stdin STDIN
+#define stdout STDOUT
+#define stderr STDERR
+
+#define va_start(ap, last) (__builtin_va_start(ap, last))
+#define va_arg(ap, type) (__builtin_va_arg(ap, type))
+#define va_end(ap) (__builtin_va_end(ap))
+#define va_copy(d, s) (__builtin_va_copy(d, s))
+
+typedef __builtin_va_list va_list;
+typedef unsigned long int uintmax_t;
+typedef long int intmax_t;
+
+int getchar();
+int putchar(int);
+int puts(const char *s);
+// /* modify */
+void printf(const char *fmt, ...);
+
+/* add */
+void fprintf(int, const char*, ...);
+char* gets(char*, int max);
+
+#endif // __STDIO_H__
diff --git a/xv6_user/include/stdlib.h b/xv6_user/include/stdlib.h
new file mode 100644
index 0000000000000000000000000000000000000000..0de7359b3bc488bc877dab3cdf385d5ac1b16313
--- /dev/null
+++ b/xv6_user/include/stdlib.h
@@ -0,0 +1,30 @@
+#ifndef __STDLIB_H__
+#define __STDLIB_H__
+
+#include "stddef.h"
+
+void panic(char *);
+
+#define WEXITSTATUS(s) (((s) & 0xff00) >> 8)
+
+#ifndef assert
+#define assert(f) \
+    if (!(f))     \
+	panic("\n --- Assert Fatal ! ---\n")
+#endif
+
+/* add */
+void* malloc(uint);
+void free(void*);
+
+struct stat {
+    int dev;     // File system's disk device
+    uint ino;    // Inode number
+    short type;  // Type of file
+    short nlink; // Number of links to file
+    uint64 size; // Size of file in bytes
+};
+
+int xv6_stat(const char*, struct stat*);
+
+#endif //__STDLIB_H__
diff --git a/xv6_user/include/string.h b/xv6_user/include/string.h
new file mode 100644
index 0000000000000000000000000000000000000000..4d9d5cda7937577ed79b33a923bc090c41598dca
--- /dev/null
+++ b/xv6_user/include/string.h
@@ -0,0 +1,24 @@
+#ifndef __STRING_H__
+#define __STRING_H__
+
+#include "stddef.h"
+
+int isspace(int c);
+int isdigit(int c);
+int atoi(const char *s);
+void *memset(void *dest, int c, size_t n);
+int strcmp(const char *l, const char *r);
+size_t strlen(const char *);
+size_t strnlen(const char *s, size_t n);
+char *strncpy(char *restrict d, const char *restrict s, size_t n);
+int strncmp(const char *_l, const char *_r, size_t n);
+
+/* add */
+char* strchr(const char*, char c);
+char* strcpy(char*, const char*);
+void *memmove(void*, const void*, int);
+int memcmp(const void *, const void *, uint);
+void *memcpy(void *, const void *, uint);
+char* strcat(char* dest, const char* src);
+
+#endif // __STRING_H__
diff --git a/xv6_user/include/unistd.h b/xv6_user/include/unistd.h
new file mode 100644
index 0000000000000000000000000000000000000000..bc55eae9421c7cd684280e482db2c94c5e99bc45
--- /dev/null
+++ b/xv6_user/include/unistd.h
@@ -0,0 +1,71 @@
+#ifndef __UNISTD_H__
+#define __UNISTD_H__
+
+#include "stddef.h"
+
+extern int __clone(int (*func)(void *), void *stack, int flags, void *arg, ...);
+
+int open(const char *, int);
+int openat(int, const char*, int);
+
+ssize_t read(int, void *, size_t);
+ssize_t write(int, const void *, size_t);
+
+int close(int);
+pid_t getpid(void);
+pid_t getppid(void);
+int sched_yield(void);
+void exit(int);
+pid_t fork(void);
+pid_t clone(int (*fn)(void *arg), void *arg, void *stack, size_t stack_size, unsigned long flags);
+int exec(char *);
+int execve(const char *, char *const [], char *const []);
+int waitpid(int, int *, int);
+int64 get_time();
+int sys_get_time(TimeVal *ts, int tz); // syscall ID: 169; tz 表示时区,这里无需考虑,始终为0; 返回值:正确返回 0,错误返回 -1。
+int times(void *mytimes);
+int sleep(unsigned long long);
+int set_priority(int prio);
+void *mmap(void *, size_t, int, int, int, off_t);
+int munmap(void *start, size_t len);
+int wait(int *);
+int spawn(char *file);
+int mailread(void *buf, int len);
+int mailwrite(int pid, void *buf, int len);
+
+int fstat(int fd, struct kstat *st);
+int sys_linkat(int olddirfd, char *oldpath, int newdirfd, char *newpath, unsigned int flags);
+int sys_unlinkat(int dirfd, char *path, unsigned int flags);
+int link(char *old_path, char *new_path);
+// int unlink(char *path);
+int uname(void *buf);
+int time(unsigned long *tloc);
+int brk(void *);
+
+char *getcwd(char *, size_t);
+int chdir(const char *);
+int mkdir(const char *, mode_t);
+int getdents(int fd, struct linux_dirent64 *dirp64, unsigned long len);
+int pipe(int [2]);
+int dup(int);
+int dup2(int, int);
+
+/* above is the oscomp's required syscall, don't modify, add new syscall below */
+/* ================================================================================= */
+
+/* modify */
+// void exit(int) __attribute__((noreturn));
+int unlink(const char *path);
+
+/* add */
+int mknod(const char* path, short major, short minor);
+char *sbrk(int);
+int kill(int);
+void shutdown();
+
+/* debug */
+int print_pgtable();
+int print_vma();
+void print_rawfile(int fd, int print);
+
+#endif // __UNISTD_H__
diff --git a/xv6_user/lib/Makefile b/xv6_user/lib/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..20173fab1757b7604113e9aeef9bb0d3a9ad70bf
--- /dev/null
+++ b/xv6_user/lib/Makefile
@@ -0,0 +1,21 @@
+BUILD=../build/lib
+# CFLAGS = -Wall -Werror -O0 -fno-omit-frame-pointer -ggdb -gdwarf-2
+CFLAGS += -Wno-error
+ULIB = $(BUILD)/ulib.o $(BUILD)/printf.o $(BUILD)/umalloc.o $(BUILD)/syscall.o $(BUILD)/clone.o
+OBJS=$(ULIB)
+ARCHIVE=lib.a
+
+all: $(ARCHIVE)
+
+include $(SCRIPTS)/rules.mk
+
+clean:
+	@-rm lib.a -f
+
+### Rule (archive): objects (`*.o`) -> `ARCHIVE.a` (ar)
+$(ARCHIVE): $(ULIB)
+	@echo + AR "->" $(shell realpath $@ --relative-to .)
+	@ar rcsT $@ $(ULIB)
+
+# test:
+#     $(foreach var,$(.VARIABLES),$(info $(var) = $($(var))))
\ No newline at end of file
diff --git a/xv6_user/lib/clone.S b/xv6_user/lib/clone.S
new file mode 100644
index 0000000000000000000000000000000000000000..db908248cdcc7a1da7d1fbd3d79d573a9d54991f
--- /dev/null
+++ b/xv6_user/lib/clone.S
@@ -0,0 +1,34 @@
+# __clone(func, stack, flags, arg, ptid, tls, ctid)
+#           a0,    a1,    a2,  a3,   a4,  a5,   a6
+
+# syscall(SYS_clone, flags, stack, ptid, tls, ctid)
+#                a7     a0,    a1,   a2,  a3,   a4
+
+.global __clone
+.type  __clone, %function
+__clone:
+	# Save func and arg to stack
+	addi a1, a1, -16
+	sd a0, 0(a1)
+	sd a3, 8(a1)
+
+	# Call SYS_clone
+	mv a0, a2
+	mv a2, a4
+	mv a3, a5
+	mv a4, a6
+	li a7, 220 # SYS_clone
+	ecall
+
+	beqz a0, 1f
+	# Parent
+	ret
+
+	# Child
+1:      ld a1, 0(sp)
+	ld a0, 8(sp)
+	jalr a1
+
+	# Exit
+	li a7, 93 # SYS_exit
+	ecall
diff --git a/xv6_user/lib/printf.c b/xv6_user/lib/printf.c
new file mode 100644
index 0000000000000000000000000000000000000000..9ce4349d87ed23b5cd8c56e5c60730ab2e7b884a
--- /dev/null
+++ b/xv6_user/lib/printf.c
@@ -0,0 +1,137 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+
+#include <stdarg.h>
+
+static char digits[] = "0123456789ABCDEF";
+
+int getchar()
+{
+    char byte = 0;
+    read(stdin, &byte, 1);
+    return byte;
+}
+
+int putchar(int c)
+{
+    char byte = c;
+    return write(stdout, &byte, 1);
+}
+
+int puts(const char *s)
+{
+    int r;
+    //r = -(write(stdout, s, strlen(s)) < 0 || putchar('\n') < 0);
+    r = -(write(stdout, s, strlen(s)) < 0);
+    return r;
+}
+
+static void
+putc(int fd, char c)
+{
+  write(fd, &c, 1);
+}
+
+static void
+printint(int fd, int xx, int base, int sgn)
+{
+  char buf[16];
+  int i, neg;
+  uint x;
+
+  neg = 0;
+  if(sgn && xx < 0){
+    neg = 1;
+    x = -xx;
+  } else {
+    x = xx;
+  }
+
+  i = 0;
+  do{
+    buf[i++] = digits[x % base];
+  }while((x /= base) != 0);
+  if(neg)
+    buf[i++] = '-';
+
+  while(--i >= 0)
+    putc(fd, buf[i]);
+}
+
+static void
+printptr(int fd, uint64 x) {
+  int i;
+  putc(fd, '0');
+  putc(fd, 'x');
+  for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4)
+    putc(fd, digits[x >> (sizeof(uint64) * 8 - 4)]);
+}
+
+// Print to the given fd. Only understands %d, %x, %p, %s.
+void
+vprintf(int fd, const char *fmt, va_list ap)
+{
+  char *s;
+  int c, i, state;
+
+  state = 0;
+  for(i = 0; fmt[i]; i++){
+    c = fmt[i] & 0xff;
+    if(state == 0){
+      if(c == '%'){
+        state = '%';
+      } else {
+        putc(fd, c);
+      }
+    } else if(state == '%'){
+      if(c == 'd'){
+        printint(fd, va_arg(ap, int), 10, 1);
+      } else if(c == 'l') {
+        printint(fd, va_arg(ap, uint64), 10, 0);
+      } else if(c == 'x') {
+        printint(fd, va_arg(ap, int), 16, 0);
+      } else if(c == 'p') {
+        printptr(fd, va_arg(ap, uint64));
+      } else if(c == 's'){
+        s = va_arg(ap, char*);
+        if(s == 0)
+          s = "(null)";
+        while(*s != 0){
+          putc(fd, *s);
+          s++;
+        }
+      } else if(c == 'c'){
+        putc(fd, va_arg(ap, uint));
+      } else if(c == '%'){
+        putc(fd, c);
+      } else {
+        // Unknown % sequence.  Print it to draw attention.
+        putc(fd, '%');
+        putc(fd, c);
+      }
+      state = 0;
+    }
+  }
+}
+
+void
+fprintf(int fd, const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start(ap, fmt);
+  vprintf(fd, fmt, ap);
+}
+
+void
+printf(const char *fmt, ...)
+{
+  va_list ap;
+
+  va_start(ap, fmt);
+  vprintf(1, fmt, ap);
+}
diff --git a/xv6_user/lib/syscall.c b/xv6_user/lib/syscall.c
new file mode 100644
index 0000000000000000000000000000000000000000..c23c24c70170fe2e4698e3648cfed6615b02d512
--- /dev/null
+++ b/xv6_user/lib/syscall.c
@@ -0,0 +1,255 @@
+#include "stddef.h"
+#include "unistd.h"
+
+#include "syscall.h"
+
+int open(const char *path, int flags)
+{
+    return syscall(SYS_openat, AT_FDCWD, path, flags, O_RDWR);
+}
+
+int openat(int dirfd,const char *path, int flags)
+{
+    return syscall(SYS_openat, dirfd, path, flags, 0600);
+}
+
+int close(int fd)
+{
+    return syscall(SYS_close, fd);
+}
+
+ssize_t read(int fd, void *buf, size_t len)
+{
+    return syscall(SYS_read, fd, buf, len);
+}
+
+ssize_t write(int fd, const void *buf, size_t len)
+{
+    return syscall(SYS_write, fd, buf, len);
+}
+
+pid_t getpid(void)
+{
+    return syscall(SYS_getpid);
+}
+
+pid_t getppid(void)
+{
+    return syscall(SYS_getppid);
+}
+
+int sched_yield(void)
+{
+    return syscall(SYS_sched_yield);
+}
+
+pid_t fork(void)
+{
+    return syscall(SYS_clone, SIGCHLD, 0);
+}
+
+pid_t clone(int (*fn)(void *arg), void *arg, void *stack, size_t stack_size, unsigned long flags)
+{
+    if (stack)
+	stack += stack_size;
+
+    return __clone(fn, stack, flags, NULL, NULL, NULL);
+    //return syscall(SYS_clone, fn, stack, flags, NULL, NULL, NULL);
+}
+void exit(int code)
+{
+    syscall(SYS_exit, code);
+}
+
+int waitpid(int pid, int *code, int options)
+{
+    return syscall(SYS_wait4, pid, code, options, 0);
+}
+
+int exec(char *name)
+{
+    return syscall(SYS_execve, name);
+}
+
+int execve(const char *name, char *const argv[], char *const argp[])
+{
+    return syscall(SYS_execve, name, argv, argp);
+}
+
+int times(void *mytimes)
+{
+	return syscall(SYS_times, mytimes);
+}
+
+int64 get_time()
+{
+    TimeVal time;
+    int err = sys_get_time(&time, 0);
+    if (err == 0)
+    {
+        return ((time.sec & 0xffff) * 1000 + time.usec / 1000);
+    }
+    else
+    {
+        return -1;
+    }
+}
+
+int sys_get_time(TimeVal *ts, int tz)
+{
+    return syscall(SYS_gettimeofday, ts, tz);
+}
+
+int time(unsigned long *tloc)
+{
+    return syscall(SYS_time, tloc);
+}
+
+int sleep(unsigned long long time)
+{
+    TimeVal tv = {.sec = time, .usec = 0};
+    if (syscall(SYS_nanosleep, &tv, &tv)) return tv.sec;
+    return 0;
+}
+
+int set_priority(int prio)
+{
+    return syscall(SYS_setpriority, prio);
+}
+
+void *mmap(void *start, size_t len, int prot, int flags, int fd, off_t off)
+{
+    return syscall(SYS_mmap, start, len, prot, flags, fd, off);
+}
+
+int munmap(void *start, size_t len)
+{
+    return syscall(SYS_munmap, start, len);
+}
+
+int wait(int *code)
+{
+    return waitpid((int)-1, code, 0);
+}
+
+int spawn(char *file)
+{
+    return syscall(SYS_spawn, file);
+}
+
+int mailread(void *buf, int len)
+{
+    return syscall(SYS_mailread, buf, len);
+}
+
+int mailwrite(int pid, void *buf, int len)
+{
+    return syscall(SYS_mailwrite, pid, buf, len);
+}
+
+int fstat(int fd, struct kstat *st)
+{
+    return syscall(SYS_fstat, fd, st);
+}
+
+int sys_linkat(int olddirfd, char *oldpath, int newdirfd, char *newpath, unsigned int flags)
+{
+    return syscall(SYS_linkat, olddirfd, oldpath, newdirfd, newpath, flags);
+}
+
+int sys_unlinkat(int dirfd, char *path, unsigned int flags)
+{
+    return syscall(SYS_unlinkat, dirfd, path, flags);
+}
+
+int link(char *old_path, char *new_path)
+{
+    return sys_linkat(AT_FDCWD, old_path, AT_FDCWD, new_path, 0);
+}
+
+int unlink(const char *path)
+{
+    return sys_unlinkat(AT_FDCWD, path, 0);
+}
+
+int uname(void *buf)
+{
+    return syscall(SYS_uname, buf);
+}
+
+int brk(void *addr)
+{
+    return syscall(SYS_brk, addr);
+}
+
+char *getcwd(char *buf, size_t size){
+    return syscall(SYS_getcwd, buf, size);
+}
+
+int chdir(const char *path){
+    return syscall(SYS_chdir, path);
+}
+
+int mkdir(const char *path, mode_t mode){
+    return syscall(SYS_mkdirat, AT_FDCWD, path, mode);
+}
+
+int getdents(int fd, struct linux_dirent64 *dirp64, unsigned long len){
+    //return syscall(SYS_getdents64, fd, dirp64, len);
+    return syscall(SYS_getdents64, fd, dirp64, len);
+}
+
+int pipe(int fd[2]){
+    return syscall(SYS_pipe2, fd, 0);
+}
+
+int dup(int fd){
+    return syscall(SYS_dup, fd);
+}
+
+int dup2(int old, int new){
+    return syscall(SYS_dup3, old, new, 0);
+}
+
+int mount(const char *special, const char *dir, const char *fstype, unsigned long flags, const void *data)
+{
+        return syscall(SYS_mount, special, dir, fstype, flags, data);
+}
+
+int umount(const char *special)
+{
+        return syscall(SYS_umount2, special, 0);
+}
+
+/* above is the oscomp's required syscall, don't modify, add new syscall below */
+/* ================================================================================= */
+
+/* add */
+int mknod(const char* path, short major, short minor) {
+    return syscall(SYS_mknod, path, major, minor);
+}
+
+char *sbrk(int increment) {
+    return syscall(SYS_sbrk, increment);
+}
+
+int kill(int pid) {
+    return syscall(SYS_kill, pid);
+}
+
+void shutdown() {
+    return syscall(SYS_shutdown);
+}
+
+/* debug */
+int print_pgtable() {
+    return syscall(SYS_print_pgtable);
+}
+
+int print_vma() {
+    return syscall(SYS_print_vma);
+}
+
+void print_rawfile(int fd, int printdir) {
+    return syscall(SYS_print_rawfile, fd, printdir);
+}
\ No newline at end of file
diff --git a/xv6_user/lib/syscall.h b/xv6_user/lib/syscall.h
new file mode 100644
index 0000000000000000000000000000000000000000..4f653e9df399c5b0ceb03e02856d9994235480da
--- /dev/null
+++ b/xv6_user/lib/syscall.h
@@ -0,0 +1,30 @@
+#ifndef __SYSCALL_H__
+#define __SYSCALL_H__
+
+#include "syscall_arch.h"
+#include "syscall_ids.h"
+
+#ifndef __scc
+#define __scc(X) ((long)(X))
+typedef long syscall_arg_t;
+#endif
+
+#define __syscall1(n, a) __syscall1(n, __scc(a))
+#define __syscall2(n, a, b) __syscall2(n, __scc(a), __scc(b))
+#define __syscall3(n, a, b, c) __syscall3(n, __scc(a), __scc(b), __scc(c))
+#define __syscall4(n, a, b, c, d) __syscall4(n, __scc(a), __scc(b), __scc(c), __scc(d))
+#define __syscall5(n, a, b, c, d, e) __syscall5(n, __scc(a), __scc(b), __scc(c), __scc(d), __scc(e))
+#define __syscall6(n, a, b, c, d, e, f) __syscall6(n, __scc(a), __scc(b), __scc(c), __scc(d), __scc(e), __scc(f))
+
+#define __SYSCALL_NARGS_X(a, b, c, d, e, f, g, h, n, ...) n
+#define __SYSCALL_NARGS(...) __SYSCALL_NARGS_X(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0, )
+#define __SYSCALL_CONCAT_X(a, b) a##b
+#define __SYSCALL_CONCAT(a, b) __SYSCALL_CONCAT_X(a, b)
+#define __SYSCALL_DISP(b, ...)                        \
+    __SYSCALL_CONCAT(b, __SYSCALL_NARGS(__VA_ARGS__)) \
+    (__VA_ARGS__)
+
+#define __syscall(...) __SYSCALL_DISP(__syscall, __VA_ARGS__)
+#define syscall(...) __syscall(__VA_ARGS__)
+
+#endif // __SYSCALL_H
diff --git a/xv6_user/lib/syscall_arch.h b/xv6_user/lib/syscall_arch.h
new file mode 100644
index 0000000000000000000000000000000000000000..042d7db1d698edd5cf76c00e95988bad50428420
--- /dev/null
+++ b/xv6_user/lib/syscall_arch.h
@@ -0,0 +1,80 @@
+#define __SYSCALL_LL_E(x) (x)
+#define __SYSCALL_LL_O(x) (x)
+
+#define __asm_syscall(...)             \
+    __asm__ __volatile__("ecall\n\t"   \
+                         : "=r"(a0)    \
+                         : __VA_ARGS__ \
+                         : "memory");  \
+    return a0;
+
+static inline long __syscall0(long n)
+{
+    register long a7 __asm__("a7") = n;
+    register long a0 __asm__("a0");
+    __asm_syscall("r"(a7))
+}
+
+static inline long __syscall1(long n, long a)
+{
+    register long a7 __asm__("a7") = n;
+    register long a0 __asm__("a0") = a;
+    __asm_syscall("r"(a7), "0"(a0))
+}
+
+static inline long __syscall2(long n, long a, long b)
+{
+    register long a7 __asm__("a7") = n;
+    register long a0 __asm__("a0") = a;
+    register long a1 __asm__("a1") = b;
+    __asm_syscall("r"(a7), "0"(a0), "r"(a1))
+}
+
+static inline long __syscall3(long n, long a, long b, long c)
+{
+    register long a7 __asm__("a7") = n;
+    register long a0 __asm__("a0") = a;
+    register long a1 __asm__("a1") = b;
+    register long a2 __asm__("a2") = c;
+    __asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2))
+}
+
+static inline long __syscall4(long n, long a, long b, long c, long d)
+{
+    register long a7 __asm__("a7") = n;
+    register long a0 __asm__("a0") = a;
+    register long a1 __asm__("a1") = b;
+    register long a2 __asm__("a2") = c;
+    register long a3 __asm__("a3") = d;
+    __asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2), "r"(a3))
+}
+
+static inline long __syscall5(long n, long a, long b, long c, long d, long e)
+{
+    register long a7 __asm__("a7") = n;
+    register long a0 __asm__("a0") = a;
+    register long a1 __asm__("a1") = b;
+    register long a2 __asm__("a2") = c;
+    register long a3 __asm__("a3") = d;
+    register long a4 __asm__("a4") = e;
+    __asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4))
+}
+
+static inline long __syscall6(long n, long a, long b, long c, long d, long e, long f)
+{
+    register long a7 __asm__("a7") = n;
+    register long a0 __asm__("a0") = a;
+    register long a1 __asm__("a1") = b;
+    register long a2 __asm__("a2") = c;
+    register long a3 __asm__("a3") = d;
+    register long a4 __asm__("a4") = e;
+    register long a5 __asm__("a5") = f;
+    __asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5))
+}
+
+#define VDSO_USEFUL
+/* We don't have a clock_gettime function.
+#define VDSO_CGT_SYM "__vdso_clock_gettime"
+#define VDSO_CGT_VER "LINUX_2.6" */
+
+#define IPC_64 0
diff --git a/xv6_user/lib/syscall_ids.h b/xv6_user/lib/syscall_ids.h
new file mode 100644
index 0000000000000000000000000000000000000000..914d3f8449ca88e83a721a9accddeaca61020742
--- /dev/null
+++ b/xv6_user/lib/syscall_ids.h
@@ -0,0 +1,291 @@
+#define SYS_io_setup 0
+#define SYS_io_destroy 1
+#define SYS_io_submit 2
+#define SYS_io_cancel 3
+#define SYS_io_getevents 4
+#define SYS_setxattr 5
+#define SYS_lsetxattr 6
+#define SYS_fsetxattr 7
+#define SYS_getxattr 8
+#define SYS_lgetxattr 9
+#define SYS_fgetxattr 10
+#define SYS_listxattr 11
+#define SYS_llistxattr 12
+#define SYS_flistxattr 13
+#define SYS_removexattr 14
+#define SYS_lremovexattr 15
+#define SYS_fremovexattr 16
+#define SYS_getcwd 17
+#define SYS_lookup_dcookie 18
+#define SYS_eventfd2 19
+#define SYS_epoll_create1 20
+#define SYS_epoll_ctl 21
+#define SYS_epoll_pwait 22
+#define SYS_dup 23
+#define SYS_dup3 24
+#define SYS_fcntl 25
+#define SYS_inotify_init1 26
+#define SYS_inotify_add_watch 27
+#define SYS_inotify_rm_watch 28
+#define SYS_ioctl 29
+#define SYS_ioprio_set 30
+#define SYS_ioprio_get 31
+#define SYS_flock 32
+#define SYS_mknodat 33
+#define SYS_mkdirat 34
+#define SYS_unlinkat 35
+#define SYS_symlinkat 36
+#define SYS_linkat 37
+#define SYS_umount2 39
+#define SYS_mount 40
+#define SYS_pivot_root 41
+#define SYS_nfsservctl 42
+#define SYS_statfs 43
+#define SYS_fstatfs 44
+#define SYS_truncate 45
+#define SYS_ftruncate 46
+#define SYS_fallocate 47
+#define SYS_faccessat 48
+#define SYS_chdir 49
+#define SYS_fchdir 50
+#define SYS_chroot 51
+#define SYS_fchmod 52
+#define SYS_fchmodat 53
+#define SYS_fchownat 54
+#define SYS_fchown 55
+#define SYS_openat 56
+#define SYS_close 57
+#define SYS_vhangup 58
+#define SYS_pipe2 59
+#define SYS_quotactl 60
+#define SYS_getdents64 61
+#define SYS_lseek 62
+#define SYS_read 63
+#define SYS_write 64
+#define SYS_readv 65
+#define SYS_writev 66
+#define SYS_pread64 67
+#define SYS_pwrite64 68
+#define SYS_preadv 69
+#define SYS_pwritev 70
+#define SYS_sendfile 71
+#define SYS_pselect6 72
+#define SYS_ppoll 73
+#define SYS_signalfd4 74
+#define SYS_vmsplice 75
+#define SYS_splice 76
+#define SYS_tee 77
+#define SYS_readlinkat 78
+#define SYS_fstatat 79
+#define SYS_fstat 80
+#define SYS_sync 81
+#define SYS_fsync 82
+#define SYS_fdatasync 83
+#define SYS_sync_file_range 84
+#define SYS_timerfd_create 85
+#define SYS_timerfd_settime 86
+#define SYS_timerfd_gettime 87
+#define SYS_utimensat 88
+#define SYS_acct 89
+#define SYS_capget 90
+#define SYS_capset 91
+#define SYS_personality 92
+#define SYS_exit 93
+#define SYS_exit_group 94
+#define SYS_waitid 95
+#define SYS_set_tid_address 96
+#define SYS_unshare 97
+#define SYS_futex 98
+#define SYS_set_robust_list 99
+#define SYS_get_robust_list 100
+#define SYS_nanosleep 101
+#define SYS_getitimer 102
+#define SYS_setitimer 103
+#define SYS_kexec_load 104
+#define SYS_init_module 105
+#define SYS_delete_module 106
+#define SYS_timer_create 107
+#define SYS_timer_gettime 108
+#define SYS_timer_getoverrun 109
+#define SYS_timer_settime 110
+#define SYS_timer_delete 111
+#define SYS_clock_settime 112
+#define SYS_clock_gettime 113
+#define SYS_clock_getres 114
+#define SYS_clock_nanosleep 115
+#define SYS_syslog 116
+#define SYS_ptrace 117
+#define SYS_sched_setparam 118
+#define SYS_sched_setscheduler 119
+#define SYS_sched_getscheduler 120
+#define SYS_sched_getparam 121
+#define SYS_sched_setaffinity 122
+#define SYS_sched_getaffinity 123
+#define SYS_sched_yield 124
+#define SYS_sched_get_priority_max 125
+#define SYS_sched_get_priority_min 126
+#define SYS_sched_rr_get_interval 127
+#define SYS_restart_syscall 128
+#define SYS_kill 129
+#define SYS_tkill 130
+#define SYS_tgkill 131
+#define SYS_sigaltstack 132
+#define SYS_rt_sigsuspend 133
+#define SYS_rt_sigaction 134
+#define SYS_rt_sigprocmask 135
+#define SYS_rt_sigpending 136
+#define SYS_rt_sigtimedwait 137
+#define SYS_rt_sigqueueinfo 138
+#define SYS_rt_sigreturn 139
+#define SYS_setpriority 140
+#define SYS_getpriority 141
+#define SYS_reboot 142
+#define SYS_setregid 143
+#define SYS_setgid 144
+#define SYS_setreuid 145
+#define SYS_setuid 146
+#define SYS_setresuid 147
+#define SYS_getresuid 148
+#define SYS_setresgid 149
+#define SYS_getresgid 150
+#define SYS_setfsuid 151
+#define SYS_setfsgid 152
+#define SYS_times 153
+#define SYS_time 1062
+#define SYS_setpgid 154
+#define SYS_getpgid 155
+#define SYS_getsid 156
+#define SYS_setsid 157
+#define SYS_getgroups 158
+#define SYS_setgroups 159
+#define SYS_uname 160
+#define SYS_sethostname 161
+#define SYS_setdomainname 162
+#define SYS_getrlimit 163
+#define SYS_setrlimit 164
+#define SYS_getrusage 165
+#define SYS_umask 166
+#define SYS_prctl 167
+#define SYS_getcpu 168
+#define SYS_gettimeofday 169
+#define SYS_settimeofday 170
+#define SYS_adjtimex 171
+#define SYS_getpid 172
+#define SYS_getppid 173
+#define SYS_getuid 174
+#define SYS_geteuid 175
+#define SYS_getgid 176
+#define SYS_getegid 177
+#define SYS_gettid 178
+#define SYS_sysinfo 179
+#define SYS_mq_open 180
+#define SYS_mq_unlink 181
+#define SYS_mq_timedsend 182
+#define SYS_mq_timedreceive 183
+#define SYS_mq_notify 184
+#define SYS_mq_getsetattr 185
+#define SYS_msgget 186
+#define SYS_msgctl 187
+#define SYS_msgrcv 188
+#define SYS_msgsnd 189
+#define SYS_semget 190
+#define SYS_semctl 191
+#define SYS_semtimedop 192
+#define SYS_semop 193
+#define SYS_shmget 194
+#define SYS_shmctl 195
+#define SYS_shmat 196
+#define SYS_shmdt 197
+#define SYS_socket 198
+#define SYS_socketpair 199
+#define SYS_bind 200
+#define SYS_listen 201
+#define SYS_accept 202
+#define SYS_connect 203
+#define SYS_getsockname 204
+#define SYS_getpeername 205
+#define SYS_sendto 206
+#define SYS_recvfrom 207
+#define SYS_setsockopt 208
+#define SYS_getsockopt 209
+#define SYS_shutdown 210
+#define SYS_sendmsg 211
+#define SYS_recvmsg 212
+#define SYS_readahead 213
+#define SYS_brk 214
+#define SYS_munmap 215
+#define SYS_mremap 216
+#define SYS_add_key 217
+#define SYS_request_key 218
+#define SYS_keyctl 219
+#define SYS_clone 220
+#define SYS_execve 221
+#define SYS_mmap 222
+#define SYS_fadvise64 223
+#define SYS_swapon 224
+#define SYS_swapoff 225
+#define SYS_mprotect 226
+#define SYS_msync 227
+#define SYS_mlock 228
+#define SYS_munlock 229
+#define SYS_mlockall 230
+#define SYS_munlockall 231
+#define SYS_mincore 232
+#define SYS_madvise 233
+#define SYS_remap_file_pages 234
+#define SYS_mbind 235
+#define SYS_get_mempolicy 236
+#define SYS_set_mempolicy 237
+#define SYS_migrate_pages 238
+#define SYS_move_pages 239
+#define SYS_rt_tgsigqueueinfo 240
+#define SYS_perf_event_open 241
+#define SYS_accept4 242
+#define SYS_recvmmsg 243
+#define SYS_arch_specific_syscall 244
+#define SYS_wait4 260
+#define SYS_prlimit64 261
+#define SYS_fanotify_init 262
+#define SYS_fanotify_mark 263
+#define SYS_name_to_handle_at 264
+#define SYS_open_by_handle_at 265
+#define SYS_clock_adjtime 266
+#define SYS_syncfs 267
+#define SYS_setns 268
+#define SYS_sendmmsg 269
+#define SYS_process_vm_readv 270
+#define SYS_process_vm_writev 271
+#define SYS_kcmp 272
+#define SYS_finit_module 273
+#define SYS_sched_setattr 274
+#define SYS_sched_getattr 275
+#define SYS_renameat2 276
+#define SYS_seccomp 277
+#define SYS_getrandom 278
+#define SYS_memfd_create 279
+#define SYS_bpf 280
+#define SYS_execveat 281
+#define SYS_userfaultfd 282
+#define SYS_membarrier 283
+#define SYS_mlock2 284
+#define SYS_copy_file_range 285
+#define SYS_preadv2 286
+#define SYS_pwritev2 287
+#define SYS_pkey_mprotect 288
+#define SYS_pkey_alloc 289
+#define SYS_pkey_free 290
+#define SYS_statx 291
+#define SYS_io_pgetevents 292
+#define SYS_rseq 293
+#define SYS_kexec_file_load 294
+#define SYS_riscv_flush_icache 244 + 15
+#define SYS_spawn 400
+#define SYS_mailread 401
+#define SYS_mailwrite 402
+
+#define SYS_mknod 16
+#define SYS_sbrk 11
+#define SYS_fork 0
+#define SYS_print_pgtable 500
+#define SYS_print_vma 501
+#define SYS_print_rawfile 502
\ No newline at end of file
diff --git a/xv6_user/lib/ulib.c b/xv6_user/lib/ulib.c
new file mode 100644
index 0000000000000000000000000000000000000000..66678b6e476015c3952f6fc6521983046f97c2c4
--- /dev/null
+++ b/xv6_user/lib/ulib.c
@@ -0,0 +1,160 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+
+//
+// wrapper so that it's OK if main() does not call exit().
+//
+void
+_main()
+{
+  extern int main();
+  main();
+  exit(0);
+}
+
+char*
+strcpy(char *s, const char *t)
+{
+  char *os;
+
+  os = s;
+  while((*s++ = *t++) != 0)
+    ;
+  return os;
+}
+
+int
+strcmp(const char *p, const char *q)
+{
+  while(*p && *p == *q)
+    p++, q++;
+  return (uchar)*p - (uchar)*q;
+}
+
+size_t strlen(const char *s)
+{
+  int n;
+
+  for(n = 0; s[n]; n++)
+    ;
+  return n;
+}
+
+void*
+memset(void *dst, int c, size_t n)
+{
+  char *cdst = (char *) dst;
+  int i;
+  for(i = 0; i < n; i++){
+    cdst[i] = c;
+  }
+  return dst;
+}
+
+char*
+strchr(const char *s, char c)
+{
+  for(; *s; s++)
+    if(*s == c)
+      return (char*)s;
+  return 0;
+}
+
+char*
+gets(char *buf, int max)
+{
+  int i, cc;
+  char c;
+
+  for(i=0; i+1 < max; ){
+    cc = read(0, &c, 1);
+    if(cc < 1)
+      break;
+    buf[i++] = c;
+    if(c == '\n' || c == '\r')
+      break;
+  }
+  buf[i] = '\0';
+  return buf;
+}
+
+// int
+// stat(const char *n, struct stat *st)
+// {
+//   int fd;
+//   int r;
+
+//   fd = open(n, O_RDONLY);
+//   if(fd < 0)
+//     return -1;
+//   r = fstat(fd, st);
+//   close(fd);
+//   return r;
+// }
+
+int
+atoi(const char *s)
+{
+  int n;
+
+  n = 0;
+  while('0' <= *s && *s <= '9')
+    n = n*10 + *s++ - '0';
+  return n;
+}
+
+void*
+memmove(void *vdst, const void *vsrc, int n)
+{
+  char *dst;
+  const char *src;
+
+  dst = vdst;
+  src = vsrc;
+  if (src > dst) {
+    while(n-- > 0)
+      *dst++ = *src++;
+  } else {
+    dst += n;
+    src += n;
+    while(n-- > 0)
+      *--dst = *--src;
+  }
+  return vdst;
+}
+
+int
+memcmp(const void *s1, const void *s2, uint n)
+{
+  const char *p1 = s1, *p2 = s2;
+  while (n-- > 0) {
+    if (*p1 != *p2) {
+      return *p1 - *p2;
+    }
+    p1++;
+    p2++;
+  }
+  return 0;
+}
+
+void *
+memcpy(void *dst, const void *src, uint n)
+{
+  return memmove(dst, src, n);
+}
+
+char* strcat(char* dest, const char* src) {
+    char* p = dest;
+    while (*p) {
+        ++p;
+    }
+    while (*src) {
+        *p++ = *src++;
+    }
+    *p = '\0';
+    return dest;
+}
diff --git a/xv6_user/lib/umalloc.c b/xv6_user/lib/umalloc.c
new file mode 100644
index 0000000000000000000000000000000000000000..b88ac03782e4b2d7f609242f1f64716de6412dc7
--- /dev/null
+++ b/xv6_user/lib/umalloc.c
@@ -0,0 +1,93 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+// #include "param.h"
+
+// Memory allocator by Kernighan and Ritchie,
+// The C programming Language, 2nd ed.  Section 8.7.
+
+typedef long Align;
+
+union header {
+  struct {
+    union header *ptr;
+    uint size;
+  } s;
+  Align x;
+};
+
+typedef union header Header;
+
+static Header base;
+static Header *freep;
+
+void
+free(void *ap)
+{
+  Header *bp, *p;
+
+  bp = (Header*)ap - 1;
+  for(p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
+    if(p >= p->s.ptr && (bp > p || bp < p->s.ptr))
+      break;
+  if(bp + bp->s.size == p->s.ptr){
+    bp->s.size += p->s.ptr->s.size;
+    bp->s.ptr = p->s.ptr->s.ptr;
+  } else
+    bp->s.ptr = p->s.ptr;
+  if(p + p->s.size == bp){
+    p->s.size += bp->s.size;
+    p->s.ptr = bp->s.ptr;
+  } else
+    p->s.ptr = bp;
+  freep = p;
+}
+
+static Header*
+morecore(uint nu)
+{
+  char *p;
+  Header *hp;
+
+  if(nu < 4096)
+    nu = 4096;
+  p = sbrk(nu * sizeof(Header));
+  if(p == (char*)-1)
+    return 0;
+  hp = (Header*)p;
+  hp->s.size = nu;
+  free((void*)(hp + 1));
+  return freep;
+}
+
+void*
+malloc(uint nbytes)
+{
+  Header *p, *prevp;
+  uint nunits;
+
+  nunits = (nbytes + sizeof(Header) - 1)/sizeof(Header) + 1;
+  if((prevp = freep) == 0){
+    base.s.ptr = freep = prevp = &base;
+    base.s.size = 0;
+  }
+  for(p = prevp->s.ptr; ; prevp = p, p = p->s.ptr){
+    if(p->s.size >= nunits){
+      if(p->s.size == nunits)
+        prevp->s.ptr = p->s.ptr;
+      else {
+        p->s.size -= nunits;
+        p += p->s.size;
+        p->s.size = nunits;
+      }
+      freep = prevp;
+      return (void*)(p + 1);
+    }
+    if(p == freep)
+      if((p = morecore(nunits)) == 0)
+        return 0;
+  }
+}
diff --git a/xv6_user/src/cat.c b/xv6_user/src/cat.c
new file mode 100644
index 0000000000000000000000000000000000000000..c6befc116bb36a71e174431edb479f7cf622fcc1
--- /dev/null
+++ b/xv6_user/src/cat.c
@@ -0,0 +1,45 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+
+char buf[512];
+
+void cat(int fd)
+{
+  int n;
+
+  while((n = read(fd, buf, sizeof(buf))) > 0) {
+    if (write(1, buf, n) != n) {
+      fprintf(2, "cat: write error\n");
+      exit(1);
+    }
+  }
+  printf("\n");
+  if(n < 0){
+    fprintf(2, "cat: read error\n");
+    exit(1);
+  }
+}
+
+int main(int argc, char *argv[])
+{
+  int fd, i;
+
+  if(argc <= 1){
+    cat(0);
+    exit(0);
+  }
+
+  for(i = 1; i < argc; i++){
+    if((fd = open(argv[i], 0)) < 0){
+      fprintf(2, "cat: cannot open %s\n", argv[i]);
+      exit(1);
+    }
+    cat(fd);
+    close(fd);
+  }
+  return 0;
+}
diff --git a/xv6_user/src/echo.c b/xv6_user/src/echo.c
new file mode 100644
index 0000000000000000000000000000000000000000..9daa3f417b66aa16f8b1eedc2dc284cff29f3aed
--- /dev/null
+++ b/xv6_user/src/echo.c
@@ -0,0 +1,20 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+int main(int argc, char *argv[])
+{
+  int i;
+  for(i = 1; i < argc; i++){
+    write(1, argv[i], strlen(argv[i]));
+    if(i + 1 < argc){
+      write(1, " ", 1);
+    } else {
+      write(1, "\n", 1);
+    }
+  }
+  exit(0);
+  return 0;
+}
diff --git a/xv6_user/src/grep.c b/xv6_user/src/grep.c
new file mode 100644
index 0000000000000000000000000000000000000000..a4c1262b56620d4004d786f40eeac955cb6c015b
--- /dev/null
+++ b/xv6_user/src/grep.c
@@ -0,0 +1,108 @@
+// Simple grep.  Only supports ^ . * $ operators.
+
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+char buf[1024];
+int match(char*, char*);
+
+void
+grep(char *pattern, int fd)
+{
+  int n, m;
+  char *p, *q;
+
+  m = 0;
+  while((n = read(fd, buf+m, sizeof(buf)-m-1)) > 0){
+    m += n;
+    buf[m] = '\0';
+    p = buf;
+    while((q = strchr(p, '\n')) != 0){
+      *q = 0;
+      if(match(pattern, p)){
+        *q = '\n';
+        write(1, p, q+1 - p);
+      }
+      p = q+1;
+    }
+    if(m > 0){
+      m -= p - buf;
+      memmove(buf, p, m);
+    }
+  }
+}
+
+int
+main(int argc, char *argv[])
+{
+  int fd, i;
+  char *pattern;
+
+  if(argc <= 1){
+    fprintf(2, "usage: grep pattern [file ...]\n");
+    exit(1);
+  }
+  pattern = argv[1];
+
+  if(argc <= 2){
+    grep(pattern, 0);
+    exit(0);
+  }
+
+  for(i = 2; i < argc; i++){
+    if((fd = open(argv[i], 0)) < 0){
+      printf("grep: cannot open %s\n", argv[i]);
+      exit(1);
+    }
+    grep(pattern, fd);
+    close(fd);
+  }
+  return 0;
+}
+
+// Regexp matcher from Kernighan & Pike,
+// The Practice of Programming, Chapter 9, or
+// https://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html
+
+int matchhere(char*, char*);
+int matchstar(int, char*, char*);
+
+int
+match(char *re, char *text)
+{
+  if(re[0] == '^')
+    return matchhere(re+1, text);
+  do{  // must look at empty string
+    if(matchhere(re, text))
+      return 1;
+  }while(*text++ != '\0');
+  return 0;
+}
+
+// matchhere: search for re at beginning of text
+int matchhere(char *re, char *text)
+{
+  if(re[0] == '\0')
+    return 1;
+  if(re[1] == '*')
+    return matchstar(re[0], re+2, text);
+  if(re[0] == '$' && re[1] == '\0')
+    return *text == '\0';
+  if(*text!='\0' && (re[0]=='.' || re[0]==*text))
+    return matchhere(re+1, text+1);
+  return 0;
+}
+
+// matchstar: search for c*re at beginning of text
+int matchstar(int c, char *re, char *text)
+{
+  do{  // a * matches zero or more instances
+    if(matchhere(re, text))
+      return 1;
+  }while(*text!='\0' && (*text++==c || c=='.'));
+  return 0;
+}
+
diff --git a/xv6_user/src/grind.c b/xv6_user/src/grind.c
new file mode 100644
index 0000000000000000000000000000000000000000..b60462545ba9ef3754ca0f50131cc02e53db39f9
--- /dev/null
+++ b/xv6_user/src/grind.c
@@ -0,0 +1,352 @@
+//
+// run random system calls in parallel forever.
+//
+
+#define USER
+#include "param.h"
+#include "types.h"
+#include "fs/stat.h"
+#include "user.h"
+ 
+#include "fs/fcntl.h"
+#include "syscall_gen/syscall_num.h"
+#include "../../memlayout.h"
+#include "../../riscv.h"
+
+// from FreeBSD.
+int
+do_rand(unsigned long *ctx)
+{
+/*
+ * Compute x = (7^5 * x) mod (2^31 - 1)
+ * without overflowing 31 bits:
+ *      (2^31 - 1) = 127773 * (7^5) + 2836
+ * From "Random number generators: good ones are hard to find",
+ * Park and Miller, Communications of the ACM, vol. 31, no. 10,
+ * October 1988, p. 1195.
+ */
+    long hi, lo, x;
+
+    /* Transform to [1, 0x7ffffffe] range. */
+    x = (*ctx % 0x7ffffffe) + 1;
+    hi = x / 127773;
+    lo = x % 127773;
+    x = 16807 * lo - 2836 * hi;
+    if (x < 0)
+        x += 0x7fffffff;
+    /* Transform to [0, 0x7ffffffd] range. */
+    x--;
+    *ctx = x;
+    return (x);
+}
+
+unsigned long rand_next = 1;
+
+int
+rand(void)
+{
+    return (do_rand(&rand_next));
+}
+
+void
+go(int which_child)
+{
+  int fd = -1;
+  static char buf[999];
+  char *break0 = sbrk(0);
+  uint64 iters = 0;
+
+  mkdir("grindir");
+  if(chdir("grindir") != 0){
+    printf("grind: chdir grindir failed\n");
+    exit(1);
+  }
+  chdir("/");
+  
+  while(1){
+    iters++;
+    if((iters % 500) == 0)
+      write(1, which_child?"B":"A", 1);
+    int what = rand() % 23;
+    if(what == 1){
+      close(open("grindir/../a", O_CREATE|O_RDWR));
+    } else if(what == 2){
+      close(open("grindir/../grindir/../b", O_CREATE|O_RDWR));
+    } else if(what == 3){
+      unlink("grindir/../a");
+    } else if(what == 4){
+      if(chdir("grindir") != 0){
+        printf("grind: chdir grindir failed\n");
+        exit(1);
+      }
+      unlink("../b");
+      chdir("/");
+    } else if(what == 5){
+      close(fd);
+      fd = open("/grindir/../a", O_CREATE|O_RDWR);
+    } else if(what == 6){
+      close(fd);
+      fd = open("/./grindir/./../b", O_CREATE|O_RDWR);
+    } else if(what == 7){
+      write(fd, buf, sizeof(buf));
+    } else if(what == 8){
+      read(fd, buf, sizeof(buf));
+    } else if(what == 9){
+      mkdir("grindir/../a");
+      close(open("a/../a/./a", O_CREATE|O_RDWR));
+      unlink("a/a");
+    } else if(what == 10){
+      mkdir("/../b");
+      close(open("grindir/../b/b", O_CREATE|O_RDWR));
+      unlink("b/b");
+    } else if(what == 11){
+      unlink("b");
+      link("../grindir/./../a", "../b");
+    } else if(what == 12){
+      unlink("../grindir/../a");
+      link(".././b", "/grindir/../a");
+    } else if(what == 13){
+      int pid = fork();
+      if(pid == 0){
+        exit(0);
+      } else if(pid < 0){
+        printf("grind: fork failed\n");
+        exit(1);
+      }
+      wait(0);
+    } else if(what == 14){
+      int pid = fork();
+      if(pid == 0){
+        fork();
+        fork();
+        exit(0);
+      } else if(pid < 0){
+        printf("grind: fork failed\n");
+        exit(1);
+      }
+      wait(0);
+    } else if(what == 15){
+      sbrk(6011);
+    } else if(what == 16){
+      if(sbrk(0) > break0)
+        sbrk(-(sbrk(0) - break0));
+    } else if(what == 17){
+      int pid = fork();
+      if(pid == 0){
+        close(open("a", O_CREATE|O_RDWR));
+        exit(0);
+      } else if(pid < 0){
+        printf("grind: fork failed\n");
+        exit(1);
+      }
+      if(chdir("../grindir/..") != 0){
+        printf("grind: chdir failed\n");
+        exit(1);
+      }
+      kill(pid);
+      wait(0);
+    } else if(what == 18){
+      int pid = fork();
+      if(pid == 0){
+        kill(getpid());
+        exit(0);
+      } else if(pid < 0){
+        printf("grind: fork failed\n");
+        exit(1);
+      }
+      wait(0);
+    } else if(what == 19){
+      int fds[2];
+      if(pipe(fds) < 0){
+        printf("grind: pipe failed\n");
+        exit(1);
+      }
+      int pid = fork();
+      if(pid == 0){
+        fork();
+        fork();
+        if(write(fds[1], "x", 1) != 1)
+          printf("grind: pipe write failed\n");
+        char c;
+        if(read(fds[0], &c, 1) != 1)
+          printf("grind: pipe read failed\n");
+        exit(0);
+      } else if(pid < 0){
+        printf("grind: fork failed\n");
+        exit(1);
+      }
+      close(fds[0]);
+      close(fds[1]);
+      wait(0);
+    } else if(what == 20){
+      int pid = fork();
+      if(pid == 0){
+        unlink("a");
+        mkdir("a");
+        chdir("a");
+        unlink("../a");
+        fd = open("x", O_CREATE|O_RDWR);
+        unlink("x");
+        exit(0);
+      } else if(pid < 0){
+        printf("grind: fork failed\n");
+        exit(1);
+      }
+      wait(0);
+    } else if(what == 21){
+      unlink("c");
+      // should always succeed. check that there are free i-nodes,
+      // file descriptors, blocks.
+      int fd1 = open("c", O_CREATE|O_RDWR);
+      if(fd1 < 0){
+        printf("grind: create c failed\n");
+        exit(1);
+      }
+      if(write(fd1, "x", 1) != 1){
+        printf("grind: write c failed\n");
+        exit(1);
+      }
+      struct stat st;
+      if(fstat(fd1, &st) != 0){
+        printf("grind: fstat failed\n");
+        exit(1);
+      }
+      if(st.size != 1){
+        printf("grind: fstat reports wrong size %d\n", (int)st.size);
+        exit(1);
+      }
+      if(st.ino > 200){
+        printf("grind: fstat reports crazy i-number %d\n", st.ino);
+        exit(1);
+      }
+      close(fd1);
+      unlink("c");
+    } else if(what == 22){
+      // echo hi | cat
+      int aa[2], bb[2];
+      if(pipe(aa) < 0){
+        fprintf(2, "grind: pipe failed\n");
+        exit(1);
+      }
+      if(pipe(bb) < 0){
+        fprintf(2, "grind: pipe failed\n");
+        exit(1);
+      }
+      int pid1 = fork();
+      if(pid1 == 0){
+        close(bb[0]);
+        close(bb[1]);
+        close(aa[0]);
+        close(1);
+        if(dup(aa[1]) != 1){
+          fprintf(2, "grind: dup failed\n");
+          exit(1);
+        }
+        close(aa[1]);
+        char *args[3] = { "echo", "hi", 0 };
+        exec("grindir/../echo", args);
+        fprintf(2, "grind: echo: not found\n");
+        exit(2);
+      } else if(pid1 < 0){
+        fprintf(2, "grind: fork failed\n");
+        exit(3);
+      }
+      int pid2 = fork();
+      if(pid2 == 0){
+        close(aa[1]);
+        close(bb[0]);
+        close(0);
+        if(dup(aa[0]) != 0){
+          fprintf(2, "grind: dup failed\n");
+          exit(4);
+        }
+        close(aa[0]);
+        close(1);
+        if(dup(bb[1]) != 1){
+          fprintf(2, "grind: dup failed\n");
+          exit(5);
+        }
+        close(bb[1]);
+        char *args[2] = { "cat", 0 };
+        exec("/cat", args);
+        fprintf(2, "grind: cat: not found\n");
+        exit(6);
+      } else if(pid2 < 0){
+        fprintf(2, "grind: fork failed\n");
+        exit(7);
+      }
+      close(aa[0]);
+      close(aa[1]);
+      close(bb[1]);
+      char buf[4] = { 0, 0, 0, 0 };
+      read(bb[0], buf+0, 1);
+      read(bb[0], buf+1, 1);
+      read(bb[0], buf+2, 1);
+      close(bb[0]);
+      int st1, st2;
+      wait(&st1);
+      wait(&st2);
+      if(st1 != 0 || st2 != 0 || strcmp(buf, "hi\n") != 0){
+        printf("grind: exec pipeline failed %d %d \"%s\"\n", st1, st2, buf);
+        exit(1);
+      }
+    }
+  }
+}
+
+void
+iter()
+{
+  unlink("a");
+  unlink("b");
+  
+  int pid1 = fork();
+  if(pid1 < 0){
+    printf("grind: fork failed\n");
+    exit(1);
+  }
+  if(pid1 == 0){
+    rand_next ^= 31;
+    go(0);
+    exit(0);
+  }
+
+  int pid2 = fork();
+  if(pid2 < 0){
+    printf("grind: fork failed\n");
+    exit(1);
+  }
+  if(pid2 == 0){
+    rand_next ^= 7177;
+    go(1);
+    exit(0);
+  }
+
+  int st1 = -1;
+  wait(&st1);
+  if(st1 != 0){
+    kill(pid1);
+    kill(pid2);
+  }
+  int st2 = -1;
+  wait(&st2);
+
+  exit(0);
+}
+
+int
+main()
+{
+  while(1){
+    int pid = fork();
+    if(pid == 0){
+      iter();
+      exit(0);
+    }
+    if(pid > 0){
+      wait(0);
+    }
+    sleep(20);
+    rand_next += 1;
+  }
+}
diff --git a/xv6_user/src/init.c b/xv6_user/src/init.c
new file mode 100644
index 0000000000000000000000000000000000000000..525f2a72843c6bae4b9d9063674fa58ad449ef62
--- /dev/null
+++ b/xv6_user/src/init.c
@@ -0,0 +1,72 @@
+// init: The initial user-level program
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+char *argv[] = { "sh", 0 };
+
+#define CONSOLE 1
+int
+main(void)
+{
+  int pid, wpid;
+
+  // if(open("console", O_RDWR) < 0){
+  //   // mknod("console", CONSOLE, 0);
+  //   open("console", O_RDWR);
+  // }
+  
+  if(openat(AT_FDCWD,"console.dev", O_RDWR) < 0){
+    mknod("console.dev", CONSOLE,0);
+    openat(AT_FDCWD,"console.dev", O_RDWR);
+  }
+
+  // printf("\n\tNow you come here, welcome my dear friend.\n\n");
+  dup(0);  // stdout
+  dup(0);  // stderr
+
+  printf("\n");
+  printf("██╗      ██████╗ ███████╗████████╗██╗    ██╗ █████╗ ██╗  ██╗███████╗██╗   ██╗██████╗ \n");
+  printf("██║     ██╔═══██╗██╔════╝╚══██╔══╝██║    ██║██╔══██╗██║ ██╔╝██╔════╝██║   ██║██╔══██╗\n");
+  printf("██║     ██║   ██║███████╗   ██║   ██║ █╗ ██║███████║█████╔╝ █████╗  ██║   ██║██████╔╝\n");
+  printf("██║     ██║   ██║╚════██║   ██║   ██║███╗██║██╔══██║██╔═██╗ ██╔══╝  ██║   ██║██╔═══╝ \n");
+  printf("███████╗╚██████╔╝███████║   ██║   ╚███╔███╔╝██║  ██║██║  ██╗███████╗╚██████╔╝██║     \n");
+  printf("╚══════╝ ╚═════╝ ╚══════╝   ╚═╝    ╚══╝╚══╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝ ╚═════╝ ╚═╝     \n");
+  printf("\n");
+
+  for (;;) {
+    printf("init: starting sh\n");
+    
+    pid = fork();
+    if (pid < 0) {
+      printf("init: fork failed\n");
+      exit(1);
+    }
+    if (pid == 0) {
+      printf("about to start sh\n");
+      execve("sh",argv,0);
+      printf("init: exec sh failed\n");
+      exit(1);
+    }
+    
+    
+    for(;;){
+      // this call to wait() returns if the shell exits,
+      // or if a parentless process exits.
+      wpid = wait((int *) 0);
+      if (wpid == pid) {
+        // the shell exited; restart it.
+        break;
+      } 
+      else if (wpid < 0){
+        printf("init: wait returned an error\n");
+        exit(1);
+      } 
+      else {
+        // it was a parentless process; do nothing.
+      }
+    }
+  }
+}
diff --git a/xv6_user/src/kalloctest.c b/xv6_user/src/kalloctest.c
new file mode 100644
index 0000000000000000000000000000000000000000..4b0f726cba18a146e91ee07081ccfe0ddf1fad8a
--- /dev/null
+++ b/xv6_user/src/kalloctest.c
@@ -0,0 +1,165 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "memory/memlayout.h"
+#include "memory/buddy.h"
+
+#define NCHILD 2
+#define N 100000
+#define SZ 4096
+
+// void test1(void);
+void test2(void);
+void test3(void);
+char buf[SZ];
+
+int
+main(int argc, char *argv[])
+{
+//   test1();
+  test2();
+  test3();
+  exit(0);
+  return 0;
+}
+
+// int ntas(int print)
+// {
+//   int n;
+//   char *c;
+
+//   if (statistics(buf, SZ) <= 0) {
+//     fprintf(2, "ntas: no stats\n");
+//   }
+//   c = strchr(buf, '=');
+//   n = atoi(c+2);
+//   if(print)
+//     printf("%s", buf);
+//   return n;
+// }
+
+// // Test concurrent kallocs and kfrees
+// void test1(void)
+// {
+//   void *a, *a1;
+//   int n, m;
+//   printf("start test1\n");  
+//   m = ntas(0);
+//   for(int i = 0; i < NCHILD; i++){
+//     int pid = fork();
+//     if(pid < 0){
+//       printf("fork failed");
+//       exit(-1);
+//     }
+//     if(pid == 0){
+//       for(i = 0; i < N; i++) {
+//         a = sbrk(4096);
+//         *(int *)(a+4) = 1;
+//         a1 = sbrk(-4096);
+//         if (a1 != a + 4096) {
+//           printf("wrong sbrk\n");
+//           exit(-1);
+//         }
+//       }
+//       exit(-1);
+//     }
+//   }
+
+//   for(int i = 0; i < NCHILD; i++){
+//     wait(0);
+//   }
+//   printf("test1 results:\n");
+//   n = ntas(1);
+//   if(n-m < 10) 
+//     printf("test1 OK\n");
+//   else
+//     printf("test1 FAIL\n");
+// }
+
+//
+// countfree() from usertests.c
+//
+int
+countfree()
+{
+  uint64 sz0 = (uint64)sbrk(0);
+  int n = 0;
+
+  while(1){
+    uint64 a = (uint64) sbrk(4096);
+    if(a == 0xffffffffffffffff){
+      break;
+    }
+    // modify the memory to make sure it's really allocated.
+    *(char *)(a + 4096 - 1) = 1;
+    n += 1;
+  }
+  sbrk(-((uint64)sbrk(0) - sz0));
+  return n;
+}
+
+// Test stealing
+void test2() {
+  int free0 = countfree();
+  int free1;
+  int n = (PHYSTOP-START_MEM)/PGSIZE;
+  printf("start test2\n");  
+  printf("total free number of pages: %d (out of %d)\n", free0, n);
+  if(n - free0 > 1000) {
+    printf("test2 FAILED: cannot allocate enough memory");
+    exit(-1);
+  }
+  for (int i = 0; i < 50; i++) {
+    free1 = countfree();
+    if(i % 10 == 9)
+      printf(".");
+    if(free1 != free0) {
+      printf("test2 FAIL: losing pages\n");
+      exit(-1);
+    }
+  }
+  printf("\ntest2 OK\n");  
+}
+
+// Test concurrent kalloc/kfree and stealing
+void test3(void)
+{
+  void *a, *a1;
+  printf("start test3\n");  
+  for(int i = 0; i < NCHILD; i++){
+    int pid = fork();
+    if(pid < 0){
+      printf("fork failed");
+      exit(-1);
+    }
+    if(pid == 0){
+      if (i == 0) {
+        for(i = 0; i < N; i++) {
+          a = sbrk(4096);
+          if ((uint64)a == -1) {
+            break;
+          }
+          *(int *)(a+4) = 1;
+          a1 = sbrk(-4096);
+          if (a1 != a + 4096) {
+            printf("wrong sbrk\n");
+            exit(-1);
+          }
+        }
+        printf("child done %d\n", i);
+        exit(0);
+      } else {
+        countfree();
+        printf("child done %d\n", i);
+        exit(0);
+      }
+    }
+  }
+
+  for(int i = 0; i < NCHILD; i++){
+    wait(0);
+  }
+  printf("test3 OK\n");
+}
diff --git a/xv6_user/src/kill.c b/xv6_user/src/kill.c
new file mode 100644
index 0000000000000000000000000000000000000000..2929b11d22a21c4ff438d2b94db9d7ac2d50e7f9
--- /dev/null
+++ b/xv6_user/src/kill.c
@@ -0,0 +1,19 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+int
+main(int argc, char **argv)
+{
+  int i;
+
+  if(argc < 2){
+    fprintf(2, "usage: kill pid...\n");
+    exit(1);
+  }
+  for(i=1; i<argc; i++)
+    kill(atoi(argv[i]));
+  return 0;
+}
diff --git a/xv6_user/src/ln.c b/xv6_user/src/ln.c
new file mode 100644
index 0000000000000000000000000000000000000000..737bd0435c5549927e55cffd8dae63f637d38ae6
--- /dev/null
+++ b/xv6_user/src/ln.c
@@ -0,0 +1,16 @@
+#define USER
+#include "types.h"
+#include "fs/stat.h"
+#include "user.h"
+
+int
+main(int argc, char *argv[])
+{
+  if(argc != 3){
+    fprintf(2, "Usage: ln old new\n");
+    exit(1);
+  }
+  if(link(argv[1], argv[2]) < 0)
+    fprintf(2, "link %s %s: failed\n", argv[1], argv[2]);
+  exit(0);
+}
diff --git a/xv6_user/src/ls.c b/xv6_user/src/ls.c
new file mode 100644
index 0000000000000000000000000000000000000000..176ba202a46375937e441fd979e2417b74894c50
--- /dev/null
+++ b/xv6_user/src/ls.c
@@ -0,0 +1,81 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+
+#define handle_error(msg) \
+               do { printf("%s\n",msg); exit(-1); } while (0)
+
+#define BUF_SIZE 4096
+
+int main(int argc, char *argv[]) {
+    int fd, nread;
+    // char buf[BUF_SIZE];
+    char *buf = (char*)malloc(BUF_SIZE);
+    struct linux_dirent64 *d;
+    int bpos;
+    unsigned char d_type;
+    
+    // printf("hello ls; %d\n",argc);
+    fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
+    if (fd == -1) {
+      handle_error("getdents");
+    }
+
+    for (;;) {
+        nread = getdents(fd, (struct linux_dirent64 *)buf, BUF_SIZE); 
+        if (nread == -1)
+            handle_error("getdents");
+
+        if (nread == 0)
+            break;
+
+        // printf("--------------- nread=%d ---------------\n", nread);
+        // printf("inode#    file type  d_reclen  d_off   d_name\n");
+        int ctr = 0;
+        for (bpos = 0; bpos < nread;) {
+            d = (struct linux_dirent64 *)(buf + bpos);
+            d_type = d->d_type;
+/*
+            printf("inode: %d      ", d->d_ino);
+            printf("dtype: %d      ", d_type);
+            printf("%s   ", (d_type == T_FILE)  ? "regular" :
+                             (d_type == T_DIR)  ? "directory" :
+                            //  (d_type == DT_FIFO) ? "FIFO" :
+                            //  (d_type == DT_SOCK) ? "socket" :
+                            //  (d_type == DT_LNK)  ? "symlink" :
+                             (d_type == T_DEVICE)  ? "dev" :
+                            //  (d_type == DT_CHR)  ? "char dev" :
+                                                   "???");
+            printf("%d %d %s \n", d->d_reclen,
+                   d->d_off, d->d_name);
+*/
+            switch (d_type) {
+            case T_DIR: 
+                printf("\033[34;1m%s\t\033[0m",d->d_name);
+                break;
+            case T_DEVICE: 
+                printf("\033[38;5;214m%s\t\033[0m",d->d_name);
+                break;
+            default:
+                printf("%s\t",d->d_name);
+                break;
+            }
+            
+            bpos += d->d_reclen;
+            if ( ++ctr == 6 ) {
+              write(STDOUT,"\n",1);
+              ctr = 0;
+            }
+
+            // break;
+        }
+        write(STDOUT,"\n",1);
+        // write(1, argv[i], strlen(argv[i]));
+    }
+    free(buf);
+    return 0;
+    exit(0);
+}
\ No newline at end of file
diff --git a/xv6_user/src/mkdir.c b/xv6_user/src/mkdir.c
new file mode 100644
index 0000000000000000000000000000000000000000..3f4edb81daeadf253ff4b5e7b53e59ba264ef410
--- /dev/null
+++ b/xv6_user/src/mkdir.c
@@ -0,0 +1,27 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+int
+main(int argc, char *argv[])
+{
+  int i;
+
+  if(argc != 2){
+    fprintf(2, "Usage: mkdir file\n");
+    exit(1);
+  }
+
+  for(i = 1; i < argc; i++){
+    // if(mkdirat(AT_FDCWD, argv[i], 0666) < 0){
+    if(mkdir(argv[i], 0666) < 0){
+      fprintf(2, "mkdirat: %s failed to create\n", argv[i]);
+      break;
+    }
+  }
+
+  exit(0);
+  return 0;
+}
diff --git a/xv6_user/src/mmaptest.c b/xv6_user/src/mmaptest.c
new file mode 100644
index 0000000000000000000000000000000000000000..1c80a61d74cc5037fe15b11c764b5b02d2946f23
--- /dev/null
+++ b/xv6_user/src/mmaptest.c
@@ -0,0 +1,356 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "riscv.h"
+#include "stdlib.h"
+#include "fs/vfs/fs_macro.h"
+
+void mmap_test();
+void fork_test();
+char buf[BSIZE];
+
+
+int
+main(int argc, char *argv[])
+{
+  mmap_test();
+  fork_test();
+  printf("mmaptest: all tests succeeded\n");
+  exit(0);
+  return 0;
+}
+
+char *testname = "???";
+
+void
+err(char *why)
+{
+  printf("mmaptest: %s failed: %s, pid=%d\n", testname, why, getpid());
+  exit(1);
+}
+
+//
+// check the content of the two mapped pages.
+//
+void
+_v1(char *p)
+{
+  int i;
+  for (i = 0; i < PGSIZE*2; i++) {
+    if (i < PGSIZE + (PGSIZE/2)) {
+      if (p[i] != 'A') {
+        printf("mismatch at %d, wanted 'A', got 0x%x\n", i, p[i]);
+        err("v1 mismatch (1)");
+      }
+    } else {
+      if (p[i] != 0) {
+        printf("mismatch at %d, wanted zero, got 0x%x\n", i, p[i]);
+        err("v1 mismatch (2)");
+      }
+    }
+  }
+}
+
+//
+// create a file to be mapped, containing
+// 1.5 pages of 'A' and half a page of zeros.
+//
+void
+makefile(const char *f)
+{
+  int i;
+  int n = PGSIZE/BSIZE;
+
+  unlink(f);
+  int fd = open(f, O_WRONLY | O_CREATE);
+  if (fd == -1)
+    err("open");
+  memset(buf, 'A', BSIZE);
+  // write 1.5 page
+  for (i = 0; i < n + n/2; i++) {
+    if (write(fd, buf, BSIZE) != BSIZE)
+      err("write 0 makefile");
+  }
+  if (close(fd) == -1)
+    err("close");
+}
+
+void
+mmap_test(void)
+{
+  int fd;
+  // int testfd;
+  int i;
+  const char * const f = "mmap.dur";
+  printf("mmap_test starting\n");
+  testname = "mmap_test";
+
+  //
+  // create a file with known content, map it into memory, check that
+  // the mapped memory has the same bytes as originally written to the
+  // file.
+  //
+  makefile(f);
+  // struct kstat stat;
+  // if ((testfd = open(f, O_RDWR)) == -1)
+  //   err("open");
+  // fstat(testfd, &stat);
+
+  // printf("kstat is %d\n", stat.st_size);
+  // for (i = 0; i < stat.st_size; i += (PGSIZE / 2)){
+  //   // char b;
+  //   char *buf = (char *)malloc(PGSIZE / 2 + 1);
+  //   read(testfd, buf, (PGSIZE) / 2);
+  //   buf[PGSIZE / 2 + 1] = '\0';
+  //   printf("buf content is :\n%s\n", buf);
+  //   printf("i is %d\n", i);
+  // }
+  // close(testfd);
+  if ((fd = open(f, O_RDONLY)) == -1)
+    err("open");
+
+  printf("test mmap f\n");
+  //
+  // this call to mmap() asks the kernel to map the content
+  // of open file fd into the address space. the first
+  // 0 argument indicates that the kernel should choose the
+  // virtual address. the second argument indicates how many
+  // bytes to map. the third argument indicates that the
+  // mapped memory should be read-only. the fourth argument
+  // indicates that, if the process modifies the mapped memory,
+  // that the modifications should not be written back to
+  // the file nor shared with other processes mapping the
+  // same file (of course in this case updates are prohibited
+  // due to PROT_READ). the fifth argument is the file descriptor
+  // of the file to be mapped. the last argument is the starting
+  // offset in the file.
+  //
+  char *p = mmap(0, PGSIZE*2, PROT_READ, MAP_PRIVATE, fd, 0);
+  if (p == MAP_FAILED)
+    err("mmap (1)");
+  _v1(p);
+  if (munmap(p, PGSIZE*2) == -1)
+    err("munmap (1)");
+
+  printf("test mmap f: OK\n");
+    
+  printf("test mmap private\n");
+  // should be able to map file opened read-only with private writable
+  // mapping
+  p = mmap(0, PGSIZE*2, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+  if (p == MAP_FAILED)
+    err("mmap (2)");
+  if (close(fd) == -1)
+    err("close");
+  _v1(p);
+  for (i = 0; i < PGSIZE*2; i++)
+    p[i] = 'Z';
+  if (munmap(p, PGSIZE*2) == -1)
+    err("munmap (2)");
+
+  printf("test mmap private: OK\n");
+    
+  printf("test mmap read-only\n");
+    
+  // check that mmap doesn't allow read/write mapping of a
+  // file opened read-only.
+  if ((fd = open(f, O_RDONLY)) == -1)
+    err("open");
+  p = mmap(0, PGSIZE*3, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+  if (p != MAP_FAILED)
+    err("mmap call should have failed");
+  if (close(fd) == -1)
+    err("close");
+
+  printf("test mmap read-only: OK\n");
+    
+  printf("test mmap read/write\n");
+  
+  // check that mmap does allow read/write mapping of a
+  // file opened read/write.
+  if ((fd = open(f, O_RDWR)) == -1)
+    err("open");
+  p = mmap(0, PGSIZE*3, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+  if (p == MAP_FAILED)
+    err("mmap (3)");
+  if (close(fd) == -1)
+    err("close");
+
+  // printf("hit 1\n");
+  // check that the mapping still works after close(fd).
+  _v1(p);
+
+  // write the mapped memory.
+  for (i = 0; i < PGSIZE*2; i++)
+    p[i] = 'Z';
+  // printf("hit 2\n");
+
+  // printf("===============================================\n");
+  // if ((testfd = open(f, O_RDWR)) == -1)
+  //   err("open");
+  // fstat(testfd, &stat);
+
+  // printf("kstat is %d\n", stat.st_size);
+  // for (i = 0; i < stat.st_size; i += (PGSIZE / 2)){
+  //   // char b;
+  //   char *buf = (char *)malloc(PGSIZE / 2 + 1);
+  //   read(testfd, buf, (PGSIZE) / 2);
+  //   buf[PGSIZE / 2 + 1] = '\0';
+  //   printf("buf content is :\n%s\n", buf);
+  //   printf("i is %d\n", i);
+  // }
+  // close(testfd);
+  // unmap just the first two of three pages of mapped memory.
+  if (munmap(p, PGSIZE*2) == -1)
+    err("munmap (3)");
+  
+  printf("test mmap read/write: OK\n");
+  
+  printf("test mmap dirty\n");
+  
+  // check that the writes to the mapped memory were
+  // written to the file.
+  if ((fd = open(f, O_RDWR)) == -1)
+    err("open");
+  // printf("=======================================\n");
+  // if ((testfd = open(f, O_RDWR)) == -1)
+  //   err("open");
+  // fstat(testfd, &stat);
+
+  // printf("kstat is %d\n", stat.st_size);
+  // for (i = 0; i < PGSIZE + (PGSIZE/2); i += (PGSIZE / 2)){
+  //   // char b;
+  //   char *buf = (char *)malloc(PGSIZE / 2 + 1);
+  //   read(testfd, buf, (PGSIZE) / 2);
+  //   buf[PGSIZE / 2 + 1] = '\0';
+  //   printf("buf content is :\n%s\n", buf);
+  //   printf("i is %d\n", i);
+  // }
+  // close(testfd);
+
+  for (i = 0; i < PGSIZE + (PGSIZE/2); i++){
+    char b;
+    if (read(fd, &b, 1) != 1)
+      err("read (1)");
+    if (b != 'Z') {
+      printf("%d\n", i);
+      err("file does not contain modifications");
+    }
+  }
+  if (close(fd) == -1)
+    err("close");
+
+  printf("test mmap dirty: OK\n");
+
+  printf("test not-mapped unmap\n");
+  
+  // unmap the rest of the mapped memory.
+  if (munmap(p+PGSIZE*2, PGSIZE) == -1)
+    err("munmap (4)");
+
+  printf("test not-mapped unmap: OK\n");
+    
+  printf("test mmap two files\n");
+  print_pgtable();
+  
+  //
+  // mmap two files at the same time.
+  //
+  int fd1;
+  if((fd1 = open("mmap1", O_RDWR|O_CREATE)) < 0)
+    err("open mmap1");
+  if(write(fd1, "12345", 5) != 5)
+    err("write mmap1");
+  print_rawfile(fd1, 0);
+  printf("gap\n");
+  char *p1 = mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd1, 0);
+  if(p1 == MAP_FAILED)
+    err("mmap mmap1");
+  close(fd1);
+  unlink("mmap1");
+
+  // print_pgtable();
+  int fd2;
+  if((fd2 = open("mmap2", O_RDWR|O_CREATE)) < 0)
+    err("open mmap2");
+  if(write(fd2, "67890", 5) != 5)
+    err("write mmap2");
+  char *p2 = mmap(0, PGSIZE, PROT_READ, MAP_PRIVATE, fd2, 0);
+  if(p2 == MAP_FAILED)
+    err("mmap mmap2");
+  close(fd2);
+  unlink("mmap2");
+
+  if(memcmp(p1, "12345", 5) != 0)
+    err("mmap1 mismatch");
+  if(memcmp(p2, "67890", 5) != 0)
+    err("mmap2 mismatch");
+
+  munmap(p1, PGSIZE);
+  if(memcmp(p2, "67890", 5) != 0)
+    err("mmap2 mismatch (2)");
+  munmap(p2, PGSIZE);
+  
+  printf("test mmap two files: OK\n");
+  
+  printf("mmap_test: ALL OK\n");
+}
+
+//
+// mmap a file, then fork.
+// check that the child sees the mapped file.
+//
+void
+fork_test(void)
+{
+  int fd;
+  int pid;
+  const char * const f = "mmap.dur";
+  
+  printf("fork_test starting\n");
+  testname = "fork_test";
+  
+  // mmap the file twice.
+  makefile(f);
+  if ((fd = open(f, O_RDONLY)) == -1)
+    err("open");
+  unlink(f);
+  // print_vma();
+  char *p1 = mmap(0, PGSIZE*2, PROT_READ, MAP_SHARED, fd, 0);
+  if (p1 == MAP_FAILED)
+    err("mmap (4)");
+  char *p2 = mmap(0, PGSIZE*2, PROT_READ, MAP_SHARED, fd, 0);
+  if (p2 == MAP_FAILED)
+    err("mmap (5)");
+  // print_vma();
+
+  // read just 2nd page.
+  if(*(p1+PGSIZE) != 'A')
+    err("fork mismatch (1)");
+
+  if((pid = fork()) < 0)
+    err("fork");
+  if (pid == 0) {
+    _v1(p1);
+    munmap(p1, PGSIZE); // just the first page
+    // print_vma();
+    exit(0); // tell the parent that the mapping looks OK.
+  }
+
+  int status = -1;
+  wait(&status);
+
+  if(status != 0){
+    printf("fork_test failed\n");
+    exit(1);
+  }
+
+  // check that the parent's mappings are still there.
+  _v1(p1);
+  _v1(p2);
+
+  printf("fork_test OK\n");
+}
+
diff --git a/xv6_user/src/rawcwd.c b/xv6_user/src/rawcwd.c
new file mode 100644
index 0000000000000000000000000000000000000000..fe819c6889e4cf049a75d6775c2180e7c1027dc3
--- /dev/null
+++ b/xv6_user/src/rawcwd.c
@@ -0,0 +1,17 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+/* print the root content */
+int main() {
+    int fd;
+    fd = open(".", O_DIRECTORY);
+    if (fd < 0) {
+        return 0;
+    } else {
+        print_rawfile(fd, 1);
+    }
+    return 0;
+}
\ No newline at end of file
diff --git a/xv6_user/src/rm.c b/xv6_user/src/rm.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e2a8e3aad05d51733ee9473793425a2566fae4b
--- /dev/null
+++ b/xv6_user/src/rm.c
@@ -0,0 +1,26 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+
+
+int main(int argc, char *argv[]) {
+    int i;
+
+    if (argc < 2) {
+        fprintf(2, "Usage: rm files...\n");
+        exit(1);
+    }
+
+    for (i = 1; i < argc; i++) {
+        if (unlink(argv[i]) < 0) {
+            fprintf(2, "rm: %s failed to delete\n", argv[i]);
+            break;
+        }
+    }
+
+    exit(0);
+    return 0;
+}
diff --git a/xv6_user/src/sh.c b/xv6_user/src/sh.c
new file mode 100644
index 0000000000000000000000000000000000000000..4e118afab2653a38a7cfd5ad4a03f3d8c51cf57a
--- /dev/null
+++ b/xv6_user/src/sh.c
@@ -0,0 +1,517 @@
+// Shell.
+
+#define USER
+#include "stddef.h"
+#include "stdio.h"
+#include "unistd.h"
+#include "string.h"
+#include "stdlib.h"
+// #include "fs/fcntl.h"
+
+// Parsed command representation
+#define EXEC  1
+#define REDIR 2
+#define PIPE  3
+#define LIST  4
+#define BACK  5
+
+#define MAXARGS 10
+
+struct cmd {
+  int type;
+};
+
+struct execcmd {
+  int type;
+  char *argv[MAXARGS];
+  char *eargv[MAXARGS];
+};
+
+struct redircmd {
+  int type;
+  struct cmd *cmd;
+  char *file;
+  char *efile;
+  int mode;
+  int fd;
+};
+
+struct pipecmd {
+  int type;
+  struct cmd *left;
+  struct cmd *right;
+};
+
+struct listcmd {
+  int type;
+  struct cmd *left;
+  struct cmd *right;
+};
+
+struct backcmd {
+  int type;
+  struct cmd *cmd;
+};
+
+int fork1(void);  // Fork but panics on failure.
+void panic(char*);
+struct cmd *parsecmd(char*);
+void runcmd(struct cmd*);
+
+// Execute cmd.  Never returns.
+void
+runcmd(struct cmd *cmd)
+{
+  int p[2];
+  struct backcmd *bcmd;
+  struct execcmd *ecmd;
+  struct listcmd *lcmd;
+  struct pipecmd *pcmd;
+  struct redircmd *rcmd;
+
+  if(cmd == 0)
+    exit(1);
+
+  switch(cmd->type){
+  default:
+    panic("runcmd");
+
+  case EXEC:
+    ecmd = (struct execcmd*)cmd;
+    if(ecmd->argv[0] == 0)
+      exit(1);
+
+    char cmdpath[20] = "/";
+    strcat(cmdpath,ecmd->argv[0]);
+    execve(cmdpath, ecmd->argv, NULL);
+    fprintf(2, "exec %s failed\n", ecmd->argv[0]);
+    break;
+
+  case REDIR:
+    rcmd = (struct redircmd*)cmd;
+    close(rcmd->fd);
+    if(openat(AT_FDCWD, rcmd->file, rcmd->mode) < 0){
+      fprintf(2, "openat %s failed\n", rcmd->file);
+      exit(1);
+    }
+    runcmd(rcmd->cmd);
+    break;
+
+  case LIST:
+    lcmd = (struct listcmd*)cmd;
+    if(fork1() == 0)
+      runcmd(lcmd->left);
+    wait(0);
+    runcmd(lcmd->right);
+    break;
+
+  case PIPE:
+    pcmd = (struct pipecmd*)cmd;
+    if(pipe(p) < 0)
+      panic("pipe");
+    if(fork1() == 0){
+      close(1);
+      dup(p[1]);
+      close(p[0]);
+      close(p[1]);
+      runcmd(pcmd->left);
+    }
+    if(fork1() == 0){
+      close(0);
+      dup(p[0]);
+      close(p[0]);
+      close(p[1]);
+      runcmd(pcmd->right);
+    }
+    close(p[0]);
+    close(p[1]);
+    wait(0);
+    wait(0);
+    break;
+
+  case BACK:
+    bcmd = (struct backcmd*)cmd;
+    if(fork1() == 0)
+      runcmd(bcmd->cmd);
+    break;
+  }
+  exit(0);
+}
+
+char cwd[128];
+int
+getcmd(char *buf, int nbuf)
+{
+  printf("%s",cwd);
+  write(2, "> ", 2);
+  memset(buf, 0, nbuf);
+  gets(buf, nbuf);
+  if(buf[0] == 0) // EOF
+    return -1;
+  return 0;
+}
+
+int
+main(void)
+{
+
+  printf("We are in sh\n\n");
+  // printf("██╗      ██████╗ ███████╗████████╗██╗    ██╗ █████╗ ██╗  ██╗███████╗██╗   ██╗██████╗ \n");
+  // printf("██║     ██╔═══██╗██╔════╝╚══██╔══╝██║    ██║██╔══██╗██║ ██╔╝██╔════╝██║   ██║██╔══██╗\n");
+  // printf("██║     ██║   ██║███████╗   ██║   ██║ █╗ ██║███████║█████╔╝ █████╗  ██║   ██║██████╔╝\n");
+  // printf("██║     ██║   ██║╚════██║   ██║   ██║███╗██║██╔══██║██╔═██╗ ██╔══╝  ██║   ██║██╔═══╝ \n");
+  // printf("███████╗╚██████╔╝███████║   ██║   ╚███╔███╔╝██║  ██║██║  ██╗███████╗╚██████╔╝██║     \n");
+  // printf("╚══════╝ ╚═════╝ ╚══════╝   ╚═╝    ╚══╝╚══╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚══════╝ ╚═════╝ ╚═╝     \n");
+  // printf("\n");
+  static char buf[100];
+  int fd;
+  getcwd(cwd,128);
+  // Ensure that three file descriptors are open.
+  while((fd = openat(AT_FDCWD,"console.dev", O_RDWR)) >= 0){
+    if(fd >= 3){
+      close(fd);
+      break;
+    }
+  }
+
+  // Read and run input commands.
+  while(getcmd(buf, sizeof(buf)) >= 0){
+    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
+      // Chdir must be called by the parent, not the child.
+      buf[strlen(buf)-1] = 0;  // chop \n
+      if(chdir(buf+3) < 0){
+        fprintf(2, "cannot cd %s\n", buf+3);
+      } 
+      else {
+        getcwd(cwd,128);
+      }
+      continue;
+    }
+    if(fork1() == 0)
+      runcmd(parsecmd(buf));
+    wait(0);
+  }
+  exit(0);
+  return 0;
+}
+
+void
+panic(char *s)
+{
+  fprintf(2, "%s\n", s);
+  exit(1);
+}
+
+int
+fork1(void)
+{
+  int pid;
+
+  pid = fork();
+  if(pid == -1)
+    panic("fork");
+  return pid;
+}
+
+//PAGEBREAK!
+// Constructors
+
+struct cmd*
+execcmd(void)
+{
+  struct execcmd *cmd;
+
+  cmd = malloc(sizeof(*cmd));
+  memset(cmd, 0, sizeof(*cmd));
+  cmd->type = EXEC;
+  return (struct cmd*)cmd;
+}
+
+struct cmd*
+redircmd(struct cmd *subcmd, char *file, char *efile, int mode, int fd)
+{
+  struct redircmd *cmd;
+
+  cmd = malloc(sizeof(*cmd));
+  memset(cmd, 0, sizeof(*cmd));
+  cmd->type = REDIR;
+  cmd->cmd = subcmd;
+  cmd->file = file;
+  cmd->efile = efile;
+  cmd->mode = mode;
+  cmd->fd = fd;
+  return (struct cmd*)cmd;
+}
+
+struct cmd*
+pipecmd(struct cmd *left, struct cmd *right)
+{
+  struct pipecmd *cmd;
+
+  cmd = malloc(sizeof(*cmd));
+  memset(cmd, 0, sizeof(*cmd));
+  cmd->type = PIPE;
+  cmd->left = left;
+  cmd->right = right;
+  return (struct cmd*)cmd;
+}
+
+struct cmd*
+listcmd(struct cmd *left, struct cmd *right)
+{
+  struct listcmd *cmd;
+
+  cmd = malloc(sizeof(*cmd));
+  memset(cmd, 0, sizeof(*cmd));
+  cmd->type = LIST;
+  cmd->left = left;
+  cmd->right = right;
+  return (struct cmd*)cmd;
+}
+
+struct cmd*
+backcmd(struct cmd *subcmd)
+{
+  struct backcmd *cmd;
+
+  cmd = malloc(sizeof(*cmd));
+  memset(cmd, 0, sizeof(*cmd));
+  cmd->type = BACK;
+  cmd->cmd = subcmd;
+  return (struct cmd*)cmd;
+}
+//PAGEBREAK!
+// Parsing
+
+char whitespace[] = " \t\r\n\v";
+char symbols[] = "<|>&;()";
+
+int
+gettoken(char **ps, char *es, char **q, char **eq)
+{
+  char *s;
+  int ret;
+
+  s = *ps;
+  while(s < es && strchr(whitespace, *s))
+    s++;
+  if(q)
+    *q = s;
+  ret = *s;
+  switch(*s){
+  case 0:
+    break;
+  case '|':
+  case '(':
+  case ')':
+  case ';':
+  case '&':
+  case '<':
+    s++;
+    break;
+  case '>':
+    s++;
+    if(*s == '>'){
+      ret = '+';
+      s++;
+    }
+    break;
+  default:
+    ret = 'a';
+    while(s < es && !strchr(whitespace, *s) && !strchr(symbols, *s))
+      s++;
+    break;
+  }
+  if(eq)
+    *eq = s;
+
+  while(s < es && strchr(whitespace, *s))
+    s++;
+  *ps = s;
+  return ret;
+}
+
+int
+peek(char **ps, char *es, char *toks)
+{
+  char *s;
+
+  s = *ps;
+  while(s < es && strchr(whitespace, *s))
+    s++;
+  *ps = s;
+  return *s && strchr(toks, *s);
+}
+
+struct cmd *parseline(char**, char*);
+struct cmd *parsepipe(char**, char*);
+struct cmd *parseexec(char**, char*);
+struct cmd *nulterminate(struct cmd*);
+
+struct cmd*
+parsecmd(char *s)
+{
+  char *es;
+  struct cmd *cmd;
+
+  es = s + strlen(s);
+  cmd = parseline(&s, es);
+  peek(&s, es, "");
+  if(s != es){
+    fprintf(2, "leftovers: %s\n", s);
+    panic("syntax");
+  }
+  nulterminate(cmd);
+  return cmd;
+}
+
+struct cmd*
+parseline(char **ps, char *es)
+{
+  struct cmd *cmd;
+
+  cmd = parsepipe(ps, es);
+  while(peek(ps, es, "&")){
+    gettoken(ps, es, 0, 0);
+    cmd = backcmd(cmd);
+  }
+  if(peek(ps, es, ";")){
+    gettoken(ps, es, 0, 0);
+    cmd = listcmd(cmd, parseline(ps, es));
+  }
+  return cmd;
+}
+
+struct cmd*
+parsepipe(char **ps, char *es)
+{
+  struct cmd *cmd;
+
+  cmd = parseexec(ps, es);
+  if(peek(ps, es, "|")){
+    gettoken(ps, es, 0, 0);
+    cmd = pipecmd(cmd, parsepipe(ps, es));
+  }
+  return cmd;
+}
+
+struct cmd*
+parseredirs(struct cmd *cmd, char **ps, char *es)
+{
+  int tok;
+  char *q, *eq;
+
+  while(peek(ps, es, "<>")){
+    tok = gettoken(ps, es, 0, 0);
+    if(gettoken(ps, es, &q, &eq) != 'a')
+      panic("missing file for redirection");
+    switch(tok){
+    case '<':
+      cmd = redircmd(cmd, q, eq, O_RDONLY, 0);
+      break;
+    case '>':
+      cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE|O_TRUNC, 1);
+      break;
+    case '+':  // >>
+      cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE, 1);
+      break;
+    }
+  }
+  return cmd;
+}
+
+struct cmd*
+parseblock(char **ps, char *es)
+{
+  struct cmd *cmd;
+
+  if(!peek(ps, es, "("))
+    panic("parseblock");
+  gettoken(ps, es, 0, 0);
+  cmd = parseline(ps, es);
+  if(!peek(ps, es, ")"))
+    panic("syntax - missing )");
+  gettoken(ps, es, 0, 0);
+  cmd = parseredirs(cmd, ps, es);
+  return cmd;
+}
+
+struct cmd*
+parseexec(char **ps, char *es)
+{
+  char *q, *eq;
+  int tok, argc;
+  struct execcmd *cmd;
+  struct cmd *ret;
+
+  if(peek(ps, es, "("))
+    return parseblock(ps, es);
+
+  ret = execcmd();
+  cmd = (struct execcmd*)ret;
+
+  argc = 0;
+  ret = parseredirs(ret, ps, es);
+  while(!peek(ps, es, "|)&;")){
+    if((tok=gettoken(ps, es, &q, &eq)) == 0)
+      break;
+    if(tok != 'a')
+      panic("syntax");
+    cmd->argv[argc] = q;
+    cmd->eargv[argc] = eq;
+    argc++;
+    if(argc >= MAXARGS)
+      panic("too many args");
+    ret = parseredirs(ret, ps, es);
+  }
+  cmd->argv[argc] = 0;
+  cmd->eargv[argc] = 0;
+  return ret;
+}
+
+// NUL-terminate all the counted strings.
+struct cmd*
+nulterminate(struct cmd *cmd)
+{
+  int i;
+  struct backcmd *bcmd;
+  struct execcmd *ecmd;
+  struct listcmd *lcmd;
+  struct pipecmd *pcmd;
+  struct redircmd *rcmd;
+
+  if(cmd == 0)
+    return 0;
+
+  switch(cmd->type){
+  case EXEC:
+    ecmd = (struct execcmd*)cmd;
+    for(i=0; ecmd->argv[i]; i++)
+      *ecmd->eargv[i] = 0;
+    break;
+
+  case REDIR:
+    rcmd = (struct redircmd*)cmd;
+    nulterminate(rcmd->cmd);
+    *rcmd->efile = 0;
+    break;
+
+  case PIPE:
+    pcmd = (struct pipecmd*)cmd;
+    nulterminate(pcmd->left);
+    nulterminate(pcmd->right);
+    break;
+
+  case LIST:
+    lcmd = (struct listcmd*)cmd;
+    nulterminate(lcmd->left);
+    nulterminate(lcmd->right);
+    break;
+
+  case BACK:
+    bcmd = (struct backcmd*)cmd;
+    nulterminate(bcmd->cmd);
+    break;
+  }
+  return cmd;
+}
diff --git a/xv6_user/src/shutdown.c b/xv6_user/src/shutdown.c
new file mode 100644
index 0000000000000000000000000000000000000000..a68725ad147dc67e12fcbf6c1ea5d94bcef10c33
--- /dev/null
+++ b/xv6_user/src/shutdown.c
@@ -0,0 +1,10 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+
+int main() {
+    shutdown();
+    return 0;
+}
\ No newline at end of file
diff --git a/xv6_user/src/user.ld b/xv6_user/src/user.ld
new file mode 100644
index 0000000000000000000000000000000000000000..0ca922b66220c86c96336abc2b091c42c3197a30
--- /dev/null
+++ b/xv6_user/src/user.ld
@@ -0,0 +1,36 @@
+OUTPUT_ARCH( "riscv" )
+ENTRY( _main )
+
+
+SECTIONS
+{
+ . = 0x0;
+ 
+  .text : {
+    *(.text .text.*)
+  }
+
+  .rodata : {
+    . = ALIGN(16);
+    *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */
+    . = ALIGN(16);
+    *(.rodata .rodata.*)
+    . = ALIGN(0x1000);
+  }
+
+  .data : {
+    . = ALIGN(16);
+    *(.sdata .sdata.*) /* do not need to distinguish this from .data */
+    . = ALIGN(16);
+    *(.data .data.*)
+  }
+
+  .bss : {
+    . = ALIGN(16);
+    *(.sbss .sbss.*) /* do not need to distinguish this from .bss */
+    . = ALIGN(16);
+    *(.bss .bss.*)
+  }
+
+  PROVIDE(end = .);
+}
diff --git a/xv6_user/src/user_test.c b/xv6_user/src/user_test.c
new file mode 100644
index 0000000000000000000000000000000000000000..8db19f5f7fe38fd9dcbc49ad0cfa1c7dd31c0840
--- /dev/null
+++ b/xv6_user/src/user_test.c
@@ -0,0 +1,2802 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+
+// Test that fork fails gracefully.
+void forktest(void) {
+    int n, pid;
+    printf("==========fork test==========\n");
+    int N = 100;
+    for (n = 0; n < 100; n++) {
+        pid = fork();
+        if (pid < 0)
+            break;
+        if (pid == 0)
+            exit(0);
+    }
+
+    if (n == N) {
+        printf("fork claimed to work N times!\n");
+        exit(1);
+    }
+
+    for (; n > 0; n--) {
+        if (wait(0) < 0) {
+            printf("wait stopped early\n");
+            exit(1);
+        }
+    }
+
+    if (wait(0) != -1) {
+        printf("wait got too many\n");
+        exit(1);
+    }
+
+    printf("fork test OK\n");
+}
+
+// concurrent forks to try to expose locking bugs.
+void forkfork() {
+    int N = 2;
+    printf("==========forkfork test==========\n");
+    for (int i = 0; i < N; i++) {
+        int pid = fork();
+        if (pid < 0) {
+            printf("fork failed");
+            exit(1);
+        }
+        if (pid == 0) {
+            for (int j = 0; j < 100; j++) {
+                int pid1 = fork();
+                if (pid1 < 0) {
+                    exit(1);
+                }
+                if (pid1 == 0) {
+                    exit(0);
+                }
+                wait(0);
+            }
+            exit(0);
+        }
+    }
+
+    int xstatus;
+    for (int i = 0; i < N; i++) {
+        wait(&xstatus);
+        if (xstatus != 0) {
+            printf("fork in child failed");
+            exit(1);
+        }
+    }
+    printf("forkfork test OK\n");
+}
+
+// simple file system tests (open syscall)
+void opentest() {
+    int fd;
+    printf("==========open test==========\n");
+
+    fd = open("echo", 0);
+    if (fd < 0) {
+        printf("open echo failed!\n");
+        exit(1);
+    }
+    close(fd);
+    fd = open("doesnotexist", 0);
+    if (fd >= 0) {
+        printf("open doesnotexist succeeded!\n");
+        exit(1);
+    }
+    printf("open test OK\n");
+}
+
+#define WEXITSTATUS(s) (((s) & 0xff00) >> 8)
+// try to find any races between exit and wait
+void exitwait() {
+    int i, pid;
+    printf("==========exitwait test==========\n");
+
+    for (i = 0; i < 1000; i++) {
+        pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+        if (pid) {
+            int exit_state;
+            if (wait(&exit_state) != pid) {
+                printf("wait wrong pid\n");
+                exit(1);
+            }
+            if ((i<<8) != exit_state) {
+                printf("%d %d %d\n", i, exit_state, WEXITSTATUS(pid));
+                printf("wait wrong exit status\n");
+                exit(1);
+            }
+        } else {
+            exit(i);
+        }
+    }
+    printf("exitwait test OK\n");
+}
+
+// what if two children exit() at the same time?
+void twochildren() {
+    printf("==========twochildren test==========\n");
+    for (int i = 0; i < 1000; i++) {
+        int pid1 = fork();
+        if (pid1 < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+        if (pid1 == 0) {
+            exit(0);
+        } else {
+            int pid2 = fork();
+            if (pid2 < 0) {
+                printf("fork failed\n");
+                exit(1);
+            }
+            if (pid2 == 0) {
+                exit(0);
+            } else {
+                wait(0);
+                wait(0);
+            }
+        }
+    }
+    printf("twochildren test OK\n");
+}
+
+// try to find races in the reparenting
+// code that handles a parent exiting
+// when it still has live children.
+void reparent() {
+    printf("==========reparent test==========\n");
+    int master_pid = getpid();
+    for (int i = 0; i < 200; i++) {
+        int pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+        if (pid) {
+            if (wait(0) != pid) {
+                printf("wait wrong pid\n");
+                exit(1);
+            }
+        } else {
+            int pid2 = fork();
+            if (pid2 < 0) {
+                kill(master_pid);
+                exit(1);
+            }
+            exit(0);
+        }
+    }
+    printf("reparent test OK\n");
+}
+
+// regression test. does reparent() violate the parent-then-child
+// locking order when giving away a child to init, so that exit()
+// deadlocks against init's wait()? also used to trigger a "panic:
+// release" due to exit() releasing a different p->parent->lock than
+// it acquired.
+void reparent2() {
+    printf("==========reparent2 test==========\n");
+    for (int i = 0; i < 400; i++) {
+        int pid1 = fork();
+        if (pid1 < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+        if (pid1 == 0) {
+            fork();
+            fork();
+            exit(0);
+        }
+        wait(0);
+    }
+    printf("reparent2 test OK\n");
+}
+
+#define MAXOPBLOCKS 10
+#define BSIZE 512
+#define BUFSZ ((MAXOPBLOCKS + 2) * BSIZE)
+char buf[BUFSZ];
+
+// write syscall
+void writetest() {
+    int fd;
+    int N = 100;
+    int SZ = 10;
+    printf("==========write test==========\n");
+    fd = open("small", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("error: create small failed!\n");
+        exit(1);
+    }
+
+    for (int i = 0; i < N; i++) {
+        if (write(fd, "aaaaaaaaaa", SZ) != SZ) {
+            printf("error: write aa %d new file failed\n", i);
+            exit(1);
+        }
+        if (write(fd, "bbbbbbbbbb", SZ) != SZ) {
+            printf("error: write bb %d new file failed\n", i);
+            exit(1);
+        }
+    }
+    close(fd);
+    fd = open("small", O_RDONLY);
+    if (fd < 0) {
+        printf("error: open small failed!\n");
+        exit(1);
+    }
+    int ret = read(fd, buf, N * SZ * 2);
+    if (ret != N * SZ * 2) {
+        printf("read failed\n");
+        exit(1);
+    }
+    close(fd);
+    printf("write test OK\n");
+}
+
+void writebig() {
+    int i, fd;
+    int n;
+    int MAXFILE = 2048;
+    printf("==========writebig test==========\n");
+    fd = open("big", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("error: creat big failed!\n");
+        exit(1);
+    }
+
+    for (i = 0; i < MAXFILE; i++) {
+        ((int *)buf)[0] = i;
+        if (write(fd, buf, BSIZE) != BSIZE) {
+            printf("error: write big file failed\n", i);
+            exit(1);
+        }
+    }
+    close(fd);
+
+    fd = open("big", O_RDONLY);
+    if (fd < 0) {
+        printf("error: open big failed!\n");
+        exit(1);
+    }
+    n = 0;
+    for (;;) {
+        i = read(fd, buf, BSIZE);
+        if (i == 0) {
+            if (n == MAXFILE - 1) {
+                printf("read only %d blocks from big", n);
+                exit(1);
+            }
+            break;
+        } else if (i != BSIZE) {
+            printf("read failed %d\n", i);
+            exit(1);
+        }
+        if (((int *)buf)[0] != n) {
+            printf("read content of block %d is %d\n", n, ((int *)buf)[0]);
+            exit(1);
+        }
+        n++;
+    }
+    close(fd);
+    if (unlink("big") < 0) {
+        printf("unlink big failed\n");
+        exit(1);
+    }
+    printf("writebig test OK\n");
+}
+
+void openiputtest() {
+    int pid, xstatus;
+    printf("==========openiputtest test==========\n");
+
+    if (mkdir("oidir", 0666) < 0) {
+        printf("mkdir oidir failed\n");
+        exit(1);
+    }
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        int fd = open("oidir", O_RDWR);
+        if (fd >= 0) {
+            printf("open directory for write succeeded\n");
+            exit(1);
+        }
+        exit(0);
+    }
+
+    sleep(1);
+    if (unlink("oidir") != 0) {
+        printf("unlink failed\n");
+        exit(1);
+    }
+
+    wait(&xstatus);
+    printf("openiputtest test OK\n");
+}
+
+void truncate1() {
+    char buf[32];
+    printf("==========truncate1 test==========\n");
+
+    unlink("truncfile");
+    int fd1 = open("truncfile", O_CREATE | O_WRONLY | O_TRUNC);
+
+    write(fd1, "abcd", 4);
+    close(fd1);
+
+    int fd2 = open("truncfile", O_RDONLY);
+
+    memset(buf, 0, sizeof(buf));
+    int n = read(fd2, buf, sizeof(buf));
+    printf("%s\n", buf);
+    if (n != 4) {
+        printf("read %d bytes, wanted 4\n", n);
+        exit(1);
+    }
+
+    fd1 = open("truncfile", O_WRONLY | O_TRUNC);
+    int fd3 = open("truncfile", O_RDONLY);
+
+    memset(buf, 0, sizeof(buf));
+    n = read(fd3, buf, sizeof(buf));
+    printf("%s\n", buf);
+
+    if (n != 0) {
+        printf("aaa fd3=%d\n", fd3);
+        printf("read %d bytes, wanted 0\n", n);
+        exit(1);
+    }
+
+    // memset(buf, 0, sizeof(buf));
+    n = read(fd2, buf, sizeof(buf));
+    // printf("%s\n",buf);
+    if (n != 0) {
+        printf("bbb fd2=%d\n", fd2);
+        printf("read %d bytes, wanted 0\n", n);
+        exit(1);
+    }
+
+    write(fd1, "abcdef", 6);
+
+    memset(buf, 0, sizeof(buf));
+    n = read(fd3, buf, sizeof(buf));
+    printf("%s\n", buf);
+
+    if (n != 6) {
+        printf("read %d bytes, wanted 6\n", n);
+        exit(1);
+    }
+
+    memset(buf, 0, sizeof(buf));
+    n = read(fd2, buf, sizeof(buf));
+    printf("%s\n", buf);
+
+    if (n != 2) {
+        printf("read %d bytes, wanted 2\n", n);
+        exit(1);
+    }
+
+    unlink("truncfile");
+
+    close(fd1);
+    close(fd2);
+    close(fd3);
+
+    printf("truncate1 test OK\n");
+}
+
+void forkforkfork() {
+    printf("==========forkforkfork test==========\n");
+    unlink("stopforking");
+    int pid = fork();
+    if (pid < 0) {
+        printf("fork failed");
+        exit(1);
+    }
+    if (pid == 0) {
+        while (1) {
+            int fd = open("stopforking", 0);
+            if (fd >= 0) {
+                exit(0);
+            }
+            if (fork() < 0) {
+                close(open("stopforking", O_CREATE | O_RDWR));
+            }
+        }
+        exit(0);
+    }
+
+    sleep(2); // two seconds
+    close(open("stopforking", O_CREATE | O_RDWR));
+    wait(0);
+    sleep(1); // one second
+
+    printf("forkforkfork test OK\n");
+}
+
+void preempt() {
+    printf("==========preempt test==========\n");
+    int pid1, pid2, pid3;
+    int pfds[2];
+
+    pid1 = fork();
+    if (pid1 < 0) {
+        printf("fork failed");
+        exit(1);
+    }
+    if (pid1 == 0)
+        for (;;)
+            ;
+
+    pid2 = fork();
+    if (pid2 < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid2 == 0)
+        for (;;)
+            ;
+
+    pipe(pfds);
+    pid3 = fork();
+    if (pid3 < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid3 == 0) {
+        close(pfds[0]);
+        if (write(pfds[1], "x", 1) != 1)
+            printf("preempt write error");
+        close(pfds[1]);
+        for (;;)
+            ;
+    }
+
+    close(pfds[1]);
+    if (read(pfds[0], buf, sizeof(buf)) != 1) {
+        printf("preempt read error");
+        return;
+    }
+    close(pfds[0]);
+    printf("kill... ");
+    kill(pid1);
+    kill(pid2);
+    kill(pid3);
+    printf("wait... ");
+    wait(0);
+    wait(0);
+    wait(0);
+
+    printf("preempt test OK\n");
+}
+
+// test if child is killed (status = -1)
+void killstatus() {
+    printf("==========killstatus test==========\n");
+    int xst;
+
+    for (int i = 0; i < 4; i++) {
+        int pid1 = fork();
+        if (pid1 < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+        if (pid1 == 0) {
+            while (1) {
+                getpid();
+            }
+            exit(0);
+        }
+        sleep(1);
+        kill(pid1);
+        wait(&xst);
+        if (xst != (-1 << 8)) {
+            printf("%d %d\n", xst, i);
+            printf("status should be -1<<8\n");
+            exit(1);
+        }
+        printf("%d\n", i);
+    }
+    printf("killstatus test OK\n");
+}
+
+// what if you pass ridiculous pointers to system calls
+// that read user memory with copyin?
+void copyin() {
+    printf("==========copyin test==========\n");
+    // print_pgtable();
+    uint64 addrs[] = {0x80000000LL, 0xffffffffffffffff};
+
+    for (int ai = 0; ai < 2; ai++) {
+        uint64 addr = addrs[ai];
+
+        int fd = open("copyin1", O_CREATE | O_WRONLY);
+        if (fd < 0) {
+            printf("open(copyin1) failed\n");
+            exit(1);
+        }
+        int n = write(fd, (void *)addr, 8192);
+        if (n >= 0) {
+            printf("write(fd, %p, 8192) returned %d, not -1\n", addr, n);
+            exit(1);
+        }
+        close(fd);
+        unlink("copyin1");
+
+        n = write(1, (char *)addr, 8192);
+        if (n > 0) {
+            printf("write(1, %p, 8192) returned %d, not -1 or 0\n", addr, n);
+            exit(1);
+        }
+
+        int fds[2];
+        if (pipe(fds) < 0) {
+            printf("pipe() failed\n");
+            exit(1);
+        }
+        n = write(fds[1], (char *)addr, 8192);
+        if (n > 0) {
+            printf("write(pipe, %p, 8192) returned %d, not -1 or 0\n", addr, n);
+            exit(1);
+        }
+        close(fds[0]);
+        close(fds[1]);
+    }
+    printf("copyin test OK\n");
+}
+
+// what if you pass ridiculous pointers to system calls
+// that write user memory with copyout?
+void copyout() {
+    printf("==========copyout test==========\n");
+    uint64 addrs[] = {0x80000000LL, 0xffffffffffffffff};
+
+    for (int ai = 0; ai < 2; ai++) {
+        uint64 addr = addrs[ai];
+
+        int fd = open("README.md", 0);
+        if (fd < 0) {
+            printf("open(README.md) failed\n");
+            exit(1);
+        }
+        int n = read(fd, (void *)addr, 8192);
+        if (n > 0) {
+            printf("read(fd, %p, 8192) returned %d, not -1 or 0\n", addr, n);
+            exit(1);
+        }
+        close(fd);
+
+        int fds[2];
+        if (pipe(fds) < 0) {
+            printf("pipe() failed\n");
+            exit(1);
+        }
+        n = write(fds[1], "x", 1);
+        if (n != 1) {
+            printf("pipe write failed\n");
+            exit(1);
+        }
+        n = read(fds[0], (void *)addr, 8192);
+        if (n > 0) {
+            printf("read(pipe, %p, 8192) returned %d, not -1 or 0\n", addr, n);
+            exit(1);
+        }
+        close(fds[0]);
+        close(fds[1]);
+    }
+    printf("copyout test OK\n");
+}
+
+// what if you pass ridiculous string pointers to system calls?
+void copyinstr1() {
+    printf("==========copyinstr1 test==========\n");
+    uint64 addrs[] = {0x80000000LL, 0xffffffffffffffff};
+
+    for (int ai = 0; ai < 2; ai++) {
+        uint64 addr = addrs[ai];
+
+        int fd = open((char *)addr, O_CREATE | O_WRONLY);
+        if (fd >= 0) {
+            printf("open(%p) returned %d, not -1\n", addr, fd);
+            exit(1);
+        }
+    }
+    printf("copyinstr1 test OK\n");
+}
+
+// See if the kernel refuses to read/write user memory that the
+// application doesn't have anymore, because it returned it.
+void rwsbrk() {
+    int fd, n;
+    printf("==========rwsbrk test==========\n");
+
+    uint64 a = (uint64)sbrk(8192);
+
+    if (a == 0xffffffffffffffffLL) {
+        printf("sbrk(rwsbrk) failed\n");
+        exit(1);
+    }
+
+    if ((uint64)sbrk(-8192) == 0xffffffffffffffffLL) {
+        printf("sbrk(rwsbrk) shrink failed\n");
+        exit(1);
+    }
+
+    fd = open("rwsbrk", O_CREATE | O_WRONLY);
+    if (fd < 0) {
+        printf("open(rwsbrk) failed\n");
+        exit(1);
+    }
+    n = write(fd, (void *)(a + 4096), 1024);
+    if (n >= 0) {
+        printf("write(fd, %p, 1024) returned %d, not -1\n", a + 4096, n);
+        exit(1);
+    }
+    close(fd);
+    unlink("rwsbrk");
+
+    fd = open("README.md", O_RDONLY);
+    if (fd < 0) {
+        printf("open(rwsbrk) failed\n");
+        exit(1);
+    }
+    n = read(fd, (void *)(a + 4096), 10);
+    if (n >= 0) {
+        printf("read(fd, %p, 10) returned %d, not -1\n", a + 4096, n);
+        exit(1);
+    }
+    close(fd);
+    printf("rwsbrk test OK\n");
+}
+
+// write to an open FD whose file has just been truncated.
+// this causes a write at an offset beyond the end of the file.
+// such writes fail on xv6 (unlike POSIX) but at least
+// they don't crash.
+void truncate2() {
+    printf("==========truncate2 test==========\n");
+    unlink("truncfile");
+
+    int fd1 = open("truncfile", O_CREATE | O_TRUNC | O_WRONLY);
+    write(fd1, "abcd", 4);
+
+    int fd2 = open("truncfile", O_TRUNC | O_WRONLY);
+
+    int n = write(fd1, "x", 1);
+    if (n != -1) {
+        printf("write returned %d, expected -1\n", n);
+        exit(1);
+    }
+
+    unlink("truncfile");
+    close(fd1);
+    close(fd2);
+    printf("truncate2 test OK\n");
+}
+
+void truncate3() {
+    printf("==========truncate3 test==========\n");
+    int pid, xstatus;
+
+    close(open("truncfile", O_CREATE | O_TRUNC | O_WRONLY));
+
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+
+    if (pid == 0) {
+        for (int i = 0; i < 100; i++) {
+            char buf[32];
+            int fd = open("truncfile", O_WRONLY);
+            if (fd < 0) {
+                printf("open failed\n");
+                exit(1);
+            }
+            int n = write(fd, "1234567890", 10);
+            if (n != 10) {
+                printf("write got %d, expected 10\n", n);
+                exit(1);
+            }
+            close(fd);
+            fd = open("truncfile", O_RDONLY);
+            read(fd, buf, sizeof(buf));
+            close(fd);
+        }
+        exit(0);
+    }
+
+    for (int i = 0; i < 150; i++) {
+        int fd = open("truncfile", O_CREATE | O_WRONLY | O_TRUNC);
+        if (fd < 0) {
+            printf("open failed\n");
+            exit(1);
+        }
+        int n = write(fd, "xxx", 3);
+        if (n != 3) {
+            printf("write got %d, expected 3\n", n);
+            exit(1);
+        }
+        close(fd);
+    }
+
+    wait(&xstatus);
+    unlink("truncfile");
+    printf("truncate3 test OK\n");
+}
+
+// does chdir() call iput(p->cwd) in a transaction?
+void iputtest() {
+    printf("==========iput test==========\n");
+    if (mkdir("iputdir", 0666) < 0) {
+        printf("mkdir failed\n");
+        exit(1);
+    }
+    if (chdir("iputdir") < 0) {
+        printf("chdir iputdir failed\n");
+        exit(1);
+    }
+    if (unlink("../iputdir") < 0) {
+        printf("unlink ../iputdir failed\n");
+        exit(1);
+    }
+    if (chdir("/") < 0) {
+        printf("chdir / failed\n");
+        exit(1);
+    }
+    printf("iput test OK\n");
+}
+
+// does exit() call iput(p->cwd) in a transaction?
+void exitiputtest() {
+    int pid, xstatus;
+    printf("==========exitiput test==========\n");
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        if (mkdir("iputdir", 0666) < 0) {
+            printf("mkdir failed\n");
+            exit(1);
+        }
+        printf("???\n");
+        if (chdir("iputdir") < 0) {
+            printf("child chdir failed\n");
+            exit(1);
+        }
+        if (unlink("../iputdir") < 0) {
+            printf("unlink ../iputdir failed\n");
+            exit(1);
+        }
+        exit(0);
+    }
+    wait(&xstatus);
+    printf("exitiput test OK\n");
+}
+
+// many creates, followed by unlink test
+void createtest() {
+    printf("==========createtest test==========\n");
+    int i, fd;
+    int N = 50;
+    char name[3];
+    name[0] = 'a';
+    name[2] = '\0';
+    for (i = 0; i < N; i++) {
+        name[1] = '0' + i;
+        fd = open(name, O_CREATE | O_RDWR);
+        close(fd);
+    }
+    name[0] = 'a';
+    name[2] = '\0';
+    for (i = 0; i < N; i++) {
+        name[1] = '0' + i;
+        unlink(name);
+    }
+    printf("createtest test OK\n");
+}
+
+// if process size was somewhat more than a page boundary, and then
+// shrunk to be somewhat less than that page boundary, can the kernel
+// still copyin() from addresses in the last page?
+void sbrklast() {
+    printf("==========sbrklast test==========\n");
+    uint64 top = (uint64)sbrk(0);
+    if ((top % 4096) != 0)
+        sbrk(4096 - (top % 4096));
+    sbrk(4096);
+    sbrk(10);
+    sbrk(-20);
+    top = (uint64)sbrk(0);
+    char *p = (char *)(top - 64);
+    p[0] = 'x';
+    p[1] = '\0';
+    int fd = open(p, O_RDWR | O_CREATE);
+    write(fd, p, 1);
+    close(fd);
+    fd = open(p, O_RDWR);
+    p[0] = '\0';
+    read(fd, p, 1);
+    if (p[0] != 'x')
+        exit(1);
+    printf("sbrklast test OK\n");
+}
+
+// // Section with tests that take a fair bit of time
+// // directory that uses indirect blocks
+// void bigdir() {
+//     printf("==========bigdir test==========\n");
+//     enum { N = 500 };
+//     int i, fd;
+//     char name[10];
+
+//     unlink("bd");
+
+//     fd = open("bd", O_CREATE);
+//     if (fd < 0) {
+//         printf("bigdir create failed\n");
+//         exit(1);
+//     }
+//     close(fd);
+
+//     for (i = 0; i < N; i++) {
+//         name[0] = 'x';
+//         name[1] = '0' + (i / 64);
+//         name[2] = '0' + (i % 64);
+//         name[3] = '\0';
+//         if (link("bd", name) != 0) {
+//             printf("bigdir link(bd, %s) failed\n", name);
+//             exit(1);
+//         }
+//     }
+
+//     unlink("bd");
+//     for (i = 0; i < N; i++) {
+//         name[0] = 'x';
+//         name[1] = '0' + (i / 64);
+//         name[2] = '0' + (i % 64);
+//         name[3] = '\0';
+//         if (unlink(name) != 0) {
+//             printf("bigdir unlink failed\n");
+//             exit(1);
+//         }
+//     }
+//     printf("bigdir test OK\n");
+// }
+
+void dirtest() {
+    printf("==========dir test=========\n");
+    if (mkdir("dir0", 0666) < 0) {
+        printf("mkdir failed\n");
+        exit(1);
+    }
+
+    if (chdir("dir0") < 0) {
+        printf("chdir dir0 failed\n");
+        exit(1);
+    }
+
+    if (chdir("..") < 0) {
+        printf("chdir .. failed\n");
+        exit(1);
+    }
+
+    if (unlink("dir0") < 0) {
+        printf("unlink dir0 failed\n");
+        exit(1);
+    }
+    printf("dir test OK\n");
+}
+
+void execvetest() {
+    printf("==========execve test=========\n");
+    int fd, xstatus, pid;
+    char *echoargv[] = {"echo", "OK", 0};
+    char buf[3];
+
+    unlink("echo-ok");
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        close(1);
+        fd = open("echo-ok", O_CREATE | O_WRONLY);
+        if (fd < 0) {
+            printf("create failed\n");
+            exit(1);
+        }
+        if (fd != 1) {
+            printf("wrong fd\n");
+            exit(1);
+        }
+        if (execve("echo", echoargv, NULL) < 0) {
+            printf("execve echo failed\n");
+            exit(1);
+        }
+        // won't get to here
+    }
+
+    if (wait(&xstatus) != pid) {
+        printf("wait failed!\n");
+    }
+    if (xstatus != 0)
+        exit(xstatus);
+
+    fd = open("echo-ok", O_RDONLY);
+    if (fd < 0) {
+        printf("open failed\n");
+        exit(1);
+    }
+    if (read(fd, buf, 2) != 2) {
+        printf("read failed\n");
+        exit(1);
+    }
+    unlink("echo-ok");
+    if (buf[0] == 'O' && buf[1] == 'K') {
+        printf("execve test OK\n");
+    } else {
+        printf("wrong output\n");
+        exit(1);
+    }
+}
+
+void uvmfree() {
+    printf("==========uvmfree test=========\n");
+    enum { BIG = 100 * 1024 * 1024 };
+    char *a, *p;
+    uint64 amt;
+
+    int pid = fork();
+    if (pid == 0) {
+        // oldbrk = sbrk(0);
+
+        // can one grow address space to something big?
+        a = sbrk(0);
+        amt = BIG - (uint64)a;
+        p = sbrk(amt);
+        if (p != a) {
+            printf("sbrk test failed to grow big address space; enough phys mem?\n");
+            exit(1);
+        }
+        printf("child done\n");
+        exit(0);
+    } else if (pid > 0) {
+        wait((int *)0);
+        printf("uvmfree test OK\n");
+    } else {
+        printf("fork failed");
+        exit(1);
+    }
+}
+
+// simple fork and pipe read/write
+void pipe1() {
+    printf("==========pipe1 test=========\n");
+    int fds[2], pid, xstatus;
+    int seq, i, n, cc, total;
+    enum { N = 5,
+           SZ = 1033 };
+
+    if (pipe(fds) != 0) {
+        printf("pipe() failed\n");
+        exit(1);
+    }
+    pid = fork();
+    seq = 0;
+    if (pid == 0) {
+        close(fds[0]);
+        for (n = 0; n < N; n++) {
+            for (i = 0; i < SZ; i++)
+                buf[i] = seq++;
+            if (write(fds[1], buf, SZ) != SZ) {
+                printf("pipe1 oops 1\n");
+                exit(1);
+            }
+        }
+        exit(0);
+    } else if (pid > 0) {
+        close(fds[1]);
+        total = 0;
+        cc = 1;
+        while ((n = read(fds[0], buf, cc)) > 0) {
+            for (i = 0; i < n; i++) {
+                if ((buf[i] & 0xff) != (seq++ & 0xff)) {
+                    printf("pipe1 oops 2\n");
+                    return;
+                }
+            }
+            total += n;
+            cc = cc * 2;
+            if (cc > sizeof(buf))
+                cc = sizeof(buf);
+        }
+        if (total != N * SZ) {
+            printf("pipe1 oops 3 total %d\n", total);
+            exit(1);
+        }
+        close(fds[0]);
+        wait(&xstatus);
+        printf("pipe1 test OK\n");
+    } else {
+        printf("fork() failed\n");
+        exit(1);
+    }
+}
+
+// allocate all mem, free it, and allocate again
+void mem() {
+    printf("==========mem test=========\n");
+    void *m1, *m2;
+    int pid;
+
+    if ((pid = fork()) == 0) {
+        m1 = 0;
+        while ((m2 = malloc(10001)) != 0) {
+            *(char **)m2 = m1;
+            m1 = m2;
+        }
+        while (m1) {
+            m2 = *(char **)m1;
+            free(m1);
+            m1 = m2;
+        }
+        m1 = malloc(1024 * 20);
+        if (m1 == 0) {
+            printf("couldn't allocate mem?!!\n");
+            exit(1);
+        }
+        free(m1);
+        exit(0);
+    } else {
+        int xstatus;
+        wait(&xstatus);
+        if (xstatus == -1) {
+            // probably page fault, so might be lazy lab,
+            // so OK.
+            exit(0);
+        }
+        printf("mem test OK\n");
+    }
+}
+
+// two processes write to the same file descriptor
+// is the offset shared? does inode locking work?
+void sharedfd() {
+    printf("==========sharedfd test=========\n");
+    int fd, pid, i, n, nc, np;
+    enum { N = 1000,
+           SZ = 10 };
+    char buf[SZ];
+
+    unlink("sharedfd");
+    fd = open("sharedfd", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("cannot open sharedfd for writing");
+        exit(1);
+    }
+    pid = fork();
+    memset(buf, pid == 0 ? 'c' : 'p', sizeof(buf));
+    for (i = 0; i < N; i++) {
+        if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
+            printf("write sharedfd failed\n");
+            exit(1);
+        }
+    }
+    if (pid == 0) {
+        exit(0);
+    } else {
+        int xstatus;
+        wait(&xstatus);
+        if (xstatus != 0)
+            exit(xstatus);
+    }
+
+    close(fd);
+    fd = open("sharedfd", 0);
+    if (fd < 0) {
+        printf("cannot open sharedfd for reading\n");
+        exit(1);
+    }
+    nc = np = 0;
+    while ((n = read(fd, buf, sizeof(buf))) > 0) {
+        for (i = 0; i < sizeof(buf); i++) {
+            if (buf[i] == 'c')
+                nc++;
+            if (buf[i] == 'p')
+                np++;
+        }
+    }
+    close(fd);
+    unlink("sharedfd");
+    if (nc == N * SZ && np == N * SZ) {
+        printf("sharedfd test OK\n");
+    } else {
+        printf("nc/np test fails\n");
+        exit(1);
+    }
+}
+
+// four processes write different files at the same
+// time, to test block allocation.
+void fourfiles() {
+    printf("==========fourfiles test=========\n");
+    int fd, pid, i, n, pi;
+    int total;
+    int j;
+    char *names[] = {"f0", "f1", "f2", "f3"};
+    char *fname;
+    enum { N = 12,
+           NCHILD = 4,
+           SZ = 500 };
+
+    for (pi = 0; pi < NCHILD; pi++) {
+        fname = names[pi];
+        unlink(fname);
+
+        pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+
+        if (pid == 0) {
+            fd = open(fname, O_CREATE | O_RDWR);
+            if (fd < 0) {
+                printf("create failed\n");
+                exit(1);
+            }
+
+            memset(buf, '0' + pi, SZ);
+            for (i = 0; i < N; i++) {
+                if ((n = write(fd, buf, SZ)) != SZ) {
+                    printf("write failed %d\n", n);
+                    exit(1);
+                }
+            }
+            exit(0);
+        }
+    }
+
+    int xstatus;
+    for (pi = 0; pi < NCHILD; pi++) {
+        wait(&xstatus);
+        if (xstatus != 0)
+            exit(xstatus);
+    }
+
+    for (i = 0; i < NCHILD; i++) {
+        fname = names[i];
+        fd = open(fname, 0);
+        total = 0;
+        while ((n = read(fd, buf, sizeof(buf))) > 0) {
+            for (j = 0; j < n; j++) {
+                if (buf[j] != '0' + i) {
+                    printf("wrong char\n");
+                    exit(1);
+                }
+            }
+            total += n;
+        }
+        close(fd);
+        if (total != N * SZ) {
+            printf("wrong length %d\n", total);
+            exit(1);
+        }
+        unlink(fname);
+    }
+    printf("fourfiles test OK\n");
+}
+
+// four processes create and delete different files in same directory
+void createdelete() {
+    printf("==========createdelete test=========\n");
+    enum { N = 20,
+           NCHILD = 4 };
+    int pid, i, fd, pi;
+    char name[32];
+
+    for (pi = 0; pi < NCHILD; pi++) {
+        pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+
+        if (pid == 0) {
+            name[0] = 'p' + pi;
+            name[2] = '\0';
+            for (i = 0; i < N; i++) {
+                name[1] = '0' + i;
+                fd = open(name, O_CREATE | O_RDWR);
+                if (fd < 0) {
+                    printf("create failed\n");
+                    exit(1);
+                }
+                close(fd);
+                if (i > 0 && (i % 2) == 0) {
+                    name[1] = '0' + (i / 2);
+                    if (unlink(name) < 0) {
+                        printf("unlink failed\n");
+                        exit(1);
+                    }
+                }
+            }
+            exit(0);
+        }
+    }
+    int xstatus;
+    for (pi = 0; pi < NCHILD; pi++) {
+        wait(&xstatus);
+        if (xstatus != 0)
+            exit(1);
+    }
+
+    name[0] = name[1] = name[2] = 0;
+    for (i = 0; i < N; i++) {
+        for (pi = 0; pi < NCHILD; pi++) {
+            name[0] = 'p' + pi;
+            name[1] = '0' + i;
+            fd = open(name, 0);
+            if ((i == 0 || i >= N / 2) && fd < 0) {
+                printf("oops createdelete %s didn't exist\n", name);
+                exit(1);
+            } else if ((i >= 1 && i < N / 2) && fd >= 0) {
+                printf("oops createdelete %s did exist\n", name);
+                exit(1);
+            }
+            if (fd >= 0)
+                close(fd);
+        }
+    }
+
+    for (i = 0; i < N; i++) {
+        for (pi = 0; pi < NCHILD; pi++) {
+            name[0] = 'p' + i;
+            name[1] = '0' + i;
+            unlink(name);
+        }
+    }
+    printf("createdelete test OK\n");
+}
+
+// can I unlink a file and still read it?
+void unlinkread() {
+    printf("==========unlinkread test=========\n");
+    enum { SZ = 5 };
+    int fd;
+
+    fd = open("unlinkread", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("create unlinkread failed\n");
+        exit(1);
+    }
+    write(fd, "hello", SZ);
+    close(fd);
+
+    fd = open("unlinkread", O_RDWR);
+    if (fd < 0) {
+        printf("open unlinkread failed\n");
+        exit(1);
+    }
+    if (unlink("unlinkread") != 0) {
+        printf("unlink unlinkread failed\n");
+        exit(1);
+    }
+
+    int fd1 = open("unlinkread", O_CREATE | O_RDWR);
+    write(fd1, "yyy", 3);
+    close(fd1);
+
+    if (read(fd, buf, sizeof(buf)) != SZ) {
+        printf("unlinkread read failed");
+        exit(1);
+    }
+
+    // printf("%s\n", buf);
+    if (buf[0] != 'h') {
+        printf("unlinkread wrong data\n");
+        exit(1);
+    }
+
+    // if (write(fd, buf, 10) != 10) {
+    //     printf("unlinkread write failed\n");
+    //     exit(1);
+    // }
+    // close(fd);
+    // unlink("unlinkread");
+    printf("unlinkread test OK\n");
+}
+
+// test writes that are larger than the log.
+void bigwrite() {
+    int fd, sz;
+    printf("==========bigwrite test=========\n");
+    unlink("bigwrite");
+    for (sz = 499; sz < (MAXOPBLOCKS + 2) * BSIZE; sz += 471) {
+        fd = open("bigwrite", O_CREATE | O_RDWR);
+        if (fd < 0) {
+            printf("cannot create bigwrite\n");
+            exit(1);
+        }
+        int i;
+        for (i = 0; i < 2; i++) {
+            int cc = write(fd, buf, sz);
+            if (cc != sz) {
+                printf("write(%d) ret %d\n", sz, cc);
+                exit(1);
+            }
+        }
+        close(fd);
+        unlink("bigwrite");
+    }
+    printf("bigwrite test OK\n");
+}
+
+void bigfile() {
+    printf("==========bigfile test=========\n");
+    enum { N = 20,
+           SZ = 600 };
+    int fd, i, total, cc;
+
+    unlink("bigfile.dat");
+    fd = open("bigfile.dat", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("cannot create bigfile");
+        exit(1);
+    }
+    for (i = 0; i < N; i++) {
+        memset(buf, i, SZ);
+        if (write(fd, buf, SZ) != SZ) {
+            printf("write bigfile failed\n");
+            exit(1);
+        }
+    }
+    close(fd);
+
+    fd = open("bigfile.dat", 0);
+    if (fd < 0) {
+        printf("cannot open bigfile\n");
+        exit(1);
+    }
+    total = 0;
+    for (i = 0;; i++) {
+        cc = read(fd, buf, SZ / 2);
+        if (cc < 0) {
+            printf("read bigfile failed\n");
+            exit(1);
+        }
+        if (cc == 0)
+            break;
+        if (cc != SZ / 2) {
+            printf("short read bigfile\n");
+            exit(1);
+        }
+        if (buf[0] != i / 2 || buf[SZ / 2 - 1] != i / 2) {
+            printf("read bigfile wrong data\n");
+            exit(1);
+        }
+        total += cc;
+    }
+    close(fd);
+    if (total != N * SZ) {
+        printf("read bigfile wrong total\n");
+        exit(1);
+    }
+    unlink("bigfile.dat");
+    printf("bigfile test OK\n");
+}
+
+void fourteen() {
+    printf("==========fourteen test=========\n");
+
+    // DIRSIZ is 14.
+
+    if (mkdir("12345678901234", 0666) != 0) {
+        printf("mkdir 12345678901234 failed\n");
+        exit(1);
+    }
+    if (mkdir("12345678901234/123456789012345", 0666) != 0) {
+        printf("mkdir 12345678901234/123456789012345 failed\n");
+        exit(1);
+    }
+    int fd = open("123456789012345/123456789012345/123456789012345", O_CREATE);
+    if (fd < 0) {
+        printf("create 123456789012345/123456789012345/123456789012345 failed\n");
+        exit(1);
+    }
+
+    // close(fd);
+    // fd = open("12345678901234/12345678901234/12345678901234", 0);
+    // if (fd < 0) {
+    //     printf("open 12345678901234/12345678901234/12345678901234 failed\n");
+    //     exit(1);
+    // }
+    // close(fd);
+
+    // if (mkdir("12345678901234/12345678901234", 0666) == 0) {
+    //     printf("mkdir 12345678901234/12345678901234 succeeded!\n");
+    //     exit(1);
+    // }
+    // if (mkdir("123456789012345/12345678901234", 0666) == 0) {
+    //     printf("mkdir 12345678901234/123456789012345 succeeded!\n");
+    //     exit(1);
+    // }
+
+    // // clean up
+    // unlink("123456789012345/12345678901234");
+    // unlink("12345678901234/12345678901234");
+    // unlink("12345678901234/12345678901234/12345678901234");
+    // unlink("123456789012345/123456789012345/123456789012345");
+    // unlink("12345678901234/123456789012345");
+    // unlink("12345678901234");
+
+    printf("fourteen test OK\n");
+}
+
+void rmdot() {
+    printf("==========rmdom test=========\n");
+    if (mkdir("dots", 066) != 0) {
+        printf("mkdir dots failed\n");
+        exit(1);
+    }
+    if (chdir("dots") != 0) {
+        printf("chdir dots failed\n");
+        exit(1);
+    }
+    if (unlink(".") == 0) {
+        printf("rm . worked!\n");
+        exit(1);
+    }
+    if (unlink("..") == 0) {
+        printf("rm .. worked!\n");
+        exit(1);
+    }
+    if (chdir("/") != 0) {
+        printf("chdir / failed\n");
+        exit(1);
+    }
+    if (unlink("dots/.") == 0) {
+        printf("unlink dots/. worked!\n");
+        exit(1);
+    }
+    if (unlink("dots/..") == 0) {
+        printf("unlink dots/.. worked!\n");
+        exit(1);
+    }
+    if (unlink("dots") != 0) {
+        printf("unlink dots failed!\n");
+        exit(1);
+    }
+    printf("rmdot test OK\n");
+}
+
+// regression test. test whether exec() leaks memory if one of the
+// arguments is invalid. the test passes if the kernel doesn't panic.
+void badarg() {
+    printf("==========badarg test=========\n");
+    for (int i = 0; i < 50000; i++) {
+        char *argv[2];
+        argv[0] = (char *)0xffffffff;
+        argv[1] = 0;
+        execve("echo", argv, NULL);
+    }
+    printf("badarg test OK\n");
+}
+
+// does sbrk handle signed int32 wrap-around with
+// negative arguments?
+void sbrk8000() {
+    printf("==========sbrk8000 test=========\n");
+    sbrk(0x80000004);
+    volatile char *top = sbrk(0);
+    *(top - 1) = *(top - 1) + 1;
+    printf("sbrk8000 test OK\n");
+}
+
+// check that writes to text segment fault
+void textwrite() {
+    printf("==========textwrite test=========\n");
+    int pid;
+    int xstatus;
+
+    pid = fork();
+    if (pid == 0) {
+        volatile int *addr = (int *)0;
+        *addr = 10;
+        exit(1);
+    } else if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    wait(&xstatus);
+    if (xstatus == -1 << 8) // kernel killed child?
+    {
+        printf("textwrite test OK\n");
+    } else
+        exit(xstatus);
+}
+
+void outofinodes() {
+    printf("==========outofinodes test=========\n");
+    int nzz = 32 * 32;
+    for (int i = 0; i < nzz; i++) {
+        char name[32];
+        name[0] = 'z';
+        name[1] = 'z';
+        name[2] = '0' + (i / 32);
+        name[3] = '0' + (i % 32);
+        name[4] = '\0';
+        unlink(name);
+        int fd = open(name, O_CREATE | O_RDWR | O_TRUNC);
+        if (fd < 0) {
+            // failure is eventually expected.
+            break;
+        }
+        close(fd);
+    }
+
+    for (int i = 0; i < nzz; i++) {
+        char name[32];
+        name[0] = 'z';
+        name[1] = 'z';
+        name[2] = '0' + (i / 32);
+        name[3] = '0' + (i % 32);
+        name[4] = '\0';
+        unlink(name);
+    }
+    printf("outofinodes test OK\n");
+}
+
+// concurrent writes to try to provoke deadlock in the virtio disk
+// driver.
+void manywrites() {
+    printf("==========manywrites test=========\n");
+    int nchildren = 4;
+    int howmany = 30; // increase to look for deadlock
+
+    for (int ci = 0; ci < nchildren; ci++) {
+        int pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+
+        if (pid == 0) {
+            char name[3];
+            name[0] = 'b';
+            name[1] = 'a' + ci;
+            name[2] = '\0';
+            unlink(name);
+
+            for (int iters = 0; iters < howmany; iters++) {
+                for (int i = 0; i < ci + 1; i++) {
+                    int fd = open(name, O_CREATE | O_RDWR);
+                    if (fd < 0) {
+                        printf("cannot create %s\n", name);
+                        exit(1);
+                    }
+                    int sz = sizeof(buf);
+                    int cc = write(fd, buf, sz);
+                    if (cc != sz) {
+                        printf("write(%d) ret %d\n", sz, cc);
+                        exit(1);
+                    }
+                    close(fd);
+                }
+                unlink(name);
+            }
+
+            unlink(name);
+            exit(0);
+        }
+    }
+
+    for (int ci = 0; ci < nchildren; ci++) {
+        int st = 0;
+        wait(&st);
+        if (st != 0)
+            exit(st);
+    }
+    printf("manywrites test OK\n");
+}
+
+// regression test. does write() with an invalid buffer pointer cause
+// a block to be allocated for a file that is then not freed when the
+// file is deleted? if the kernel has this bug, it will panic: balloc:
+// out of blocks. assumed_free may need to be raised to be more than
+// the number of free blocks. this test takes a long time.
+void badwrite() {
+    printf("==========badwrite test=========\n");
+    int assumed_free = 600;
+
+    unlink("junk");
+    for (int i = 0; i < assumed_free; i++) {
+        int fd = open("junk", O_CREATE | O_WRONLY);
+        if (fd < 0) {
+            printf("open junk failed\n");
+            exit(1);
+        }
+        write(fd, (char *)0xffffffffffL, 1);
+        close(fd);
+        unlink("junk");
+    }
+
+    int fd = open("junk", O_CREATE | O_WRONLY);
+    if (fd < 0) {
+        printf("open junk failed\n");
+        exit(1);
+    }
+    if (write(fd, "x", 1) != 1) {
+        printf("write failed\n");
+        exit(1);
+    }
+    close(fd);
+    unlink("junk");
+
+    printf("badwrite test OK\n");
+}
+
+// test the execveout() code that cleans up if it runs out
+// of memory. it's really a test that such a condition
+// doesn't cause a panic.
+void execveout() {
+    printf("==========execveout test=========\n");
+    for (int avail = 0; avail < 15; avail++) {
+        int pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        } else if (pid == 0) {
+            // allocate all of memory.
+            while (1) {
+                uint64 a = (uint64)sbrk(4096);
+                if (a == 0xffffffffffffffffLL)
+                    break;
+                *(char *)(a + 4096 - 1) = 1;
+            }
+
+            // free a few pages, in order to let exec() make some
+            // progress.
+            for (int i = 0; i < avail; i++)
+                sbrk(-4096);
+
+            close(1);
+            char *args[] = {"echo", "x", 0};
+            execve("echo", args, NULL);
+            exit(0);
+        } else {
+            wait((int *)0);
+        }
+    }
+    printf("execveout test OK\n");
+}
+
+// can the kernel tolerate running out of disk space?
+void diskfull() {
+    printf("==========diskfull test=========\n");
+    int fi;
+    int done = 0;
+    int MAXFILE = 2000;
+
+    unlink("diskfulldir");
+
+    for (fi = 0; done == 0; fi++) {
+        char name[32];
+        name[0] = 'b';
+        name[1] = 'i';
+        name[2] = 'g';
+        name[3] = '0' + fi;
+        name[4] = '\0';
+        unlink(name);
+        int fd = open(name, O_CREATE | O_RDWR | O_TRUNC);
+        if (fd < 0) {
+            // oops, ran out of inodes before running out of blocks.
+            printf("could not create file %s\n", name);
+            done = 1;
+            break;
+        }
+        for (int i = 0; i < MAXFILE; i++) {
+            // char buf[BSIZE];
+            if (write(fd, "hello", 5) != 5) {
+                done = 1;
+                close(fd);
+                break;
+            }
+        }
+        done = 1;
+        close(fd);
+    }
+    printf("disk is full\n");
+    // now that there are no free blocks, test that dirlink()
+    // merely fails (doesn't panic) if it can't extend
+    // directory content. one of these file creations
+    // is expected to fail.
+    // int nzz = 128;
+    // for (int i = 0; i < nzz; i++) {
+    //     char name[32];
+    //     name[0] = 'z';
+    //     name[1] = 'z';
+    //     name[2] = '0' + (i / 32);
+    //     name[3] = '0' + (i % 32);
+    //     name[4] = '\0';
+    //     unlink(name);
+    //     int fd = open(name, O_CREATE | O_RDWR | O_TRUNC);
+    //     if (fd < 0)
+    //         break;
+    //     close(fd);
+    // }
+
+    // // this mkdir() is expected to fail.
+    // if (mkdir("diskfulldir", 0666) == 0)
+    //     printf("mkdir(diskfulldir) unexpectedly succeeded!\n");
+
+    // unlink("diskfulldir");
+
+    // for (int i = 0; i < nzz; i++) {
+    //     char name[32];
+    //     name[0] = 'z';
+    //     name[1] = 'z';
+    //     name[2] = '0' + (i / 32);
+    //     name[3] = '0' + (i % 32);
+    //     name[4] = '\0';
+    //     unlink(name);
+    // }
+
+    // for (int i = 0; i < fi; i++) {
+    //     char name[32];
+    //     name[0] = 'b';
+    //     name[1] = 'i';
+    //     name[2] = 'g';
+    //     name[3] = '0' + i;
+    //     name[4] = '\0';
+    //     unlink(name);
+    // }
+    printf("diskfull test OK\n");
+}
+
+void sbrkbasic() {
+    printf("==========sbrkbasic test=========\n");
+    enum { TOOMUCH = 1024 * 1024 * 1024 };
+    int i, pid, xstatus;
+    char *c, *a, *b;
+
+    // does sbrk() return the expected failure value?
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed in sbrkbasic\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        a = sbrk(TOOMUCH);
+        if (a == (char *)0xffffffffffffffffL) {
+            // it's OK if this fails.
+            exit(0);
+        }
+
+        for (b = a; b < a + TOOMUCH; b += 4096) {
+            *b = 99;
+        }
+
+        // we should not get here! either sbrk(TOOMUCH)
+        // should have failed, or (with lazy allocation)
+        // a pagefault should have killed this process.
+        exit(1);
+    }
+
+    wait(&xstatus);
+    if (xstatus == 1) {
+        printf("too much memory allocated!\n");
+        exit(1);
+    }
+
+    // can one sbrk() less than a page?
+    a = sbrk(0);
+    for (i = 0; i < 5000; i++) {
+        b = sbrk(1);
+        if (b != a) {
+            printf("sbrk test failed %d %x %x\n", i, a, b);
+            exit(1);
+        }
+        *b = 1;
+        a = b + 1;
+    }
+    pid = fork();
+    if (pid < 0) {
+        printf("sbrk test fork failed\n");
+        exit(1);
+    }
+    c = sbrk(1);
+    c = sbrk(1);
+    if (c != a + 1) {
+        printf("sbrk test failed post-fork\n");
+        exit(1);
+    }
+    if (pid == 0)
+        exit(0);
+    wait(&xstatus);
+    printf("sbrkbasic test OK\n");
+}
+
+#define PGSIZE 4096 // bytes per page
+void sbrkmuch() {
+    printf("==========sbrkmuch test=========\n");
+    enum { BIG = 100 * 1024 * 1024 };
+    char *c, *oldbrk, *a, *lastaddr, *p;
+    uint64 amt;
+
+    oldbrk = sbrk(0);
+
+    // can one grow address space to something big?
+    a = sbrk(0);
+    amt = BIG - (uint64)a;
+    p = sbrk(amt);
+    if (p != a) {
+        printf("sbrk test failed to grow big address space; enough phys mem?\n");
+        exit(1);
+    }
+
+    // touch each page to make sure it exists.
+    char *eee = sbrk(0);
+    // printf("eee is %x", eee);
+    // print_pgtable();
+    // int cnt = 0;
+    for (char *pp = a; pp < eee; pp += 4096) {
+        // printf("%x\n", pp);
+        *pp = 1;
+    }
+
+    lastaddr = (char *)(BIG - 1);
+    *lastaddr = 99;
+
+    // can one de-allocate?
+    a = sbrk(0);
+    // printf("a is %x %d\n", a, a);
+    // print_pgtable();
+    c = sbrk(-PGSIZE);
+    // print_pgtable();
+    if (c == (char *)0xffffffffffffffffL) {
+        printf("sbrk could not deallocate\n");
+        exit(1);
+    }
+    c = sbrk(0);
+    // printf("c is %x %d\n", c, c);
+    if (c != a - PGSIZE) {
+        printf("sbrk deallocation produced wrong address, a %x c %x\n", a, c);
+        exit(1);
+    }
+
+    // can one re-allocate that page?
+    a = sbrk(0);
+    c = sbrk(PGSIZE);
+    if (c != a || sbrk(0) != a + PGSIZE) {
+        printf("sbrk re-allocation failed, a %x c %x\n", a, c);
+        exit(1);
+    }
+    if (*lastaddr == 99) {
+        // should be zero
+        printf("sbrk de-allocation didn't really deallocate\n");
+        exit(1);
+    }
+
+    a = sbrk(0);
+    c = sbrk(-(sbrk(0) - oldbrk));
+    if (c != a) {
+        printf("sbrk downsize failed, a %x c %x\n", a, c);
+        exit(1);
+    }
+    printf("sbrkmuch test OK\n");
+}
+
+#define KERNBASE 0x80200000L
+// can we read the kernel's memory?
+void kernmem() {
+    printf("==========kernmem test=========\n");
+    char *a;
+    int pid;
+
+    for (a = (char *)(KERNBASE); a < (char *)(KERNBASE + 2000000); a += 50000) {
+        pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+        if (pid == 0) {
+            printf("oops could read %x = %x\n", a, *a);
+            exit(1);
+        }
+        int xstatus;
+        wait(&xstatus);
+        printf("%d\n", xstatus);
+        if (xstatus != -1 << 8) // did kernel kill child?
+            exit(1);
+    }
+    printf("kernmem test OK\n");
+}
+
+#define MAXVA (1L << (9 + 9 + 9 + 12 - 1))
+// user code should not be able to write to addresses above MAXVA.
+void MAXVAplus() {
+    printf("==========MAXVAplus test=========\n");
+    volatile uint64 a = MAXVA;
+    for (; a != 0; a <<= 1) {
+        int pid;
+        pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(1);
+        }
+        if (pid == 0) {
+            *(char *)a = 99;
+            printf("oops wrote %x\n", a);
+            exit(1);
+        }
+        int xstatus;
+        wait(&xstatus);
+        if (xstatus != -1 << 8) // did kernel kill child?
+            exit(1);
+    }
+    printf("MAXVAplus test OK\n");
+}
+
+// if we run the system out of memory, does it clean up the last
+// failed allocation?
+void sbrkfail() {
+    printf("==========sbrkfail test=========\n");
+    enum { BIG = 100 * 1024 * 1024 };
+    int i, xstatus;
+    int fds[2];
+    char scratch;
+    char *c, *a;
+    int pids[10];
+    int pid;
+
+    if (pipe(fds) != 0) {
+        printf("pipe() failed\n");
+        exit(1);
+    }
+    for (i = 0; i < sizeof(pids) / sizeof(pids[0]); i++) {
+        if ((pids[i] = fork()) == 0) {
+            // allocate a lot of memory
+            sbrk(BIG - (uint64)sbrk(0));
+            write(fds[1], "x", 1);
+            // sit around until killed
+            for (;;) sleep(1000);
+        }
+        if (pids[i] != -1)
+            read(fds[0], &scratch, 1);
+    }
+
+    // if those failed allocations freed up the pages they did allocate,
+    // we'll be able to allocate here
+    c = sbrk(PGSIZE);
+    for (i = 0; i < sizeof(pids) / sizeof(pids[0]); i++) {
+        if (pids[i] == -1)
+            continue;
+        kill(pids[i]);
+        wait(0);
+    }
+    if (c == (char *)0xffffffffffffffffL) {
+        printf("failed sbrk leaked memory\n");
+        exit(1);
+    }
+
+    // test running fork with the above allocated page
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        // allocate a lot of memory.
+        // this should produce a page fault,
+        // and thus not complete.
+        a = sbrk(0);
+        sbrk(10 * BIG);
+        int n = 0;
+        for (i = 0; i < 10 * BIG; i += PGSIZE) {
+            n += *(a + i);
+        }
+        // print n so the compiler doesn't optimize away
+        // the for loop.
+        printf("allocate a lot of memory succeeded %d\n", n);
+        exit(1);
+    }
+    wait(&xstatus);
+    if (xstatus != -1 << 8 && xstatus != 2)
+        exit(1);
+    printf("sbrkfail test OK\n");
+}
+
+// test reads/writes from/to allocated memory
+void sbrkarg() {
+    printf("==========sbrkarg test=========\n");
+    char *a;
+    int fd, n;
+
+    a = sbrk(PGSIZE);
+    fd = open("sbrk", O_CREATE | O_WRONLY);
+    unlink("sbrk");
+    if (fd < 0) {
+        printf("open sbrk failed\n");
+        exit(1);
+    }
+    if ((n = write(fd, a, PGSIZE)) < 0) {
+        printf("write sbrk failed\n");
+        exit(1);
+    }
+    close(fd);
+
+    // test writes to allocated memory
+    a = sbrk(PGSIZE);
+    if (pipe((int *)a) != 0) {
+        printf("pipe() failed\n");
+        exit(1);
+    }
+    printf("sbrkarg test OK\n");
+}
+
+// does uninitialized data start out zero?
+char uninit[10000];
+void bsstest() {
+    printf("==========bsstest test=========\n");
+    int i;
+    for (i = 0; i < sizeof(uninit); i++) {
+        if (uninit[i] != '\0') {
+            printf("bss test failed\n");
+            exit(1);
+        }
+    }
+    printf("bsstest test OK\n");
+}
+
+// does exec return an error if the arguments
+// are larger than a page? or does it write
+// below the stack and wreck the instructions/data?
+#define MAXARG 32 // max exec arguments
+void bigargtest() {
+    printf("==========bigargtest test=========\n");
+    int pid, fd, xstatus;
+
+    unlink("bigarg-ok");
+    pid = fork();
+    if (pid == 0) {
+        static char *args[MAXARG];
+        int i;
+        for (i = 0; i < MAXARG - 1; i++)
+            args[i] = "bigargs test: failed\n                                                                                                                                                                                                       ";
+        args[MAXARG - 1] = 0;
+        execve("echo", args, NULL);
+        fd = open("bigarg-ok", O_CREATE);
+        close(fd);
+        exit(0);
+    } else if (pid < 0) {
+        printf("bigargtest: fork failed\n");
+        exit(1);
+    }
+
+    wait(&xstatus);
+    if (xstatus != 0)
+        exit(xstatus);
+    fd = open("bigarg-ok", 0);
+    if (fd < 0) {
+        printf("bigarg test failed!\n");
+        exit(1);
+    }
+    close(fd);
+    printf("bigargtest test OK\n");
+}
+
+// what happens when the file system runs out of blocks?
+// answer: balloc panics, so this test is not useful.
+void fsfull() {
+    int nfiles;
+    int fsblocks = 0;
+
+    printf("==========fsfull test=========\n");
+
+    for (nfiles = 0;; nfiles++) {
+        char name[64];
+        name[0] = 'f';
+        name[1] = '0' + nfiles / 1000;
+        name[2] = '0' + (nfiles % 1000) / 100;
+        name[3] = '0' + (nfiles % 100) / 10;
+        name[4] = '0' + (nfiles % 10);
+        name[5] = '\0';
+        printf("writing %s\n", name);
+        int fd = open(name, O_CREATE | O_RDWR);
+        if (fd < 0) {
+            printf("open %s failed\n", name);
+            break;
+        }
+        int total = 0;
+        while (1) {
+            int cc = write(fd, buf, BSIZE);
+            if (cc < BSIZE)
+                break;
+            total += cc;
+            fsblocks++;
+        }
+        printf("wrote %d bytes\n", total);
+        close(fd);
+        if (total == 0)
+            break;
+    }
+
+    while (nfiles >= 0) {
+        char name[64];
+        name[0] = 'f';
+        name[1] = '0' + nfiles / 1000;
+        name[2] = '0' + (nfiles % 1000) / 100;
+        name[3] = '0' + (nfiles % 100) / 10;
+        name[4] = '0' + (nfiles % 10);
+        name[5] = '\0';
+        unlink(name);
+        nfiles--;
+    }
+
+    printf("fsfull test OK\n");
+}
+
+void argptest() {
+    printf("==========argptest test=========\n");
+    int fd;
+    fd = open("init", O_RDONLY);
+    if (fd < 0) {
+        printf("open failed\n");
+        exit(1);
+    }
+    read(fd, sbrk(0) - 1, -1);
+    close(fd);
+    printf("argptest test OK\n");
+}
+
+static inline uint64
+r_sp() {
+    uint64 x;
+    asm volatile("mv %0, sp"
+                 : "=r"(x));
+    return x;
+}
+
+// check that there's an invalid page beneath
+// the user stack, to catch stack overflow.
+void stacktest() {
+    printf("==========stacktest test=========\n");
+    int pid;
+    int xstatus;
+
+    pid = fork();
+    if (pid == 0) {
+        char *sp = (char *)r_sp();
+        sp -= PGSIZE;
+        // the *sp should cause a trap.
+        printf("stacktest: read below stack %p\n", *sp);
+        exit(1);
+    } else if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    wait(&xstatus);
+    if (xstatus == -1 << 8) // kernel killed child?
+    {
+        printf("stacktest test OK\n");
+    } else
+        exit(xstatus);
+}
+
+// regression test. copyin(), copyout(), and copyinstr() used to cast
+// the virtual page address to uint, which (with certain wild system
+// call arguments) resulted in a kernel page faults.
+void *big = (void *)0xeaeb0b5b00002f5e;
+void pgbug() {
+    printf("==========pgbug test=========\n");
+    char *argv[1];
+    argv[0] = 0;
+    execve(big, argv, NULL);
+    pipe(big);
+
+    printf("pgbug test OK\n");
+}
+
+// regression test. does the kernel panic if a process sbrk()s its
+// size to be less than a page, or zero, or reduces the break by an
+// amount too small to cause a page to be freed?
+void sbrkbugs() {
+    printf("==========sbrkbugs test=========\n");
+    int pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        int sz = (uint64)sbrk(0);
+        // free all user memory; there used to be a bug that
+        // would not adjust p->sz correctly in this case,
+        // causing exit() to panic.
+        sbrk(-sz);
+        // user page fault here.
+        exit(0);
+    }
+    wait(0);
+
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        int sz = (uint64)sbrk(0);
+        // set the break to somewhere in the very first
+        // page; there used to be a bug that would incorrectly
+        // free the first page.
+        sbrk(-(sz - 3500));
+        exit(0);
+    }
+    wait(0);
+
+    pid = fork();
+    if (pid < 0) {
+        printf("fork failed\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        // set the break in the middle of a page.
+        sbrk((10 * 4096 + 2048) - (uint64)sbrk(0));
+
+        // reduce the break a bit, but not enough to
+        // cause a page to be freed. this used to cause
+        // a panic.
+        sbrk(-10);
+
+        exit(0);
+    }
+    wait(0);
+
+    printf("sbrkbugs test OK\n");
+}
+
+// cow tests
+// allocate more than half of physical memory,
+// then fork. this will fail in the default
+// kernel, which does not support copy-on-write.
+#define PHYSTOP (0x80000000L + 130 * 1024 * 1024)
+#define START_MEM 0x80a00000
+void simpletest() {
+    printf("==========simpletest test=========\n");
+    uint64 phys_size = PHYSTOP - START_MEM;
+    int sz = (phys_size / 3) * 2;
+
+    printf("simple: ");
+
+    char *p = sbrk(sz);
+    if (p == (char *)0xffffffffffffffffL) {
+        printf("sbrk(%d) failed\n", sz);
+        exit(-1);
+    }
+
+    for (char *q = p; q < p + sz; q += 4096) {
+        *(int *)q = getpid();
+    }
+
+    int pid = fork();
+    if (pid < 0) {
+        printf("fork() failed\n");
+        exit(-1);
+    }
+
+    if (pid == 0)
+        exit(0);
+
+    wait(0);
+
+    if (sbrk(-sz) == (char *)0xffffffffffffffffL) {
+        printf("sbrk(-%d) failed\n", sz);
+        exit(-1);
+    }
+
+    printf("simpletest test OK\n");
+}
+
+// three processes all write COW memory.
+// this causes more than half of physical memory
+// to be allocated, so it also checks whether
+// copied pages are freed.
+void threetest() {
+    printf("==========threetest test=========\n");
+    uint64 phys_size = PHYSTOP - START_MEM;
+    int sz = phys_size / 4;
+    int pid1, pid2;
+
+    printf("three: ");
+
+    char *p = sbrk(sz);
+    if (p == (char *)0xffffffffffffffffL) {
+        printf("sbrk(%d) failed\n", sz);
+        exit(-1);
+    }
+
+    pid1 = fork();
+    if (pid1 < 0) {
+        printf("fork failed\n");
+        exit(-1);
+    }
+    if (pid1 == 0) {
+        pid2 = fork();
+        if (pid2 < 0) {
+            printf("fork failed");
+            exit(-1);
+        }
+        if (pid2 == 0) {
+            for (char *q = p; q < p + (sz / 5) * 4; q += 4096) {
+                *(int *)q = getpid();
+            }
+            for (char *q = p; q < p + (sz / 5) * 4; q += 4096) {
+                if (*(int *)q != getpid()) {
+                    printf("wrong content\n");
+                    exit(-1);
+                }
+            }
+            exit(-1);
+        }
+        for (char *q = p; q < p + (sz / 2); q += 4096) {
+            *(int *)q = 9999;
+        }
+        exit(0);
+    }
+
+    for (char *q = p; q < p + sz; q += 4096) {
+        *(int *)q = getpid();
+    }
+
+    wait(0);
+
+    sleep(1);
+
+    for (char *q = p; q < p + sz; q += 4096) {
+        if (*(int *)q != getpid()) {
+            printf("wrong content\n");
+            exit(-1);
+        }
+    }
+
+    if (sbrk(-sz) == (char *)0xffffffffffffffffL) {
+        printf("sbrk(-%d) failed\n", sz);
+        exit(-1);
+    }
+
+    printf("threetest test OK\n");
+}
+
+char buf2[4096];
+int fds[2];
+
+// test whether copyout() simulates COW faults.
+void filetest() {
+    printf("==========filetest test==========\n");
+
+    buf2[0] = 99;
+
+    for (int i = 0; i < 4; i++) {
+        if (pipe(fds) != 0) {
+            printf("pipe() failed\n");
+            exit(-1);
+        }
+        int pid = fork();
+        if (pid < 0) {
+            printf("fork failed\n");
+            exit(-1);
+        }
+        if (pid == 0) {
+            sleep(1);
+            if (read(fds[0], buf2, sizeof(i)) != sizeof(i)) {
+                printf("error: read failed\n");
+                exit(1);
+            }
+            sleep(1);
+            int j = *(int *)buf2;
+            if (j != i) {
+                printf("error: read the wrong value\n");
+                exit(1);
+            }
+            exit(0);
+        }
+        if (write(fds[1], &i, sizeof(i)) != sizeof(i)) {
+            printf("error: write failed\n");
+            exit(-1);
+        }
+    }
+
+    int xstatus = 0;
+    for (int i = 0; i < 4; i++) {
+        wait(&xstatus);
+        if (xstatus != 0) {
+            exit(1);
+        }
+    }
+
+    if (buf2[0] != 99) {
+        printf("error: child overwrote parent\n");
+        exit(1);
+    }
+
+    printf("filetest test OK\n");
+}
+
+void cowtest() {
+    simpletest();
+
+    // check that the first simpletest() freed the physical memory.
+    simpletest();
+
+    threetest();
+    threetest();
+    threetest();
+
+    filetest();
+
+    printf("ALL COW TESTS PASSED\n");
+}
+
+
+void stressfs() {
+    int fd, i;
+    char path[] = "stressfs0";
+    char data[512];
+
+    printf("==========stressfs test==========\n");
+    memset(data, 'a', sizeof(data));
+
+    for (i = 0; i < 4; i++)
+        if (fork() > 0)
+            break;
+
+    printf("write %d\n", i);
+
+    path[8] += i;
+    fd = open(path, O_CREATE | O_RDWR);
+    for (i = 0; i < 20; i++)
+        //    printf(fd, "%d\n", i);
+        write(fd, data, sizeof(data));
+    close(fd);
+
+    printf("read\n");
+
+    fd = open(path, O_RDONLY);
+    for (i = 0; i < 20; i++)
+        read(fd, data, sizeof(data));
+    close(fd);
+
+    wait(0);
+    printf("stressfs test OK\n");
+}
+
+
+void copyinstr3() {
+    printf("==========copyinstr3 test==========\n");
+    sbrk(8192);
+    uint64 top = (uint64)sbrk(0);
+    if ((top % PGSIZE) != 0) {
+        sbrk(PGSIZE - (top % PGSIZE));
+    }
+    top = (uint64)sbrk(0);
+    if (top % PGSIZE) {
+        printf("oops\n");
+        exit(1);
+    }
+
+    char *b = (char *)(top - 1);
+    *b = 'x';
+
+    int ret = unlink(b);
+    if (ret != -1) {
+        printf("unlink(%s) returned %d, not -1\n", b, ret);
+        exit(1);
+    }
+
+    int fd = open(b, O_CREATE | O_WRONLY);
+    if (fd != -1) {
+        printf("open(%s) returned %d, not -1\n", b, fd);
+        exit(1);
+    }
+
+    char *args[] = {"xx", 0};
+    ret = execve(b, args, NULL);
+    if (ret != -1) {
+        printf("execve(%s) returned %d, not -1\n", b, fd);
+        exit(1);
+    }
+    printf("copyinstr3 test OK\n");
+}
+
+
+void subdir() {
+    int fd, cc;
+    printf("==========subdir test==========\n");
+    unlink("ff");
+    if (mkdir("dd", 0666) != 0) {
+        printf("mkdir dd failed\n");
+        exit(1);
+    }
+
+    fd = open("dd/ff", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("create dd/ff failed\n");
+        exit(1);
+    }
+    write(fd, "ff", 2);
+    close(fd);
+
+    if (unlink("dd") >= 0) {
+        printf("unlink dd (non-empty dir) succeeded!\n");
+        exit(1);
+    }
+
+    if (mkdir("/dd/dd", 0666) != 0) {
+        printf("subdir mkdir dd/dd failed\n");
+        exit(1);
+    }
+
+    fd = open("dd/dd/ff", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("create dd/dd/ff failed\n");
+        exit(1);
+    }
+    write(fd, "FF", 2);
+    close(fd);
+
+    fd = open("dd/dd/../ff", 0);
+    if (fd < 0) {
+        printf("open dd/dd/../ff failed\n");
+        exit(1);
+    }
+    cc = read(fd, buf, sizeof(buf));
+    printf("%s", buf);
+    if (cc != 2 || buf[0] != 'f') {
+        printf("dd/dd/../ff wrong content\n");
+        exit(1);
+    }
+    close(fd);
+
+    if (unlink("dd/dd/ff") != 0) {
+        printf("unlink dd/dd/ff failed\n");
+        exit(1);
+    }
+    if (open("dd/dd/ff", O_RDONLY) >= 0) {
+        printf("open (unlinked) dd/dd/ff succeeded\n");
+        exit(1);
+    }
+
+    if (chdir("dd") != 0) {
+        printf("chdir dd failed\n");
+        exit(1);
+    }
+    if (chdir("dd/../../dd") != 0) {
+        printf("chdir dd/../../dd failed\n");
+        exit(1);
+    }
+    if (chdir("dd/../../../dd") != 0) {
+        printf("chdir dd/../../dd failed\n");
+        exit(1);
+    }
+    if (chdir("./..") != 0) {
+        printf("chdir ./.. failed\n");
+        exit(1);
+    }
+
+
+    if (open("dd/dd/ff", O_RDONLY) >= 0) {
+        printf("open (unlinked) dd/dd/ff succeeded!\n");
+        exit(1);
+    }
+
+    if (open("dd/ff/ff", O_CREATE | O_RDWR) >= 0) {
+        printf("create dd/ff/ff succeeded!\n");
+        exit(1);
+    }
+    if (open("dd/xx/ff", O_CREATE | O_RDWR) >= 0) {
+        printf("create dd/xx/ff succeeded!\n");
+        exit(1);
+    }
+    if (open("dd", O_CREATE) >= 0) {
+        printf("create dd succeeded!\n");
+        exit(1);
+    }
+    if (open("dd", O_RDWR) >= 0) {
+        printf("open dd rdwr succeeded!\n");
+        exit(1);
+    }
+
+    if (open("dd", O_WRONLY) >= 0) {
+        printf("open dd wronly succeeded!\n");
+        exit(1);
+    }
+
+    // if (mkdir("dd/ff/ff", 066) == 0) {
+    //     printf("mkdir dd/ff/ff succeeded!\n");
+    //     exit(1);
+    // }
+    // if (mkdir("dd/xx/ff", 066) == 0) {
+    //     printf("mkdir dd/xx/ff succeeded!\n");
+    //     exit(1);
+    // }
+    // if (mkdir("dd/dd/ffff", 0666) == 0) {
+    //     printf("mkdir dd/dd/ffff succeeded!\n");
+    //     exit(1);
+    // }
+    // if (unlink("dd/xx/ff") == 0) {
+    //     printf("unlink dd/xx/ff succeeded!\n");
+    //     exit(1);
+    // }
+    // if (unlink("dd/ff/ff") == 0) {
+    //     printf("unlink dd/ff/ff succeeded!\n");
+    //     exit(1);
+    // }
+    // if (chdir("dd/ff") == 0) {
+    //     printf("chdir dd/ff succeeded!\n");
+    //     exit(1);
+    // }
+    // if (chdir("dd/xx") == 0) {
+    //     printf("chdir dd/xx succeeded!\n");
+    //     exit(1);
+    // }
+
+    // if (unlink("dd/dd/ffff") != 0) {
+    //     printf("unlink dd/dd/ff failed\n");
+    //     exit(1);
+    // }
+    // if (unlink("dd/ff") != 0) {
+    //     printf("unlink dd/ff failed\n");
+    //     exit(1);
+    // }
+    // if (unlink("dd") == 0) {
+    //     printf("unlink non-empty dd succeeded!\n");
+    //     exit(1);
+    // }
+    // if (unlink("dd/dd") < 0) {
+    //     printf("unlink dd/dd failed\n");
+    //     exit(1);
+    // }
+    // if (unlink("dd") < 0) {
+    //     printf("unlink dd failed\n");
+    //     exit(1);
+    // }
+    printf("subdir test OK\n");
+}
+
+void dirfile() {
+    printf("==========dirfile test==========\n");
+    int fd;
+
+    fd = open("dirfile", O_CREATE);
+    if (fd < 0) {
+        printf("create dirfile failed\n");
+        exit(1);
+    }
+    close(fd);
+    if (chdir("dirfile") == 0) {
+        printf("chdir dirfile succeeded!\n");
+        exit(1);
+    }
+    fd = open("dirfile/xx", 0);
+    if (fd >= 0) {
+        printf("create dirfile/xx succeeded!\n");
+        exit(1);
+    }
+    fd = open("dirfile/xx", O_CREATE);
+    if (fd >= 0) {
+        printf("create dirfile/xx succeeded!\n");
+        exit(1);
+    }
+    if (mkdir("dirfile/xx", 0666) == 0) {
+        printf("mkdir dirfile/xx succeeded!\n");
+        exit(1);
+    }
+    if (unlink("dirfile/xx") == 0) {
+        printf("unlink dirfile/xx succeeded!\n");
+        exit(1);
+    }
+
+    if (unlink("dirfile") != 0) {
+        printf("unlink dirfile failed!\n");
+        exit(1);
+    }
+
+    close(fd);
+    printf("dirfile test OK\n");
+}
+
+
+int main(void) {
+    forktest();
+    exitwait();
+    forkfork();
+    forkforkfork();
+    twochildren();
+    reparent();
+    reparent2();
+    killstatus();
+    
+    
+    opentest();
+    openiputtest();
+    writetest();
+    writebig();
+    preempt();
+    truncate1();
+    copyin();
+    copyout();
+    copyinstr1();
+    truncate2();
+    truncate3();
+    iputtest();
+    exitiputtest();
+    createtest();
+
+    sbrklast();
+    dirtest();
+    execvetest();
+    uvmfree();
+    pipe1();
+    mem();
+    sharedfd();
+    createdelete();
+    fourfiles();
+    bigwrite();
+    bigfile();
+    rmdot();
+    badarg();
+    sbrk8000();
+    textwrite();
+    outofinodes();
+    manywrites();
+    badwrite();
+    sbrkbasic();
+    sbrkmuch();
+    kernmem();
+    MAXVAplus();
+    sbrkfail();
+    sbrkarg();
+    bsstest();
+    bigargtest();
+    argptest();
+    stacktest();
+    pgbug();
+    sbrkbugs();
+    cowtest();
+    copyinstr3();
+    stressfs();
+
+
+    // TODO :
+    // fsfull();
+    // diskfull();
+    // execveout();
+    // fourteen();
+    // unlinkread();
+    // subdir();
+    // dirfile();
+
+    printf("ALL TESTS PASSED\n");
+    exit(0);
+    return 0;
+}
diff --git a/xv6_user/src/usertests.c b/xv6_user/src/usertests.c
new file mode 100644
index 0000000000000000000000000000000000000000..e64760f07ff6d15b15b66df8ff5e87ddb47959cb
--- /dev/null
+++ b/xv6_user/src/usertests.c
@@ -0,0 +1,711 @@
+#define USER
+#include "param.h"
+#include "types.h"
+#include "fs/stat.h"
+#include "user.h"
+
+#include "fs/fcntl.h"
+#include "syscall_gen/syscall_num.h"
+#include "memory/memlayout.h"
+#include "riscv.h"
+#include "memory/buddy.h"
+
+//
+// Tests xv6 system calls.  usertests without arguments runs them all
+// and usertests <name> runs <name> test. The test runner creates for
+// each test a process and based on the exit status of the process,
+// the test runner reports "OK" or "FAILED".  Some tests result in
+// kernel printing usertrap messages, which can be ignored if test
+// prints "OK".
+//
+
+#define BUFSZ ((MAXOPBLOCKS + 2) * BSIZE)
+
+char buf[BUFSZ];
+
+
+char junk1[4096];
+int fds[2];
+char junk2[4096];
+char buf2[4096];
+char junk3[4096];
+
+
+
+
+//
+// Section with tests that run fairly quickly.  Use -q if you want to
+// run just those.  With -q usertests also runs the ones that take a
+// fair of time.
+//
+
+// what if a string argument crosses over the end of last user page?
+
+
+
+
+
+
+// does the error path in open() for attempt to write a
+// directory call iput() in a transaction?
+// needs a hacked kernel that pauses just after the namei()
+// call in sys_open():
+//    if((ip = namei(path)) == 0)
+//      return -1;
+//    {
+//      int i;
+//      for(i = 0; i < 10000; i++)
+//        yield();
+//    }
+
+
+// More file system tests
+
+void linktest(char *s) {
+    enum { SZ = 5 };
+    int fd;
+
+    unlink("lf1");
+    unlink("lf2");
+
+    fd = open("lf1", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("%s: create lf1 failed\n", s);
+        exit(1);
+    }
+    if (write(fd, "hello", SZ) != SZ) {
+        printf("%s: write lf1 failed\n", s);
+        exit(1);
+    }
+    close(fd);
+
+    if (link("lf1", "lf2") < 0) {
+        printf("%s: link lf1 lf2 failed\n", s);
+        exit(1);
+    }
+    unlink("lf1");
+
+    if (open("lf1", 0) >= 0) {
+        printf("%s: unlinked lf1 but it is still there!\n", s);
+        exit(1);
+    }
+
+    fd = open("lf2", 0);
+    if (fd < 0) {
+        printf("%s: open lf2 failed\n", s);
+        exit(1);
+    }
+    if (read(fd, buf, sizeof(buf)) != SZ) {
+        printf("%s: read lf2 failed\n", s);
+        exit(1);
+    }
+    close(fd);
+
+    if (link("lf2", "lf2") >= 0) {
+        printf("%s: link lf2 lf2 succeeded! oops\n", s);
+        exit(1);
+    }
+
+    unlink("lf2");
+    if (link("lf2", "lf1") >= 0) {
+        printf("%s: link non-existent succeeded! oops\n", s);
+        exit(1);
+    }
+
+    if (link(".", "lf1") >= 0) {
+        printf("%s: link . lf1 succeeded! oops\n", s);
+        exit(1);
+    }
+}
+
+// test concurrent create/link/unlink of the same file
+void concreate(char *s) {
+    enum { N = 40 };
+    char file[3];
+    int i, pid, n, fd;
+    char fa[N];
+    struct {
+        ushort inum;
+        char name[DIRSIZ];
+    } de;
+
+    file[0] = 'C';
+    file[2] = '\0';
+    for (i = 0; i < N; i++) {
+        file[1] = '0' + i;
+        unlink(file);
+        pid = fork();
+        if (pid && (i % 3) == 1) {
+            link("C0", file);
+        } else if (pid == 0 && (i % 5) == 1) {
+            link("C0", file);
+        } else {
+            fd = open(file, O_CREATE | O_RDWR);
+            if (fd < 0) {
+                printf("concreate create %s failed\n", file);
+                exit(1);
+            }
+            close(fd);
+        }
+        if (pid == 0) {
+            exit(0);
+        } else {
+            int xstatus;
+            wait(&xstatus);
+            if (xstatus != 0)
+                exit(1);
+        }
+    }
+
+    memset(fa, 0, sizeof(fa));
+    fd = open(".", 0);
+    n = 0;
+    while (read(fd, &de, sizeof(de)) > 0) {
+        if (de.inum == 0)
+            continue;
+        if (de.name[0] == 'C' && de.name[2] == '\0') {
+            i = de.name[1] - '0';
+            if (i < 0 || i >= sizeof(fa)) {
+                printf("%s: concreate weird file %s\n", s, de.name);
+                exit(1);
+            }
+            if (fa[i]) {
+                printf("%s: concreate duplicate file %s\n", s, de.name);
+                exit(1);
+            }
+            fa[i] = 1;
+            n++;
+        }
+    }
+    close(fd);
+
+    if (n != N) {
+        printf("%s: concreate not enough files in directory listing\n", s);
+        exit(1);
+    }
+
+    for (i = 0; i < N; i++) {
+        file[1] = '0' + i;
+        pid = fork();
+        if (pid < 0) {
+            printf("%s: fork failed\n", s);
+            exit(1);
+        }
+        if (((i % 3) == 0 && pid == 0) || ((i % 3) == 1 && pid != 0)) {
+            close(open(file, 0));
+            close(open(file, 0));
+            close(open(file, 0));
+            close(open(file, 0));
+            close(open(file, 0));
+            close(open(file, 0));
+        } else {
+            unlink(file);
+            unlink(file);
+            unlink(file);
+            unlink(file);
+            unlink(file);
+            unlink(file);
+        }
+        if (pid == 0)
+            exit(0);
+        else
+            wait(0);
+    }
+}
+
+// another concurrent link/unlink/create test,
+// to look for deadlocks.
+void linkunlink(char *s) {
+    int pid, i;
+
+    unlink("x");
+    pid = fork();
+    if (pid < 0) {
+        printf("%s: fork failed\n", s);
+        exit(1);
+    }
+
+    unsigned int x = (pid ? 1 : 97);
+    for (i = 0; i < 100; i++) {
+        x = x * 1103515245 + 12345;
+        if ((x % 3) == 0) {
+            close(open("x", O_RDWR | O_CREATE));
+        } else if ((x % 3) == 1) {
+            link("cat", "x");
+        } else {
+            unlink("x");
+        }
+    }
+
+    if (pid)
+        wait(0);
+    else
+        exit(0);
+}
+
+void subdir(char *s) {
+    int fd, cc;
+
+    unlink("ff");
+    if (mkdir("dd") != 0) {
+        printf("%s: mkdir dd failed\n", s);
+        exit(1);
+    }
+
+    fd = open("dd/ff", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("%s: create dd/ff failed\n", s);
+        exit(1);
+    }
+    write(fd, "ff", 2);
+    close(fd);
+
+    if (unlink("dd") >= 0) {
+        printf("%s: unlink dd (non-empty dir) succeeded!\n", s);
+        exit(1);
+    }
+
+    if (mkdir("/dd/dd") != 0) {
+        printf("subdir mkdir dd/dd failed\n", s);
+        exit(1);
+    }
+
+    fd = open("dd/dd/ff", O_CREATE | O_RDWR);
+    if (fd < 0) {
+        printf("%s: create dd/dd/ff failed\n", s);
+        exit(1);
+    }
+    write(fd, "FF", 2);
+    close(fd);
+
+    fd = open("dd/dd/../ff", 0);
+    if (fd < 0) {
+        printf("%s: open dd/dd/../ff failed\n", s);
+        exit(1);
+    }
+    cc = read(fd, buf, sizeof(buf));
+    if (cc != 2 || buf[0] != 'f') {
+        printf("%s: dd/dd/../ff wrong content\n", s);
+        exit(1);
+    }
+    close(fd);
+
+    if (link("dd/dd/ff", "dd/dd/ffff") != 0) {
+        printf("link dd/dd/ff dd/dd/ffff failed\n", s);
+        exit(1);
+    }
+
+    if (unlink("dd/dd/ff") != 0) {
+        printf("%s: unlink dd/dd/ff failed\n", s);
+        exit(1);
+    }
+    if (open("dd/dd/ff", O_RDONLY) >= 0) {
+        printf("%s: open (unlinked) dd/dd/ff succeeded\n", s);
+        exit(1);
+    }
+
+    if (chdir("dd") != 0) {
+        printf("%s: chdir dd failed\n", s);
+        exit(1);
+    }
+    if (chdir("dd/../../dd") != 0) {
+        printf("%s: chdir dd/../../dd failed\n", s);
+        exit(1);
+    }
+    if (chdir("dd/../../../dd") != 0) {
+        printf("chdir dd/../../dd failed\n", s);
+        exit(1);
+    }
+    if (chdir("./..") != 0) {
+        printf("%s: chdir ./.. failed\n", s);
+        exit(1);
+    }
+
+    fd = open("dd/dd/ffff", 0);
+    if (fd < 0) {
+        printf("%s: open dd/dd/ffff failed\n", s);
+        exit(1);
+    }
+    if (read(fd, buf, sizeof(buf)) != 2) {
+        printf("%s: read dd/dd/ffff wrong len\n", s);
+        exit(1);
+    }
+    close(fd);
+
+    if (open("dd/dd/ff", O_RDONLY) >= 0) {
+        printf("%s: open (unlinked) dd/dd/ff succeeded!\n", s);
+        exit(1);
+    }
+
+    if (open("dd/ff/ff", O_CREATE | O_RDWR) >= 0) {
+        printf("%s: create dd/ff/ff succeeded!\n", s);
+        exit(1);
+    }
+    if (open("dd/xx/ff", O_CREATE | O_RDWR) >= 0) {
+        printf("%s: create dd/xx/ff succeeded!\n", s);
+        exit(1);
+    }
+    if (open("dd", O_CREATE) >= 0) {
+        printf("%s: create dd succeeded!\n", s);
+        exit(1);
+    }
+    if (open("dd", O_RDWR) >= 0) {
+        printf("%s: open dd rdwr succeeded!\n", s);
+        exit(1);
+    }
+    if (open("dd", O_WRONLY) >= 0) {
+        printf("%s: open dd wronly succeeded!\n", s);
+        exit(1);
+    }
+    if (link("dd/ff/ff", "dd/dd/xx") == 0) {
+        printf("%s: link dd/ff/ff dd/dd/xx succeeded!\n", s);
+        exit(1);
+    }
+    if (link("dd/xx/ff", "dd/dd/xx") == 0) {
+        printf("%s: link dd/xx/ff dd/dd/xx succeeded!\n", s);
+        exit(1);
+    }
+    if (link("dd/ff", "dd/dd/ffff") == 0) {
+        printf("%s: link dd/ff dd/dd/ffff succeeded!\n", s);
+        exit(1);
+    }
+    if (mkdir("dd/ff/ff") == 0) {
+        printf("%s: mkdir dd/ff/ff succeeded!\n", s);
+        exit(1);
+    }
+    if (mkdir("dd/xx/ff") == 0) {
+        printf("%s: mkdir dd/xx/ff succeeded!\n", s);
+        exit(1);
+    }
+    if (mkdir("dd/dd/ffff") == 0) {
+        printf("%s: mkdir dd/dd/ffff succeeded!\n", s);
+        exit(1);
+    }
+    if (unlink("dd/xx/ff") == 0) {
+        printf("%s: unlink dd/xx/ff succeeded!\n", s);
+        exit(1);
+    }
+    if (unlink("dd/ff/ff") == 0) {
+        printf("%s: unlink dd/ff/ff succeeded!\n", s);
+        exit(1);
+    }
+    if (chdir("dd/ff") == 0) {
+        printf("%s: chdir dd/ff succeeded!\n", s);
+        exit(1);
+    }
+    if (chdir("dd/xx") == 0) {
+        printf("%s: chdir dd/xx succeeded!\n", s);
+        exit(1);
+    }
+
+    if (unlink("dd/dd/ffff") != 0) {
+        printf("%s: unlink dd/dd/ff failed\n", s);
+        exit(1);
+    }
+    if (unlink("dd/ff") != 0) {
+        printf("%s: unlink dd/ff failed\n", s);
+        exit(1);
+    }
+    if (unlink("dd") == 0) {
+        printf("%s: unlink non-empty dd succeeded!\n", s);
+        exit(1);
+    }
+    if (unlink("dd/dd") < 0) {
+        printf("%s: unlink dd/dd failed\n", s);
+        exit(1);
+    }
+    if (unlink("dd") < 0) {
+        printf("%s: unlink dd failed\n", s);
+        exit(1);
+    }
+}
+
+
+// test that iput() is called at the end of _namei().
+// also tests empty file names.
+void iref(char *s) {
+    int i, fd;
+
+    for (i = 0; i < NINODE + 1; i++) {
+        if (mkdir("irefd") != 0) {
+            printf("%s: mkdir irefd failed\n", s);
+            exit(1);
+        }
+        if (chdir("irefd") != 0) {
+            printf("%s: chdir irefd failed\n", s);
+            exit(1);
+        }
+
+        mkdir("");
+        link("README.md", "");
+        fd = open("", O_CREATE);
+        if (fd >= 0)
+            close(fd);
+        fd = open("xx", O_CREATE);
+        if (fd >= 0)
+            close(fd);
+        unlink("xx");
+    }
+
+    // clean up
+    for (i = 0; i < NINODE + 1; i++) {
+        chdir("..");
+        unlink("irefd");
+    }
+
+    chdir("/");
+}
+
+void validatetest(char *s) {
+    int hi;
+    uint64 p;
+
+    hi = 1100 * 1024;
+    for (p = 0; p <= (uint)hi; p += PGSIZE) {
+        // try to crash the kernel by passing in a bad string pointer
+        if (link("nosuchfile", (char *)p) != -1) {
+            printf("%s: link should not succeed\n", s);
+            exit(1);
+        }
+    }
+}
+
+
+struct test {
+    void (*f)(char *);
+    char *s;
+} quicktests[] = {
+    {cowtest, "cowtest"},
+    {copyin, "copyin"},
+    {copyout, "copyout"},
+    {copyinstr1, "copyinstr1"},
+    {copyinstr2, "copyinstr2"},
+    {copyinstr3, "copyinstr3"},
+    {rwsbrk, "rwsbrk"},
+    {truncate1, "truncate1"},
+    {truncate2, "truncate2"},
+    {truncate3, "truncate3"},
+    {openiputtest, "openiput"},
+    {exitiputtest, "exitiput"},
+    {iputtest, "iput"},
+    {opentest, "opentest"},
+    {writetest, "writetest"},
+    {writebig, "writebig"},
+    {createtest, "createtest"},
+    {dirtest, "dirtest"},
+    {exectest, "exectest"},
+    {pipe1, "pipe1"},
+    {killstatus, "killstatus"},
+    {preempt, "preempt"},
+    {exitwait, "exitwait"},
+    {reparent, "reparent"},
+    {twochildren, "twochildren"},
+    {forkfork, "forkfork"},
+    {forkforkfork, "forkforkfork"},
+    {reparent2, "reparent2"},
+    {mem, "mem"},
+    {sharedfd, "sharedfd"},
+    {fourfiles, "fourfiles"},
+    {createdelete, "createdelete"},
+    {unlinkread, "unlinkread"},
+    {linktest, "linktest"},
+    {concreate, "concreate"},
+    {linkunlink, "linkunlink"},
+    {subdir, "subdir"},
+    {bigwrite, "bigwrite"},
+    {bigfile, "bigfile"},
+    {fourteen, "fourteen"},
+    {rmdot, "rmdot"},
+    {dirfile, "dirfile"},
+    {iref, "iref"},
+    {forktest, "forktest"},
+    {sbrkbasic, "sbrkbasic"},
+    {sbrkmuch, "sbrkmuch"},
+    {kernmem, "kernmem"},
+    {MAXVAplus, "MAXVAplus"},
+    {sbrkfail, "sbrkfail"},
+    {sbrkarg, "sbrkarg"},
+    {validatetest, "validatetest"},
+    {bsstest, "bsstest"},
+    {bigargtest, "bigargtest"},
+    {argptest, "argptest"},
+    {stacktest, "stacktest"},
+    {textwrite, "textwrite"},
+    {pgbug, "pgbug"},
+    {sbrkbugs, "sbrkbugs"},
+    {sbrklast, "sbrklast"},
+    {sbrk8000, "sbrk8000"},
+    {badarg, "badarg"},
+    {uvmfree, "uvmfree"},
+
+    {0, 0},
+};
+
+struct test slowtests[] = {
+    {bigdir, "bigdir"},
+    {manywrites, "manywrites"},
+    {badwrite, "badwrite"},
+    {execout, "execout"},
+    {diskfull, "diskfull"},
+    {outofinodes, "outofinodes"},
+
+    {0, 0},
+};
+
+//
+// drive tests
+//
+
+// run each test in its own process. run returns 1 if child's exit()
+// indicates success.
+int run(void f(char *), char *s) {
+    int pid;
+    int xstatus;
+
+    printf("test %s: ", s);
+    if ((pid = fork()) < 0) {
+        printf("runtest: fork error\n");
+        exit(1);
+    }
+    if (pid == 0) {
+        f(s);
+        exit(0);
+    } else {
+        wait(&xstatus);
+        if (xstatus != 0)
+            printf("FAILED\n");
+        else
+            printf("OK\n");
+        return xstatus == 0;
+    }
+}
+
+int runtests(struct test *tests, char *justone) {
+    for (struct test *t = tests; t->s != 0; t++) {
+        if ((justone == 0) || strcmp(t->s, justone) == 0) {
+            if (!run(t->f, t->s)) {
+                printf("SOME TESTS FAILED\n");
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+//
+// use sbrk() to count how many free physical memory pages there are.
+// touches the pages to force allocation.
+// because out of memory with lazy allocation results in the process
+// taking a fault and being killed, fork and report back.
+//
+int countfree() {
+    int fds[2];
+
+    if (pipe(fds) < 0) {
+        printf("pipe() failed in countfree()\n");
+        exit(1);
+    }
+
+    int pid = fork();
+
+    if (pid < 0) {
+        printf("fork failed in countfree()\n");
+        exit(1);
+    }
+
+    if (pid == 0) {
+        close(fds[0]);
+
+        while (1) {
+            uint64 a = (uint64)sbrk(4096);
+            if (a == 0xffffffffffffffff) {
+                break;
+            }
+
+            // modify the memory to make sure it's really allocated.
+            *(char *)(a + 4096 - 1) = 1;
+
+            // report back one more page.
+            if (write(fds[1], "x", 1) != 1) {
+                printf("write() failed in countfree()\n");
+                exit(1);
+            }
+        }
+
+        exit(0);
+    }
+
+    close(fds[1]);
+
+    int n = 0;
+    while (1) {
+        char c;
+        int cc = read(fds[0], &c, 1);
+        if (cc < 0) {
+            printf("read() failed in countfree()\n");
+            exit(1);
+        }
+        if (cc == 0)
+            break;
+        n += 1;
+    }
+
+    close(fds[0]);
+    wait((int *)0);
+
+    return n;
+}
+
+int drivetests(int quick, int continuous, char *justone) {
+    do {
+        printf("usertests starting\n");
+        int free0 = countfree();
+        int free1 = 0;
+        if (runtests(quicktests, justone)) {
+            if (continuous != 2) {
+                return 1;
+            }
+        }
+        if (!quick) {
+            if (justone == 0)
+                printf("usertests slow tests starting\n");
+            if (runtests(slowtests, justone)) {
+                if (continuous != 2) {
+                    return 1;
+                }
+            }
+        }
+        if ((free1 = countfree()) < free0) {
+            printf("FAILED -- lost some free pages %d (out of %d)\n", free1, free0);
+            if (continuous != 2) {
+                return 1;
+            }
+        }
+    } while (continuous);
+    return 0;
+}
+
+int main(int argc, char *argv[]) {
+    int continuous = 0;
+    int quick = 0;
+    char *justone = 0;
+
+    // print_pgtable();
+    if (argc == 2 && strcmp(argv[1], "-q") == 0) {
+        quick = 1;
+    } else if (argc == 2 && strcmp(argv[1], "-c") == 0) {
+        continuous = 1;
+    } else if (argc == 2 && strcmp(argv[1], "-C") == 0) {
+        continuous = 2;
+    } else if (argc == 2 && argv[1][0] != '-') {
+        justone = argv[1];
+    } else if (argc > 1) {
+        printf("Usage: usertests [-c] [-C] [-q] [testname]\n");
+        exit(1);
+    }
+    if (drivetests(quick, continuous, justone)) {
+        exit(1);
+    }
+    printf("ALL TESTS PASSED\n");
+    exit(0);
+}
diff --git a/xv6_user/src/wc.c b/xv6_user/src/wc.c
new file mode 100644
index 0000000000000000000000000000000000000000..a568142e0d8ed8f8d93776465d47cf8627dfc94b
--- /dev/null
+++ b/xv6_user/src/wc.c
@@ -0,0 +1,54 @@
+#define USER
+#include "stddef.h"
+#include "unistd.h"
+#include "stdio.h"
+#include "string.h"
+#include "stdlib.h"
+
+char buf[512];
+
+void wc(int fd, char *name) {
+    int i, n;
+    int l, w, c, inword;
+
+    l = w = c = 0;
+    inword = 0;
+    while ((n = read(fd, buf, sizeof(buf))) > 0) {
+        for (i = 0; i < n; i++) {
+            c++;
+            if (buf[i] == '\n')
+                l++;
+            if (strchr(" \r\t\n\v", buf[i]))
+                inword = 0;
+            else if (!inword) {
+                w++;
+                inword = 1;
+            }
+        }
+    }
+    if (n < 0) {
+        printf("wc: read error\n");
+        exit(1);
+    }
+    printf("%d %d %d %s\n", l, w, c, name);
+}
+
+int main(int argc, char *argv[]) {
+    int fd, i;
+
+    if (argc <= 1) {
+        wc(0, "");
+        exit(0);
+    }
+
+    for (i = 1; i < argc; i++) {
+        if ((fd = open(argv[i], 0)) < 0) {
+            printf("wc: cannot open %s\n", argv[i]);
+            exit(1);
+        }
+        wc(fd, argv[i]);
+        close(fd);
+    }
+    exit(0);
+    return 0;
+}