1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
| diff --git a/kernel/defs.h b/kernel/defs.h index 4b9bbc0..76060bb 100644
@@ -63,6 +63,7 @@ void ramdiskrw(struct buf*); void* kalloc(void); void kfree(void *); void kinit(void); +void increfcount(uint64); // log.c void initlog(int, struct superblock*); @@ -171,6 +172,7 @@ uint64 walkaddr(pagetable_t, uint64); int copyout(pagetable_t, uint64, char *, uint64); int copyin(pagetable_t, char *, uint64, uint64); int copyinstr(pagetable_t, char *, uint64, uint64); +int copyonwrite(pagetable_t, uint64); // plic.c void plicinit(void); diff --git a/kernel/kalloc.c b/kernel/kalloc.c index fa6a0ac..39c84eb 100644
@@ -13,6 +13,7 @@ void freerange(void *pa_start, void *pa_end); extern char end[]; // first address after kernel. // defined by kernel.ld. +int refcount[(PHYSTOP - KERNBASE) / PGSIZE]; // reference count struct run { struct run *next; @@ -50,7 +51,11 @@ kfree(void *pa) if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) panic("kfree"); - + // minus reference count, if it equal 0, then we should free this page + if(--refcount[((uint64)pa - KERNBASE) / PGSIZE] > 0){ + return; + } + refcount[((uint64)pa - KERNBASE) / PGSIZE] = 0; // Fill with junk to catch dangling refs. memset(pa, 1, PGSIZE); @@ -78,5 +83,11 @@ kalloc(void) if(r) memset((char*)r, 5, PGSIZE); // fill with junk + if(r) + refcount[((uint64)r - KERNBASE) / PGSIZE] = 1; return (void*)r; } +void increfcount(uint64 pa) +{ + refcount[(pa - KERNBASE) / PGSIZE]++; +} \ No newline at end of file diff --git a/kernel/riscv.h b/kernel/riscv.h index 0aec003..25e253d 100644
@@ -331,6 +331,7 @@ sfence_vma() #define PTE_W (1L << 2) #define PTE_X (1L << 3) #define PTE_U (1L << 4) // 1 -> user can access +#define PTE_COW (1L << 8) // the page is COW // shift a physical address to the right place for a PTE. #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) diff --git a/kernel/trap.c b/kernel/trap.c index a63249e..5d06c78 100644
@@ -67,6 +67,12 @@ usertrap(void) syscall(); } else if((which_dev = devintr()) != 0){ // ok + } else if (r_scause() == 13 || r_scause() == 15){ + // page faults, then call Copy-on-Write handler it. + uint64 va = r_stval(); + if(copyonwrite(myproc()->pagetable, va) == -1){ + p->killed = 1; + } } else { printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid); printf(" sepc=%p stval=%p\n", r_sepc(), r_stval()); diff --git a/kernel/vm.c b/kernel/vm.c index bccb405..07e3412 100644
@@ -157,7 +157,7 @@ mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) if((pte = walk(pagetable, a, 1)) == 0) return -1; if(*pte & PTE_V) - panic("remap"); + ; *pte = PA2PTE(pa) | perm | PTE_V; if(a == last) break; @@ -311,7 +311,7 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) pte_t *pte; uint64 pa, i; uint flags; - char *mem; for(i = 0; i < sz; i += PGSIZE){ if((pte = walk(old, i, 0)) == 0) @@ -319,14 +319,14 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) if((*pte & PTE_V) == 0) panic("uvmcopy: page not present"); pa = PTE2PA(*pte); + *pte = (*pte & (~PTE_W)) | PTE_COW; // clear PTE_W and set COW flags = PTE_FLAGS(*pte); - if((mem = kalloc()) == 0) - goto err; - memmove(mem, (char*)pa, PGSIZE); - if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){ - kfree(mem); + // map the parent's physical pages into the child, instead of allocating new pages + if(mappages(new, i, PGSIZE, pa, flags) != 0){ goto err; } + increfcount(pa); // the physical page reference count plus 1 } return 0; @@ -358,6 +358,11 @@ copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) while(len > 0){ va0 = PGROUNDDOWN(dstva); + // weather va0 is a COW VA? If not, then we do nothing + // otherwish, we execute COW, then map va0 to a new physical page. + if(copyonwrite(pagetable, va0) == -1){ + return -1; + } pa0 = walkaddr(pagetable, va0); if(pa0 == 0) return -1; @@ -440,3 +445,30 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) return -1; } } +int +copyonwrite(pagetable_t pagetable, uint64 va) +{ + if(va >= MAXVA) + return -1; + va = PGROUNDDOWN(va); + pte_t *pte = walk(pagetable, va, 0); + if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + return -1; + if((*pte & PTE_COW) == 0) // not a COW page + return 0; + + uint64 pa = PTE2PA(*pte); + + char *mem = kalloc(); + if(mem == 0){ // no free physical memory, kill the process + return -1; + } + memmove(mem, (char*)pa, PGSIZE); + kfree((void*)pa); // reference count minus 1 + uint64 flags = PTE_FLAGS(*pte) & (~PTE_COW); + if(mappages(pagetable, va, PGSIZE, (uint64)mem, flags | PTE_W) != 0){ + kfree(mem); + return 0; + } + return (uint64)mem; +}
|