简介
众所周知,在fork时,属于进程private的内存页将会进行COW机制。所谓COW,就是一个资源如果需要值拷贝,在读时不创建出副本,仅当写时再创建。这样的话,就可以方便地判断出什么资源需要真的进行拷贝,而能够共享则无需拷贝,从而减少了复制的开销。
这个流程分为两部分:
Fork
设置父子进程的所有内存页的标志为write protected,
而在mmap中被标识为shared的内存则会通过wp_page_reuse标记为wriable
因为谁先写不知道,所以两者都应该是wp,都能进行COW机制。
这里产生了一个问题:
假如父子进程都使用COW,那么在子进程已经copy过的情况下,父进程再copy一次就会造成浪费。(此时原本的一个物理页会对应三个物理页,copy两次)
而且父子同时使用副本的话,原页在没有进程使用的情况下应该如何释放?如果使用计数的话,我们可以知道这个页在cnt==0时应该gc,但是假如我们已经知道了计数,我们完全可以在cnt==1时就不再复制。(此时原本的一个物理页会对应两个物理页,copy1次)
Linux中,也的确很节省地使用了这样的方式。
COW
首先和常识相同,write这些页会触发page fault:
handle_pte _fault
linux使用handle_pte_fault函数处理:
如果vma是writable但是却触发了write fault,则调用do_wp_page(write protect)
do_wp_page
在这个函数里,kernel将会根据物理页遍历所有对应的虚拟页(使用链表)求map cnt,如果map cnt为1,说明当前物理页仅被一个进程使用,不需要COW。(这个过程加锁,防止cnt不同步)。这种情况下,则调用 wp_page_reuse 。
wp_page_reuse
这个函数会在两种情况下调用,要么是上述map cnt==1,要么是mmap里声明为shared(VM_SHARED),原本write_protect的页会直接被标识为writable,即跳过copy。
总结
COW机制下,父子进程的页都会被标记为write protect
- 父子进程均有可能进行copy
- 最后一个写的进程不会进行copy,而是直接使用原本的物理页。