MIT 6.1810 · Lab cow · 写时复制

Lab: Copy-on-Write Fork for xv6

虚拟内存提供了一层间接性:内核可以通过将 PTE 标记为无效或只读来拦截内存引用,导致 page fault,并可以通过修改 PTE 来更改地址的含义。

难度:困难

分支:cow

实验日期:2026 秋季

$ git fetch
$ git checkout cow
$ make clean

问题

xv6 中的 fork() 系统调用将父进程的所有用户空间内存复制到子进程中。如果父进程很大,复制可能需要很长时间。更糟糕的是,工作通常大部分被浪费:fork() 通常在子进程中跟随着 exec(),它丢弃了复制的内存,通常没有使用大部分内容。

另一方面,如果父进程和子进程都使用复制的页面,并且一个或两个写入它,那么复制才是真正需要的。

解决方案

你的目标是实现写时复制 (COW) fork(),推迟分配和复制物理内存页,直到真正需要复制,如果需要的话。

COW fork() 只为子进程创建一个页表,用户内存的 PTE 指向父进程的物理页。COW fork() 将父进程和子进程中的所有用户 PTE 标记为只读。当任一进程尝试写入这些 COW 页之一时,CPU 将强制产生 page fault。内核 page fault handler 检测这种情况,为 faulting 进程分配一个物理内存页,将原始页复制到新页中,并修改 faulting 进程中的相关 PTE 以引用新页,这次 PTE 标记为可写。

COW fork() 使实现用户内存的物理页的释放变得有点棘手。给定物理页可能被多个进程的页表引用,并且只在最后一个引用消失时才被释放。在像 xv6 这样的简单内核中,这种簿记相当简单,但在生产内核中,这可能很难做好。

实现写时复制 fork 困难

你的任务是在 xv6 内核中实现写时复制 fork。如果你的修改后的内核成功执行 cowtest 和 'usertests -q' 程序,你就完成了。

$ cowtest
simple: ok
simple: ok
three: ok
three: ok
three: ok
file: ok
forkfork: ok
ALL COW TESTS PASSED
$ usertests -q
...
ALL TESTS PASSED
$

这是一个合理的攻击计划:

  1. 修改 uvmcopy() 将父进程的物理页映射到子进程中,而不是分配新页。清除子进程和父进程中具有 PTE_W 的页的 PTE_W。
  2. 修改 vmfault() 识别 page fault。当 COW 页上发生写 page fault 时,分配新页,复制旧页,安装新页并设置 PTE_W。
  3. 确保每个物理页在最后一个 PTE 引用消失时被释放——但不超过。为每个物理页维护一个"引用计数"。
  4. 修改 copyout() 在遇到 COW 页时使用与 page fault 相同的方案。

一些提示:

提交实验

时间花费

创建一个新文件 time.txt,放入一个整数,表示你花在实验上的小时数。

答案

如果本实验有问题,将答案写在 answers-cow.txt 中。

提交

运行 make zipball,上传到 Gradescope。

可选挑战练习