xv6 学习笔记: Lab Copy on-write
学习
- xv6 book 第 5 章 5.3 节
要点
COW (Copy On Write) fork 旨在当子进程从父进程 fork 出来时,不实际复制物理页。而是把父进程页表的 PTE 改为只读,然后只为子进程创建新页表,PTE 和父进程相同。如果某一页原来可写,还需要给 PTE 加上额外的 COW 标志。当 page fault 发生时,对于原本就只读的页,走正常的 page fault 流程。对于原来可写的页,内核会识别出 COW 标志,申请新页,把旧页内容复制到新页,PTE 改为指向新页,去除 COW 标志并设为可写。
xv6 学习笔记: Lab Page tables 中有 RISC-V 的 PTE 结构示意图。PTE 的第 8 位和第 9 位保留给内核使用。可以用第 8 位作为 COW 标志。
许多内核函数用到 kfree。当内核中没有多个 PTE 指向同一物理页时,kfree 不是问题。但这种情况存在时,如果任意一个 kfree 直接释放了物理页,会造成其他 PTE 的垂悬引用。所以需要为每个物理页创建引用计数,记录有多少个 PTE 指向它。kalloc 时,修改物理页的引用计数为 1。新增一个指向物理页的 PTE 时,将其引用计数 +1。kfree 减小引用计数,当最后一个 PTE 不再指向某个物理页时,该物理页才真正释放。引用计数可以统一保存在一个数组里。该数组需要索引任何 kalloc 有可能申请到的物理页,即 end - PHYSTOP 这段物理页。引用计数的读写必须加锁。引用计数不会超过进程的数量,NPROC 为 64,用 uint8 保存引用计数即可。
原来的 vmfault 只有在尝试为 va 成功新分配一页的时候才返回页面物理地址,其余时候(包括 va 已经映射到了物理页)返回 0。新的 vmfault 还应该在 va 已经映射到 COW 页时,尝试为其分配新的可写页并修改 PTE,其余情况返回 0。
copyout 因为要向进程的地址空间写入值,所以可能会遇到 COW 页,需要额外处理。
魔鬼藏在细节里。需要仔细思考各种边界情况。
不要改太多代码。否则可能会牵一发而动全身,引发各种奇妙问题。