From 94313bf34d3caa5d89199130491b30edd233760b Mon Sep 17 00:00:00 2001 From: fedos Date: Tue, 11 Nov 2025 22:34:29 +0300 Subject: [PATCH 1/3] :construction: cowtest passed --- kernel/defs.h | 7 +++++ kernel/exec.c | 6 ++++ kernel/kalloc.c | 73 ++++++++++++++++++++++++++++++++++++++++--- kernel/riscv.h | 1 + kernel/trap.c | 38 +++++++++++++++++++++++ kernel/vm.c | 82 +++++++++++++++++++++++++++++++++++++++++++------ 6 files changed, 193 insertions(+), 14 deletions(-) diff --git a/kernel/defs.h b/kernel/defs.h index f947e24..7fbb4a7 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -10,6 +10,13 @@ struct stat; struct superblock; struct list; +void vmprint(pagetable_t pagetable); + + +int kref_inc(uint64 pa); +int kref_dec(uint64 pa); +int kref_read(uint64 pa); + // bio.c void binit(void); struct buf* bread(uint, uint); diff --git a/kernel/exec.c b/kernel/exec.c index 6d7c452..2a9426a 100644 --- a/kernel/exec.c +++ b/kernel/exec.c @@ -8,6 +8,7 @@ #include "elf.h" static int loadseg(pde_t *, uint64, struct inode *, uint, uint); +static int vmprint_done = 0; int flags2perm(int flags) { @@ -128,6 +129,11 @@ exec(char *path, char **argv) p->trapframe->sp = sp; // initial stack pointer proc_freepagetable(oldpagetable, oldsz); + if (!vmprint_done) { + vmprint(p->pagetable); + vmprint_done = 1; + } + return argc; // this ends up in a0, the first argument to main(argc, argv) bad: diff --git a/kernel/kalloc.c b/kernel/kalloc.c index 0699e7e..a44659a 100644 --- a/kernel/kalloc.c +++ b/kernel/kalloc.c @@ -14,6 +14,10 @@ void freerange(void *pa_start, void *pa_end); extern char end[]; // first address after kernel. // defined by kernel.ld. +// refcount per physical page. +// index = (pa - KERNBASE) / PGSIZE +static int refcount[(PHYSTOP - KERNBASE) / PGSIZE]; + struct run { struct run *next; }; @@ -23,6 +27,18 @@ struct { struct run *freelist; } kmem; +static inline int +pa2idx(uint64 pa) +{ + return (pa - KERNBASE) / PGSIZE; +} + +static inline int +npage_total(void) +{ + return (PHYSTOP - KERNBASE) / PGSIZE; +} + void kinit() { @@ -35,8 +51,12 @@ freerange(void *pa_start, void *pa_end) { char *p; p = (char*)PGROUNDUP((uint64)pa_start); - for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE) + for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE){ + int idx = pa2idx((uint64)p); + if (idx >= 0 && idx < npage_total()) + refcount[idx] = 1; // will be set to 0 in kfree kfree(p); + } } // Free the page of physical memory pointed at by pa, @@ -51,12 +71,25 @@ kfree(void *pa) if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) panic("kfree"); + acquire(&kmem.lock); + + int idx = pa2idx((uint64)pa); + int c = refcount[idx]; + if (c <= 0){ + release(&kmem.lock); + panic("kfree: refcount underflow"); + } + c = --refcount[idx]; + if (c > 0){ + release(&kmem.lock); + return; // still being used by someone + } + // Fill with junk to catch dangling refs. memset(pa, 1, PGSIZE); r = (struct run*)pa; - acquire(&kmem.lock); r->next = kmem.freelist; kmem.freelist = r; release(&kmem.lock); @@ -72,11 +105,43 @@ kalloc(void) acquire(&kmem.lock); r = kmem.freelist; - if(r) + if(r){ kmem.freelist = r->next; - release(&kmem.lock); + int idx = pa2idx((uint64) r); + refcount[idx] = 1; + } + release(&kmem.lock); if(r) memset((char*)r, 5, PGSIZE); // fill with junk return (void*)r; } + + +// --- refcount helpers for COW --- +int kref_inc(uint64 pa) +{ + acquire(&kmem.lock); + int idx = pa2idx(pa); + int v = ++refcount[idx]; + release(&kmem.lock); + return v; +} + +int kref_dec(uint64 pa) +{ + acquire(&kmem.lock); + int idx = pa2idx(pa); + int v = --refcount[idx]; + release(&kmem.lock); + return v; +} + +int kref_read(uint64 pa) +{ + acquire(&kmem.lock); + int idx = pa2idx(pa); + int v = refcount[idx]; + release(&kmem.lock); + return v; +} diff --git a/kernel/riscv.h b/kernel/riscv.h index f7aaa8a..4956193 100644 --- a/kernel/riscv.h +++ b/kernel/riscv.h @@ -362,6 +362,7 @@ typedef uint64 *pagetable_t; // 512 PTEs #define PTE_W (1L << 2) #define PTE_X (1L << 3) #define PTE_U (1L << 4) // user can access +#define PTE_COW (1L << 8) // RSW // 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 d454a7d..be5d476 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -67,6 +67,44 @@ usertrap(void) syscall(); } else if((which_dev = devintr()) != 0){ // ok + } else if (r_scause() == 13 || r_scause() == 15) { + uint64 va = r_stval(); + uint64 a = PGROUNDDOWN(va); + + pte_t *pte = walk(p->pagetable, a, 0); + + // make sure its COW page + if (pte && (*pte & PTE_V) && (*pte & PTE_COW) && ((*pte & PTE_W) == 0 )) { + uint64 pa = PTE2PA(*pte); + uint flags = PTE_FLAGS(*pte); + int refs = kref_read(pa); + + if (refs > 1) { + char *mem = kalloc(); + if (mem == 0) { + // no memory + printf("COW: out of memory pid=%d va=0x%lx\n", p->pid, va); + setkilled(p); + } + else{ + memmove(mem, (void*)pa, PGSIZE); + // new pte + *pte = PA2PTE((uint64)mem); + *pte |= ((flags | PTE_W) & ~PTE_COW); // unset COW, set W + kref_dec(pa); + sfence_vma(); // update tlb for new va + // + } + } else { + // page is not used anymore + *pte = (*pte | PTE_W) & ~PTE_COW; + sfence_vma(); + } + } else { + printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); + printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); + setkilled(p); + } } else { printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); diff --git a/kernel/vm.c b/kernel/vm.c index 62421a2..38a8f8a 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -293,6 +293,32 @@ freewalk(pagetable_t pagetable) kfree((void*)pagetable); } +static void +vmprint_rec(pagetable_t pagetable, int level){ + for (int i = 0; i < 512; i++){ + pte_t pte = pagetable[i]; + if ((pte & PTE_V) == 0) + continue; + + uint64 pa = PTE2PA(pte); + for (int d = 0; d < level; d++){ + printf(".. "); + } + printf("%d: pte 0x00000000%lx pa 0x00000000%lx\n", i, pte, pa); // 0x%16lx doesnt work here LUL + + if ((pte & (PTE_R | PTE_W | PTE_X)) == 0) { + vmprint_rec((pagetable_t)pa, level + 1); + } + } +} + +void +vmprint(pagetable_t pagetable) { + printf("page table 0x00000000%lx\n", (uint64)pagetable); // same here + vmprint_rec(pagetable, 0); +} + + // Free user memory pages, // then free page-table pages. void @@ -315,7 +341,6 @@ 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) @@ -324,18 +349,34 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) panic("uvmcopy: page not present"); pa = PTE2PA(*pte); 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); - goto err; + if (flags & PTE_W) { + *pte = (*pte & ~PTE_W) | PTE_COW; // read-only + cow + uint child_flags = (flags & ~PTE_W) | PTE_COW; // same + if (mappages(new, i, PGSIZE, pa, child_flags) != 0) + goto err; + } else { + // map without cow + if (mappages(new, i, PGSIZE, pa, flags) != 0) + goto err; } + + kref_inc(pa); } + sfence_vma(); // update TLB return 0; err: - uvmunmap(new, 0, i / PGSIZE, 1); + for (uint64 j = 0; j < i; j+= PGSIZE) { + pte_t *opte = walk(old, j, 0); + if (opte && (*opte & PTE_V)){ + uint64 opa = PTE2PA(*opte); + if (*opte & PTE_COW) { // if COW was set, then W was unset, so revert + *opte = (*opte | PTE_W) & ~PTE_COW; + } + kref_dec(opa); + } + } + uvmunmap(new, 0, i / PGSIZE, 0); return -1; } @@ -366,9 +407,30 @@ copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) if(va0 >= MAXVA) return -1; pte = walk(pagetable, va0, 0); - if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 || - (*pte & PTE_W) == 0) + if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) return -1; + + if ((*pte & PTE_W) == 0) { + if ((*pte& PTE_COW) == 0) { + return -1; + } + uint64 pa = PTE2PA(*pte); + uint flags = PTE_FLAGS(*pte); + int refs = kref_read(pa); + if (refs > 1) { + char *mem = kalloc(); + if (mem == 0) + return -1; + memmove(mem, (void*)pa, PGSIZE); + *pte = PA2PTE(mem); + *pte |= ((flags | PTE_W) & ~PTE_COW); // unset COW, set W + kref_dec(pa); + sfence_vma(); + } else { + *pte = (*pte | PTE_W) & ~PTE_COW; + sfence_vma(); + } + } pa0 = PTE2PA(*pte); n = PGSIZE - (dstva - va0); if(n > len) From 6a722b1cbd0508aa521f0bfc8496391c1bafb49e Mon Sep 17 00:00:00 2001 From: fedos Date: Tue, 11 Nov 2025 23:04:18 +0300 Subject: [PATCH 2/3] :green_heart: usertests & cowtest --- kernel/trap.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/kernel/trap.c b/kernel/trap.c index be5d476..ba33441 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -71,6 +71,10 @@ usertrap(void) uint64 va = r_stval(); uint64 a = PGROUNDDOWN(va); + if (a >= MAXVA) { + goto unexpected_page_fault; + } + pte_t *pte = walk(p->pagetable, a, 0); // make sure its COW page @@ -93,7 +97,6 @@ usertrap(void) *pte |= ((flags | PTE_W) & ~PTE_COW); // unset COW, set W kref_dec(pa); sfence_vma(); // update tlb for new va - // } } else { // page is not used anymore @@ -101,16 +104,19 @@ usertrap(void) sfence_vma(); } } else { - printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); - printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); - setkilled(p); + goto unexpected_page_fault; } } else { - printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); - printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); - setkilled(p); + goto unexpected_page_fault; } + goto done; +unexpected_page_fault: + printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); + printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); + setkilled(p); + +done: if(killed(p)) exit(-1); From a7b2d3d4a5a8c5cafc8a1946376b887ff04b3533 Mon Sep 17 00:00:00 2001 From: fedos Date: Wed, 12 Nov 2025 18:12:12 +0300 Subject: [PATCH 3/3] :sparkles: lazy allocation --- kernel/defs.h | 4 +++- kernel/exec.c | 2 +- kernel/proc.c | 21 ++++++++++++++++----- kernel/sysproc.c | 24 +++++++++++++++++++++--- kernel/trap.c | 28 +++++++++++++++++++++++++--- kernel/vm.c | 43 +++++++++++++++++++++++++++++++++---------- 6 files changed, 99 insertions(+), 23 deletions(-) diff --git a/kernel/defs.h b/kernel/defs.h index 7fbb4a7..610c0fd 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -10,8 +10,10 @@ struct stat; struct superblock; struct list; -void vmprint(pagetable_t pagetable); +uint64 proc_getsz(struct proc *p); + +void vmprint(pagetable_t pagetable); int kref_inc(uint64 pa); int kref_dec(uint64 pa); diff --git a/kernel/exec.c b/kernel/exec.c index 2a9426a..14a026a 100644 --- a/kernel/exec.c +++ b/kernel/exec.c @@ -130,7 +130,7 @@ exec(char *path, char **argv) proc_freepagetable(oldpagetable, oldsz); if (!vmprint_done) { - vmprint(p->pagetable); + // vmprint(p->pagetable); vmprint_done = 1; } diff --git a/kernel/proc.c b/kernel/proc.c index 130d9ce..3bf48ef 100644 --- a/kernel/proc.c +++ b/kernel/proc.c @@ -26,6 +26,13 @@ extern char trampoline[]; // trampoline.S // must be acquired before any p->lock. struct spinlock wait_lock; +// helper for calling from vm +uint64 +proc_getsz(struct proc* p) +{ + return p->sz; +} + // Allocate a page for each process's kernel stack. // Map it high in memory, followed by an invalid // guard page. @@ -408,14 +415,18 @@ wait(uint64 addr) if(pp->state == ZOMBIE){ // Found one. pid = pp->pid; + freeproc(pp); + release(&pp->lock); + if(addr != 0 && copyout(p->pagetable, addr, (char *)&pp->xstate, sizeof(pp->xstate)) < 0) { - release(&pp->lock); - release(&wait_lock); - return -1; + // release(&pp->lock); + // release(&wait_lock); + // return -1; } - freeproc(pp); - release(&pp->lock); + + // freeproc(pp); + // release(&pp->lock); release(&wait_lock); return pid; } diff --git a/kernel/sysproc.c b/kernel/sysproc.c index 3b4d5bd..1442b4a 100644 --- a/kernel/sysproc.c +++ b/kernel/sysproc.c @@ -40,11 +40,29 @@ sys_sbrk(void) { uint64 addr; int n; + struct proc *p = myproc(); argint(0, &n); - addr = myproc()->sz; - if(growproc(n) < 0) - return -1; + addr = p->sz; + if (n > 0) { + uint64 temp = p->sz + n; + if (temp >= MAXVA) + return -1; + p->sz += n; + + } else if (n < 0) { + uint64 dec = -n; + if (dec > p->sz) + return -1; + uint64 oldsz = p->sz; + uint64 newsz = oldsz - dec; + uint64 a = PGROUNDUP(newsz); + uint64 end = PGROUNDUP(oldsz); + if (end > a) { + uvmunmap(p->pagetable, a, (end - a) / PGSIZE, 1); + } + p->sz = newsz; + } return addr; } diff --git a/kernel/trap.c b/kernel/trap.c index ba33441..af1d8aa 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -77,6 +77,28 @@ usertrap(void) pte_t *pte = walk(p->pagetable, a, 0); + + // lazy alloc logic + if (pte == 0 || (*pte & PTE_V) == 0) { + if (a < p->sz) { + char *mem = kalloc(); + if (mem == 0) { + // printf("lazy alloc: kalloc fault, pid=%d, va=0x%lx\n", p->pid, va); + setkilled(p); + goto done; + } + memset(mem, 0, PGSIZE); + if (mappages(p->pagetable, a, PGSIZE, (uint64)mem, PTE_R | PTE_W | PTE_U) != 0) { + kfree(mem); + // printf("lazy alloc: mappages fault, pid=%d, va=0x%lx\n", p->pid, va); + setkilled(p); + goto done; + } + sfence_vma(); + goto done; + } + } + // make sure its COW page if (pte && (*pte & PTE_V) && (*pte & PTE_COW) && ((*pte & PTE_W) == 0 )) { uint64 pa = PTE2PA(*pte); @@ -87,7 +109,7 @@ usertrap(void) char *mem = kalloc(); if (mem == 0) { // no memory - printf("COW: out of memory pid=%d va=0x%lx\n", p->pid, va); + // printf("COW: out of memory pid=%d va=0x%lx\n", p->pid, va); setkilled(p); } else{ @@ -112,8 +134,8 @@ usertrap(void) goto done; unexpected_page_fault: - printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); - printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); + // printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); + // printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); setkilled(p); done: diff --git a/kernel/vm.c b/kernel/vm.c index 38a8f8a..d7a6559 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -185,9 +185,11 @@ uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) for(a = va; a < va + npages*PGSIZE; a += PGSIZE){ if((pte = walk(pagetable, a, 0)) == 0) - panic("uvmunmap: walk"); + // panic("uvmunmap: walk"); + continue; if((*pte & PTE_V) == 0) - panic("uvmunmap: not mapped"); + // panic("uvmunmap: not mapped"); + continue; if(PTE_FLAGS(*pte) == PTE_V) panic("uvmunmap: not a leaf"); if(do_free){ @@ -343,10 +345,13 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) uint flags; for(i = 0; i < sz; i += PGSIZE){ - if((pte = walk(old, i, 0)) == 0) - panic("uvmcopy: pte should exist"); - if((*pte & PTE_V) == 0) - panic("uvmcopy: page not present"); + // if((pte = walk(old, i, 0)) == 0) + // panic("uvmcopy: pte should exist"); + // if((*pte & PTE_V) == 0) + // panic("uvmcopy: page not present"); + pte = walk(old, i, 0); + if (pte == 0 || (*pte & PTE_V) == 0) + continue; pa = PTE2PA(*pte); flags = PTE_FLAGS(*pte); if (flags & PTE_W) { @@ -370,9 +375,6 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) pte_t *opte = walk(old, j, 0); if (opte && (*opte & PTE_V)){ uint64 opa = PTE2PA(*opte); - if (*opte & PTE_COW) { // if COW was set, then W was unset, so revert - *opte = (*opte | PTE_W) & ~PTE_COW; - } kref_dec(opa); } } @@ -406,8 +408,29 @@ copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) va0 = PGROUNDDOWN(dstva); if(va0 >= MAXVA) return -1; + +retry_page: pte = walk(pagetable, va0, 0); - if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0) + if (pte == 0 || (*pte & PTE_V) == 0) { + // lazy alloc + struct proc *p = myproc(); + if (va0 < proc_getsz(p)) { + char *mem = kalloc(); + if (mem == 0) + return -1; + memset(mem, 0, PGSIZE); + if (mappages(pagetable, va0, PGSIZE, (uint64)mem, PTE_R | PTE_W | PTE_U) != 0){ + kfree(mem); + return -1; + } + sfence_vma(); + goto retry_page; + } else { + return -1; + } + } + + if ((*pte & PTE_U) == 0) return -1; if ((*pte & PTE_W) == 0) {