COW机制介绍
COW的机制其实非常简单,核心思想还是将内存的分配推迟:在fork()
的时候只是将子进程的虚拟页映射到父进程的物理内存上;当之后发生了实际的写内存操作引发page fault
后,再进行实际的内存分配。
COW实现
尽管COW的机制非常简单,实现起来也看似简单,但是非常易错,且调试难度也比较大(多核并发)
具体代码见:cow lab
实现过程基本上参考手册的那几步就行:
1、修改 uvmcopy()
函数,将parent
的物理页映射到child
的页表中,而不是分配新的物理页,并且清空PTE_W
标志,设置cow page
的标识。
// uvmcopy for cow fork
// it maps the parent's physical pages into the child
// instead of allocating the new pages
// and clear the PTE_W flag and set the cow page sign
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
pte_t *pte;
uint64 pa, i;
uint flags;
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);
flags = PTE_FLAGS(*pte);
flags = (flags & (~PTE_W)) | PTE_COW; // clear the PTE_W and set the cow sign
*pte = PA2PTE(pa) | flags; // update the parent's pte flags
if(mappages(new, i, PGSIZE, pa, flags) != 0){
panic("uvmcopy: can't map the parent's physical pages into child.");
goto err;
}
add_refcount(PGROUNDDOWN(pa));
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 0);
return -1;
}
2、在usertrap()
中识别page fault
,并进行相应的处理。
...
else if (scause == 15 || scause == 12){
// page fault
uint64 va = r_stval(); // error va
if (cowcopy(p->pagetable, va) != 0) {
p->killed = 1;
}
}
...
3、对物理页设置引用计数机制
这一部分应该是最难的一块,需要考虑并发加锁。
struct {
struct spinlock lock;
struct run *freelist;
uint8 page_references[(PHYSTOP-KERNBASE)/PGSIZE];
} kmem;
4、修改copyout
,机制和处理page fault
一样
...
va0 = PGROUNDDOWN(dstva);
if (cowcopy(pagetable, va0) != 0) return -1;
pa0 = walkaddr(pagetable, va0);
...
错误汇总
1、报错 remap
原因是在建立新的映射之前,要先将原来的映射取消掉;
2、scause = 2 非法指令
思考排错的过程:
初步猜测是:在用户空间使用内核指令?
根据backtrace出错的位置:在用户空间是
cowtest
中的simpletest
,在wait(0)
后面的r_scause()
引发了错误
**可能是地址空间出现错误!**需要严格检查地址是否越界等!
但是 该错误一直没有得到解决
经过长时间的debug,发现出现错误的原因是没有正确地实现引用计数机制,出现了并发的错误。本来在实现的过程中,是想实现一点测试一点的,以为实现好了基本的功能应该能跑过简单测试,所以在排错的时候一直没有考虑是引用计数的问题!
3、减少引用计数的位置
在修改引用计数机制的时候,我是希望将引用的更新作为一个函数,然后当更新后的引用为0
的时候再调用kfree
释放物理内存,这个时候就会出现一个问题:这样实现相当于在kfree
的前面加了一层引用更新函数,在cow page
的相关实现中是可以正确调度内存的释放,但是系统的其他地方会有直接调用kfree的情况,就会导致 即使该内存页的引用不为0 该内存页还是被释放了的情况,所以减少引用计数的位置应该在kfree
中。