diff --git a/Makefile b/Makefile index 88130e1..03befd7 100644 --- a/Makefile +++ b/Makefile @@ -128,6 +128,7 @@ UPROGS=\ $U/_usertests\ $U/_wc\ $U/_zombie\ + $U/_cow\ fs.img: mkfs/mkfs README $(UPROGS) mkfs/mkfs fs.img README $(UPROGS) diff --git a/kernel/defs.h b/kernel/defs.h index 1b397fe..bd89af0 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -185,9 +185,9 @@ pagetable_t uvmcreate(void); void uvminit(pagetable_t, uchar *, uint); uint64 uvmalloc(pagetable_t, uint64, uint64); uint64 uvmdealloc(pagetable_t, uint64, uint64); -void uvmcopy(pagetable_t, pagetable_t, uint64); +int uvmcopy(pagetable_t, pagetable_t, uint64); void uvmfree(pagetable_t, uint64); -void mappages(pagetable_t, uint64, uint64, uint64, int); +int mappages(pagetable_t, uint64, uint64, uint64, int); void unmappages(pagetable_t, uint64, uint64, int); uint64 walkaddr(pagetable_t, uint64); int copyout(pagetable_t, uint64, char *, uint64); diff --git a/kernel/kernelvec.S b/kernel/kernelvec.S index 4f52688..e9b0ced 100644 --- a/kernel/kernelvec.S +++ b/kernel/kernelvec.S @@ -47,7 +47,7 @@ kernelvec: ld ra, 0(sp) ld sp, 8(sp) ld gp, 16(sp) - ld tp, 24(sp) + // not this, in case we moved CPUs: ld tp, 24(sp) ld t0, 32(sp) ld t1, 40(sp) ld t2, 48(sp) diff --git a/kernel/proc.c b/kernel/proc.c index c12d97e..8ea09b5 100644 --- a/kernel/proc.c +++ b/kernel/proc.c @@ -111,6 +111,30 @@ found: return p; } +// free a proc structure and the data hanging from it, +// including user pages. +// the proc lock must be held. +static void +freeproc(struct proc *p) +{ + if(p->kstack) + kfree(p->kstack); + p->kstack = 0; + if(p->tf) + kfree((void*)p->tf); + p->tf = 0; + if(p->pagetable) + proc_freepagetable(p->pagetable, p->sz); + p->pagetable = 0; + p->sz = 0; + p->pid = 0; + p->parent = 0; + p->name[0] = 0; + p->chan = 0; + p->killed = 0; + p->state = UNUSED; +} + // Create a page table for a given process, // with no users pages, but with trampoline pages. // Called both when creating a process, and @@ -147,7 +171,8 @@ proc_freepagetable(pagetable_t pagetable, uint64 sz) { unmappages(pagetable, TRAMPOLINE, PGSIZE, 0); unmappages(pagetable, TRAMPOLINE-PGSIZE, PGSIZE, 0); - uvmfree(pagetable, sz); + if(sz > 0) + uvmfree(pagetable, sz); } // a user program that calls exec("/init") @@ -226,7 +251,10 @@ fork(void) } // Copy user memory from parent to child. - uvmcopy(p->pagetable, np->pagetable, p->sz); + if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){ + freeproc(np); + return -1; + } np->sz = p->sz; np->parent = p; @@ -342,17 +370,7 @@ wait(void) if(np->state == ZOMBIE){ // Found one. pid = np->pid; - kfree(np->kstack); - np->kstack = 0; - kfree((void*)np->tf); - np->tf = 0; - proc_freepagetable(np->pagetable, np->sz); - np->pagetable = 0; - np->pid = 0; - np->parent = 0; - np->name[0] = 0; - np->killed = 0; - np->state = UNUSED; + freeproc(np); release(&np->lock); release(&p->lock); return pid; diff --git a/kernel/syscall.c b/kernel/syscall.c index 8e9d51c..a054da2 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -117,29 +117,29 @@ argstr(int n, char *buf, int max) return fetchstr(addr, buf, max); } -extern int sys_chdir(void); -extern int sys_close(void); -extern int sys_dup(void); -extern int sys_exec(void); -extern int sys_exit(void); -extern int sys_fork(void); -extern int sys_fstat(void); -extern int sys_getpid(void); -extern int sys_kill(void); -extern int sys_link(void); -extern int sys_mkdir(void); -extern int sys_mknod(void); -extern int sys_open(void); -extern int sys_pipe(void); -extern int sys_read(void); -extern int sys_sbrk(void); -extern int sys_sleep(void); -extern int sys_unlink(void); -extern int sys_wait(void); -extern int sys_write(void); -extern int sys_uptime(void); +extern uint64 sys_chdir(void); +extern uint64 sys_close(void); +extern uint64 sys_dup(void); +extern uint64 sys_exec(void); +extern uint64 sys_exit(void); +extern uint64 sys_fork(void); +extern uint64 sys_fstat(void); +extern uint64 sys_getpid(void); +extern uint64 sys_kill(void); +extern uint64 sys_link(void); +extern uint64 sys_mkdir(void); +extern uint64 sys_mknod(void); +extern uint64 sys_open(void); +extern uint64 sys_pipe(void); +extern uint64 sys_read(void); +extern uint64 sys_sbrk(void); +extern uint64 sys_sleep(void); +extern uint64 sys_unlink(void); +extern uint64 sys_wait(void); +extern uint64 sys_write(void); +extern uint64 sys_uptime(void); -static int (*syscalls[])(void) = { +static uint64 (*syscalls[])(void) = { [SYS_fork] sys_fork, [SYS_exit] sys_exit, [SYS_wait] sys_wait, diff --git a/kernel/sysfile.c b/kernel/sysfile.c index 33f37f2..533e097 100644 --- a/kernel/sysfile.c +++ b/kernel/sysfile.c @@ -52,7 +52,7 @@ fdalloc(struct file *f) return -1; } -int +uint64 sys_dup(void) { struct file *f; @@ -66,7 +66,7 @@ sys_dup(void) return fd; } -int +uint64 sys_read(void) { struct file *f; @@ -78,7 +78,7 @@ sys_read(void) return fileread(f, p, n); } -int +uint64 sys_write(void) { struct file *f; @@ -91,7 +91,7 @@ sys_write(void) return filewrite(f, p, n); } -int +uint64 sys_close(void) { int fd; @@ -104,7 +104,7 @@ sys_close(void) return 0; } -int +uint64 sys_fstat(void) { struct file *f; @@ -116,7 +116,7 @@ sys_fstat(void) } // Create the path new as a link to the same inode as old. -int +uint64 sys_link(void) { char name[DIRSIZ], new[MAXPATH], old[MAXPATH]; @@ -182,7 +182,7 @@ isdirempty(struct inode *dp) } //PAGEBREAK! -int +uint64 sys_unlink(void) { struct inode *ip, *dp; @@ -284,7 +284,7 @@ create(char *path, short type, short major, short minor) return ip; } -int +uint64 sys_open(void) { char path[MAXPATH]; @@ -347,7 +347,7 @@ sys_open(void) return fd; } -int +uint64 sys_mkdir(void) { char path[MAXPATH]; @@ -363,7 +363,7 @@ sys_mkdir(void) return 0; } -int +uint64 sys_mknod(void) { struct inode *ip; @@ -383,7 +383,7 @@ sys_mknod(void) return 0; } -int +uint64 sys_chdir(void) { char path[MAXPATH]; @@ -408,7 +408,7 @@ sys_chdir(void) return 0; } -int +uint64 sys_exec(void) { char path[MAXPATH], *argv[MAXARG]; @@ -446,7 +446,7 @@ sys_exec(void) return ret; } -int +uint64 sys_pipe(void) { uint64 fdarray; // user pointer to array of two integers diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 65dde26..face81a 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -7,32 +7,32 @@ #include "spinlock.h" #include "proc.h" -int +uint64 sys_exit(void) { exit(); return 0; // not reached } -int +uint64 sys_getpid(void) { return myproc()->pid; } -int +uint64 sys_fork(void) { return fork(); } -int +uint64 sys_wait(void) { return wait(); } -int +uint64 sys_sbrk(void) { int addr; @@ -46,7 +46,7 @@ sys_sbrk(void) return addr; } -int +uint64 sys_sleep(void) { int n; @@ -67,7 +67,7 @@ sys_sleep(void) return 0; } -int +uint64 sys_kill(void) { int pid; @@ -79,7 +79,7 @@ sys_kill(void) // return how many clock tick interrupts have occurred // since start. -int +uint64 sys_uptime(void) { uint xticks; diff --git a/kernel/trap.c b/kernel/trap.c index 6c0d04b..018b7db 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -37,7 +37,7 @@ void usertrap(void) { int which_dev = 0; - + if((r_sstatus() & SSTATUS_SPP) != 0) panic("usertrap: not from user mode"); @@ -49,8 +49,6 @@ usertrap(void) // save user program counter. p->tf->epc = r_sepc(); - - intr_on(); if(r_scause() == 8){ // system call @@ -59,11 +57,15 @@ usertrap(void) // but we want to return to the next instruction. p->tf->epc += 4; + // an interrupt will change sstatus &c registers, + // so don't enable until done with those registers. + intr_on(); + syscall(); } else if((which_dev = devintr()) != 0){ // ok } else { - printf("usertrap(): unexpected scause 0x%p pid=%d\n", r_scause(), p->pid); + printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); p->killed = 1; } @@ -121,12 +123,14 @@ usertrapret(void) ((void (*)(uint64,uint64))TRAMPOLINE)(TRAMPOLINE - PGSIZE, satp); } -// interrupts and exceptions from kernel code go here, +// interrupts and exceptions from kernel code go here via kernelvec, // on whatever the current kernel stack is. // must be 4-byte aligned to fit in stvec. void kerneltrap() { + int which_dev = 0; + uint64 sepc = r_sepc(); uint64 sstatus = r_sstatus(); uint64 scause = r_scause(); @@ -135,11 +139,20 @@ kerneltrap() if(intr_get() != 0) panic("kerneltrap: interrupts enabled"); - if(devintr() == 0){ - printf("scause 0x%p\n", scause); + if((which_dev = devintr()) == 0){ + printf("scause %p\n", scause); printf("sepc=%p stval=%p\n", r_sepc(), r_stval()); panic("kerneltrap"); } + + // give up the CPU if this is a timer interrupt. + if(which_dev == 2 && myproc() != 0 && myproc()->state == RUNNING) + yield(); + + // the yield() may have caused some traps to occur, + // so restore trap registers for use by kernelvec.S's sepc instruction. + w_sepc(sepc); + w_sstatus(sstatus); } // check if it's an external interrupt or software interrupt, diff --git a/kernel/vm.c b/kernel/vm.c index 580669f..bdb53c2 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -97,8 +97,8 @@ walk(pagetable_t pagetable, uint64 va, int alloc) } // Look up a virtual address, return the physical address, -// Can only be used to look up user pages. // or 0 if not mapped. +// Can only be used to look up user pages. uint64 walkaddr(pagetable_t pagetable, uint64 va) { @@ -119,8 +119,9 @@ walkaddr(pagetable_t pagetable, uint64 va) // Create PTEs for virtual addresses starting at va that refer to // physical addresses starting at pa. va and size might not -// be page-aligned. -void +// be page-aligned. Returns 0 on success, -1 if walk() couldn't +// allocate a needed page-table page. +int mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) { uint64 a, last; @@ -130,7 +131,7 @@ mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) last = PGROUNDDOWN(va + size - 1); for(;;){ if((pte = walk(pagetable, a, 1)) == 0) - panic("mappages: walk"); + return -1; if(*pte & PTE_V) panic("remap"); *pte = PA2PTE(pa) | perm | PTE_V; @@ -139,6 +140,7 @@ mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) a += PGSIZE; pa += PGSIZE; } + return 0; } // Remove mappings from a page table. The mappings in @@ -222,7 +224,11 @@ uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) return 0; } memset(mem, 0, PGSIZE); - mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U); + if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){ + kfree(mem); + uvmdealloc(pagetable, a, oldsz); + return 0; + } } return newsz; } @@ -273,7 +279,9 @@ uvmfree(pagetable_t pagetable, uint64 sz) // its memory into a child's page table. // Copies both the page table and the // physical memory. -void +// returns 0 on success, -1 on failure. +// frees any allocated pages on failure. +int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) { pte_t *pte; @@ -289,10 +297,18 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) pa = PTE2PA(*pte); flags = PTE_FLAGS(*pte); if((mem = kalloc()) == 0) - panic("uvmcopy: kalloc failed"); + goto err; memmove(mem, (char*)pa, PGSIZE); - mappages(new, i, PGSIZE, (uint64)mem, flags); + if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){ + kfree(mem); + goto err; + } } + return 0; + + err: + unmappages(new, 0, i, 1); + return -1; } // Copy from kernel to user. diff --git a/runoff b/runoff index be362d0..c6580ca 100755 --- a/runoff +++ b/runoff @@ -12,7 +12,10 @@ pad() # create formatted (numbered) files mkdir -p fmt -rm -f fmt/* +mkdir -p fmt/kernel +mkdir -p fmt/user +rm -f fmt/kernel/* +rm -f fmt/user/* cp README fmt echo > fmt/blank files=`grep -v '^#' runoff.list | awk '{print $1}'` diff --git a/runoff.list b/runoff.list index 9fdc9e0..6e6af18 100644 --- a/runoff.list +++ b/runoff.list @@ -1,76 +1,64 @@ # basic headers -types.h -param.h -memlayout.h -defs.h -x86.h -asm.h -mmu.h -elf.h -date.h +kernel/types.h +kernel/param.h +kernel/memlayout.h +kernel/defs.h +kernel/riscv.h +kernel/elf.h +kernel/date.h # entering xv6 -entry.S -entryother.S -main.c +kernel/entry.S +kernel/main.c # locks -spinlock.h -spinlock.c +kernel/spinlock.h +kernel/spinlock.c # processes -vm.c -proc.h -proc.c -swtch.S -kalloc.c +kernel/vm.c +kernel/proc.h +kernel/proc.c +kernel/swtch.S +kernel/kalloc.c # system calls -traps.h -vectors.pl -trapasm.S -trap.c -syscall.h -syscall.c -sysproc.c +user/usys.pl +kernel/trap.c +kernel/syscall.h +kernel/syscall.c +kernel/sysproc.c # file system -buf.h -sleeplock.h -fcntl.h -stat.h -fs.h -file.h -ide.c -bio.c -sleeplock.c -log.c -fs.c -file.c -sysfile.c -exec.c +kernel/buf.h +kernel/sleeplock.h +kernel/fcntl.h +kernel/stat.h +kernel/fs.h +kernel/file.h +kernel/virtio_disk.c +kernel/bio.c +kernel/sleeplock.c +kernel/log.c +kernel/fs.c +kernel/file.c +kernel/sysfile.c +kernel/exec.c # pipes -pipe.c +kernel/pipe.c # string operations -string.c +kernel/string.c # low-level hardware -mp.h -mp.c -lapic.c -ioapic.c -kbd.h -kbd.c -console.c -uart.c +kernel/uart.c # user-level -initcode.S -usys.S -init.c -sh.c +user/initcode.S +user/usys.S +user/init.c +user/sh.c # link -kernel.ld +kernel/kernel.ld diff --git a/user/cow.c b/user/cow.c new file mode 100644 index 0000000..45efc98 --- /dev/null +++ b/user/cow.c @@ -0,0 +1,196 @@ +// +// tests for copy-on-write fork() assignment. +// + +#include "kernel/types.h" +#include "kernel/memlayout.h" +#include "user/user.h" + +// allocate more than half of physical memory, +// then fork. this will fail in the default +// kernel, which does not support copy-on-write. +void +simpletest() +{ + uint64 phys_size = PHYSTOP - KERNBASE; + int sz = (phys_size / 3) * 2; + + printf(1, "simple: "); + + char *p = sbrk(sz); + if(p == (char*)0xffffffffffffffffL){ + printf(1, "sbrk(%d) failed\n", sz); + exit(); + } + + for(char *q = p; q < p + sz; q += 4096){ + *(int*)q = getpid(); + } + + int pid = fork(); + if(pid < 0){ + printf(1, "fork() failed\n"); + exit(); + } + + if(pid == 0) + exit(); + + wait(); + + if(sbrk(-sz) == (char*)0xffffffffffffffffL){ + printf(1, "sbrk(-%d) failed\n", sz); + exit(); + } + + printf(1, "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() +{ + uint64 phys_size = PHYSTOP - KERNBASE; + int sz = phys_size / 4; + int pid1, pid2; + + printf(1, "three: "); + + char *p = sbrk(sz); + if(p == (char*)0xffffffffffffffffL){ + printf(1, "sbrk(%d) failed\n", sz); + exit(); + } + + pid1 = fork(); + if(pid1 < 0){ + printf(1, "fork failed\n"); + exit(); + } + if(pid1 == 0){ + pid2 = fork(); + if(pid2 < 0){ + printf(1, "fork failed"); + exit(); + } + 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(1, "wrong content\n"); + exit(); + } + } + exit(); + } + for(char *q = p; q < p + (sz/2); q += 4096){ + *(int*)q = 9999; + } + exit(); + } + + for(char *q = p; q < p + sz; q += 4096){ + *(int*)q = getpid(); + } + + wait(); + + sleep(1); + + for(char *q = p; q < p + sz; q += 4096){ + if(*(int*)q != getpid()){ + printf(1, "wrong content\n"); + exit(); + } + } + + if(sbrk(-sz) == (char*)0xffffffffffffffffL){ + printf(1, "sbrk(-%d) failed\n", sz); + exit(); + } + + printf(1, "ok\n"); +} + +char junk1[4096]; +int fds[2]; +char junk2[4096]; +char buf[4096]; +char junk3[4096]; + +// test whether copyout() simulates COW faults. +void +filetest() +{ + int parent = getpid(); + + printf(1, "file test: "); + + buf[0] = 99; + + for(int i = 0; i < 4; i++){ + if(pipe(fds) != 0){ + printf(1, "pipe() failed\n"); + exit(); + } + int pid = fork(); + if(pid < 0){ + printf(1, "fork failed\n"); + exit(); + } + if(pid == 0){ + sleep(1); + if(read(fds[0], buf, sizeof(i)) != sizeof(i)){ + printf(1, "read failed\n"); + kill(parent); + exit(); + } + sleep(1); + int j = *(int*)buf; + if(j != i){ + printf(1, "read the wrong value\n"); + kill(parent); + exit(); + } + exit(); + } + if(write(fds[1], &i, sizeof(i)) != sizeof(i)){ + printf(1, "write failed\n"); + exit(); + } + } + + for(int i = 0; i < 4; i++) + wait(); + + if(buf[0] != 99){ + printf(1, "child overwrote parent\n"); + exit(); + } + + printf(1, "ok\n"); +} + +int +main(int argc, char *argv[]) +{ + simpletest(); + + // check that the first simpletest() freed the physical memory. + simpletest(); + + threetest(); + threetest(); + threetest(); + + filetest(); + + printf(1, "ALL COW TESTS PASSED\n"); + + exit(); +} diff --git a/user/usertests.c b/user/usertests.c index beca8f9..ef70bfb 100644 --- a/user/usertests.c +++ b/user/usertests.c @@ -1438,6 +1438,13 @@ sbrktest(void) printf(stdout, "sbrk test\n"); oldbrk = sbrk(0); + // does sbrk() return the expected failure value? + a = sbrk(1024*1024*1024); + if(a != (char*)0xffffffffffffffffL){ + printf(stdout, "sbrk() returned %p\n", a); + exit(); + } + // can one sbrk() less than a page? a = sbrk(0); for(i = 0; i < 5000; i++){ @@ -1466,7 +1473,7 @@ sbrktest(void) // can one grow address space to something big? a = sbrk(0); - amt = (BIG) - (uint64)a; + amt = BIG - (uint64)a; p = sbrk(amt); if (p != a) { printf(stdout, "sbrk test failed to grow big address space; enough phys mem?\n"); @@ -1478,7 +1485,7 @@ sbrktest(void) // can one de-allocate? a = sbrk(0); c = sbrk(-4096); - if(c == (char*)0xffffffff){ + if(c == (char*)0xffffffffffffffffL){ printf(stdout, "sbrk could not deallocate\n"); exit(); } @@ -1551,7 +1558,7 @@ sbrktest(void) kill(pids[i]); wait(); } - if(c == (char*)0xffffffff){ + if(c == (char*)0xffffffffffffffffL){ printf(stdout, "failed sbrk leaked memory\n"); exit(); }