xv6中的fork()系统调用将父进程的所有用户空间内存复制到子进程中。如果父进程较大,则复制可能需要很长时间。更糟糕的是,这项工作经常造成大量浪费;例如,子进程中的fork()后跟exec()将导致子进程丢弃复制的内存,而其中的大部分可能都从未使用过。另一方面,如果父子进程都使用一个页面,并且其中一个或两个对该页面有写操作,则确实需要复制。

解决方案

copy-on-write (COW) fork()的目标是推迟到子进程实际需要物理内存拷贝时再进行分配和复制物理内存页面。
COW fork()只为子进程创建一个页表,用户内存的PTE指向父进程的物理页。COW fork()将父进程和子进程中的所有用户PTE标记为不可写。当任一进程试图写入其中一个COW页时,CPU将强制产生页面错误。内核页面错误处理程序检测到这种情况将为出错进程分配一页物理内存,将原始页复制到新页中,并修改出错进程中的相关PTE指向新的页面,将PTE标记为可写。当页面错误处理程序返回时,用户进程将能够写入其页面副本。
COW fork()将使得释放用户内存的物理页面变得更加棘手。给定的物理页可能会被多个进程的页表引用,并且只有在最后一个引用消失时才应该被释放。

思路理解

  1. 添加PTE_COW的页表项标志位
  2. fork函数实际上会去调用vm.c中的uvmcopy函数,这个函数之前是分配同样的内存映射给子进程,那么我们把父进程的PTE_COW设为1,然后子进程同步这个映射。

但是里面有一个比较麻烦的问题,就是进程虚拟内存的引用计数,但引用数为0时,才清空该虚拟内存。

  1. kalloc.c文件是用于分配物理内存的文件,我们在原先的mem内存块中添加计数相关的字段。

image.png
具体这块去看代码比较好。

Fork

xv6原先的做法:将父进程用户空间复制一份新的,将子进程的用户空间映射到这份新的物理内存上面。
Cow:

  • 将子进程的用户空间直接映射到父进程的用户空间上面。
  • 回收相应物理页上的写权限。

    Page fault

    考察当前的触发page fault的va,是否是由于cow导致的?
    如果是的话,则解除之前的映射,执行拷贝操作。

引用计数

当释放相应的物理页的时候,可能物理页还存在着其他的映射。
使用“引用计数”来解决该问题。

自述

先设计一个页表项的标志位 PTE_COW,1 << 8,fork函数会去调用uvmcopy这个方法,我们在这个方法中,不去另外开辟一份物理内存,当之后发送缺页异常时,去判断虚拟地址的PTE_COW是否为1,是的话,在去开辟物理内存,再去mappage进行一个映射。

还有就是需要去考虑引用计数,但没进程去依赖同一物理内存时才回收。