Files
xv6-labs/cowlab.md
2025-07-03 10:28:51 +08:00

159 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 释放页时直接调用 kfreekfree 内部已处理引用计数。
### 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 标志,保证了内存的正确共享与释放。
- 通过边界检查和优雅失败,保证了系统健壮性。
本实验加深了对操作系统内存管理、页表机制和异常处理的理解。