diff --git a/cowlab.md b/cowlab.md new file mode 100644 index 0000000..bc00cf6 --- /dev/null +++ b/cowlab.md @@ -0,0 +1,159 @@ +# xv6-labs: Copy-On-Write (COW) Fork 实验详细总结 + +## 实验目标 + +实现 xv6 操作系统的 COW(写时复制)fork: +- fork 时父子进程共享物理页,只有在写入时才真正分配和复制物理页。 +- 提高 fork+exec 的效率,减少不必要的内存拷贝。 + +## 主要实现步骤与详细代码 + +### 1. 物理页引用计数 +**文件:kernel/kalloc.c** +- 在文件顶部添加: +```c +#define NPAGE ((PHYSTOP - KERNBASE) / PGSIZE) +static int refcnt[NPAGE]; +static inline int pa2idx(void *pa) { return ((uint64)pa - KERNBASE) / PGSIZE; } +``` +- kalloc 分配新页时: +```c +if(r) { + memset((char*)r, 5, PGSIZE); + int idx = pa2idx((void*)r); + refcnt[idx] = 1; +} +``` +- kfree 释放页时: +```c +int idx = pa2idx(pa); +acquire(&kmem.lock); +if(refcnt[idx] > 1) { + refcnt[idx]--; + release(&kmem.lock); + return; +} +refcnt[idx] = 0; +release(&kmem.lock); +memset(pa, 1, PGSIZE); +// ...加入空闲链表... +``` +- 增加辅助函数: +```c +void incref(void *pa) { refcnt[pa2idx(pa)]++; } +void decref(void *pa) { refcnt[pa2idx(pa)]--; } +int getref(void *pa) { return refcnt[pa2idx(pa)]; } +``` + +### 2. PTE 标记 COW +**文件:kernel/riscv.h** +```c +#define PTE_COW (1L << 8) // 使用RSW位标记COW +``` + +### 3. 修改 uvmcopy 实现 COW fork +**文件:kernel/vm.c** +- 在文件头部声明: +```c +void incref(void *pa); +``` +- 修改 uvmcopy: +```c +int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) { + ... + for(i = 0; i < sz; i += PGSIZE){ + ... + pa = PTE2PA(*pte); + flags = PTE_FLAGS(*pte); + if(flags & PTE_W) { + flags = (flags & ~PTE_W) | PTE_COW; + *pte = PA2PTE(pa) | flags; + } + if(mappages(new, i, PGSIZE, pa, flags) != 0){ + uvmunmap(new, 0, i / PGSIZE, 0); + return -1; + } + incref((void*)pa); + } + return 0; +} +``` + +### 4. usertrap 处理写页错误 +**文件:kernel/trap.c** +- 在文件头部声明: +```c +void kfree(void *pa); +void *kalloc(void); +``` +- 在 usertrap() 添加COW处理: +```c +else if((scause == 15 || scause == 13)) { // store/load page fault + pte_t *pte = walk(p->pagetable, stval, 0); + if(pte && (*pte & PTE_V) && (*pte & PTE_U) && (*pte & PTE_COW)) { + uint64 pa = PTE2PA(*pte); + char *mem = kalloc(); + if(mem == 0) { + setkilled(p); + } else { + memmove(mem, (void*)pa, PGSIZE); + *pte = PA2PTE(mem) | (PTE_FLAGS(*pte) & ~PTE_COW) | PTE_W; + kfree((void*)pa); + } + } else { + setkilled(p); + } +} +``` + +### 5. copyout 处理 COW +**文件:kernel/vm.c** +- 修改 copyout: +```c +if((*pte & PTE_W) == 0) { + if((*pte & PTE_COW) != 0) { + pa0 = PTE2PA(*pte); + char *mem = kalloc(); + if(mem == 0) + return -1; + memmove(mem, (void*)pa0, PGSIZE); + *pte = PA2PTE(mem) | PTE_FLAGS(*pte) | PTE_W; + *pte &= ~PTE_COW; + kfree((void*)pa0); + } else { + return -1; + } +} +``` + +### 6. 释放页时引用计数 +- uvmunmap 释放页时直接调用 kfree,kfree 内部已处理引用计数。 + +### 7. walk() 边界健壮性 +**文件:kernel/vm.c** +- 修改 walk(): +```c +if(va >= MAXVA) + return 0; +``` + +## 调试与测试 + +- 通过 `make qemu`,运行 `usertests` 和 `cowtest`,确保所有测试通过。 +- 重点关注 `MAXVAplus`、`forktest`、`sbrkfail` 等边界和异常测试。 +- 遇到 panic("walk") 问题,修正 walk() 返回 0。 +- 通过多次 fork/exec、写入、回收等场景,验证内存共享和释放的正确性。 + +## 常见问题与修复 + +- **panic("walk")**:walk() 对非法地址应返回0而不是panic。 +- **refcnt数组不能用end做基址**:应以KERNBASE为基址,保证数组大小编译期可知。 +- **重复定义PTE宏**:只保留riscv.h中的定义。 + +## 总结 + +- COW fork 显著提升了 fork+exec 的效率。 +- 通过引用计数和 COW 标志,保证了内存的正确共享与释放。 +- 通过边界检查和优雅失败,保证了系统健壮性。 + +本实验加深了对操作系统内存管理、页表机制和异常处理的理解。 \ No newline at end of file diff --git a/kernel/vm.c b/kernel/vm.c index ddd6309..315c220 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -90,7 +90,8 @@ pte_t * walk(pagetable_t pagetable, uint64 va, int alloc) { if(va >= MAXVA) - panic("walk"); + return 0; + // panic("walk"); for(int level = 2; level > 0; level--) { pte_t *pte = &pagetable[PX(level, va)]; diff --git a/time.txt b/time.txt new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/time.txt @@ -0,0 +1 @@ +9