# 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 标志,保证了内存的正确共享与释放。 - 通过边界检查和优雅失败,保证了系统健壮性。 本实验加深了对操作系统内存管理、页表机制和异常处理的理解。