3.9 KiB
3.9 KiB
xv6-labs: Copy-On-Write (COW) Fork 实验详细总结
实验目标
实现 xv6 操作系统的 COW(写时复制)fork:
- fork 时父子进程共享物理页,只有在写入时才真正分配和复制物理页。
- 提高 fork+exec 的效率,减少不必要的内存拷贝。
主要实现步骤与详细代码
1. 物理页引用计数
文件:kernel/kalloc.c
- 在文件顶部添加:
#define NPAGE ((PHYSTOP - KERNBASE) / PGSIZE)
static int refcnt[NPAGE];
static inline int pa2idx(void *pa) { return ((uint64)pa - KERNBASE) / PGSIZE; }
- kalloc 分配新页时:
if(r) {
memset((char*)r, 5, PGSIZE);
int idx = pa2idx((void*)r);
refcnt[idx] = 1;
}
- kfree 释放页时:
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);
// ...加入空闲链表...
- 增加辅助函数:
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
#define PTE_COW (1L << 8) // 使用RSW位标记COW
3. 修改 uvmcopy 实现 COW fork
文件:kernel/vm.c
- 在文件头部声明:
void incref(void *pa);
- 修改 uvmcopy:
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
- 在文件头部声明:
void kfree(void *pa);
void *kalloc(void);
- 在 usertrap() 添加COW处理:
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:
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():
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 标志,保证了内存的正确共享与释放。
- 通过边界检查和优雅失败,保证了系统健壮性。
本实验加深了对操作系统内存管理、页表机制和异常处理的理解。