在这个实验中要为xv6实现copy on write fork的功能。
在fork子进程的时候不需要复制父进程的物理页给子进程,而是让子进程的页表映射到同一个物理页,修改二者的PTE让其变为只读,让父进程和子进程读共享,节约fork时间和内存空间。当其中某个进程需要写的时候,再为其分配新的物理页。
实现这个功能的第一步需要修改uvmcopy函数。在xv6中,uvmcopy仅在fork时被调用,将父进程的内存复制给子进程。具体修改如下。
可以看到修改之后的uvmcopy不会调用kalloc分配一个新的物理页,而是将子进程的虚拟地址映射到父进程相同虚拟地址所对应的物理页,将该物理页的引用计数加一。同时需要将二者的PTE对应的权限设为只读,并都标记为cow页面。
PTE_COW是一个宏,定义在riscv.h中
#define PTE_COW (1L << 8) // copy on write flag
它位于PTE的RSW字段上,我们用第8位标记某一个页是cow页:
在cow场景下,由于多个进程会映射到同一个物理页,所以需要维护物理页的引用计数。
这里用fefcnt数组来维护物理页的应用技术。内核会分配给用户的物理页地址在 [KERNELBASE, PHYSTOP) 中。(准确的来说是 [end[], PHYSTOP),但end定义在kernel.ld中,是链接的时候才知道的,所以方便起见使用前者即可)。
所以这里refcnt的长度设为(PHYSTOP-KERNBASE)/PGSIZE。
增加了refcnt后,需要修改原来的kalloc和kfree。kalloc分配页时将其引用计数设为1。kfree对于引用计数大于1的页面将其引用计数减1,对于引用计数等于1的页面才真正free并置引用计数为0。提供一个inc_pageref函数,其作用仅仅是讲引用计数加1。
下面需要修改usertrap部分,类似lab lazy那样对page fault进行处理。
在这个实验中,由于只用为cow这一种场景处理page fault。所以只在发生store page fault时处理。当进程想要写一个cow页面,导致page fault,在usertrap中为它分配物理页,将原来的页内容复制到新的物理页上,将触发异常的虚拟页映射到新的物理页上,修改权限为可写,并清除PTE_COW位。
还需要修改的是mappage函数,删除下图部分,因为在cow场景下,这是合法的。
和lab lazy类似,需要处理直接在内核态写的情况。(lab lazy中需要处理在内核态读写未分配的页的情况,要修改copyin和copyout。lab cow只需要处理在内核态写cow页的情况,所以只需要修改copyout)
修改copyout函数如下。
老规矩测试下用例是否通过。
lab cow到此结束。