本Lab为xv6添加一个copy on write的功能。
笔者用时约6h(太菜啦 不难但是细节多
概述
xv6中原始fork系统的实现是,当产生一个子进程时,直接把父进程页表中的每一页复制给子进程的页表,这样子做无疑有许多物理空间被浪费,因为并不是每一个空间都会在之后被修改。一个经典的思路就是copy on write,也就是一开始父子进程共享一块物理空间,当某一个物理空间需要被写的时候,再进行复制。
具体的做法就是,当父进程fork出一个子进程的时候,关闭父进程所有页表项的写权限,然后进行复制,复制时父子进程映射到相同的物理地址空间。当代码对没有写权限的页表项进行写操作时,有两种情况,分为内核态与用户态,如果在用户态,则会产生page fault;如果在内核态,则是在copyout函数中;两种情况的处理方式相同,都是为错误的虚拟地址重新分配一页物理内存,具体细节见下文。
Implement copy-on write
在写代码之前最好先把细节都想清楚,不然就会像笔者一样产生一堆bug >_<
由于fork函数中会调用uvmcopy函数对页表进行复制,故首先修改uvmcopy函数,将父进程的物理页映射到子进程中,而不是分配新物理页,并清空父子进程对应PTE中的PTE_W标志。具体代码如下,其中的updateref函数之后会讲。
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
pte_t *pte;
uint64 pa, i;
uint flags;
// char *mem;
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
panic("uvmcopy: pte should exist");
if((*pte & PTE_V) == 0)
panic("uvmcopy: page not present");
pa = PTE2PA(*pte);
// remove the PTE_W flag
flags = PTE_FLAGS(*pte);
if ((flags & PTE_W) != 0) {
flags ^= PTE_W;
*pte ^= PTE_W;
}
updateref((void*) pa, 1);
// map child's va to parent's pa
if(mappages(new, i, PGSIZE, pa, flags) != 0){
kfree((void*) pa);
goto err;
}
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}
接下来需要修改usertrap函数,以便在page fault出现时进行处理。类似lazy lab,需要先判断是否为page fault,这里只需要处理scause为15的情况(13对应load的page fault,15对应store的)。首先需要判断当前错误的虚拟地址对应的PTE是否是不可写状态(文档里说重新用一个位来标志当前页面是否为cow页,但是在这里我直接用是否不可写来判断了,好像不太严谨但是也能过测试),之后便是分配新的物理页,并复制旧物理页内容到新物理页中,把旧物理页映射删除(这里用uvmunmap是有必要的,后面会讲)。
<

最低0.47元/天 解锁文章
1706

被折叠的 条评论
为什么被折叠?



