lab6 也是虚拟内存的一种应用,主要实现fork时的写时复制copy-on-write功能。
问题
xv6中的fork()系统调用将所有父进程的用户空间内存复制到子进程中。如果父进程用户空间很大,复制可能需要很长时间。更糟糕的是,这种复制工作经常被浪费;例如,子进程中的fork()后跟exec()将导致子进程丢弃复制的内存,可能根本就没有使用复制来的很大一部分内存。另一方面,如果父类和子类都使用一个页面,而其中一方或双方都编写该页面,则确实需要一个副本。
解决方法
写时拷贝(COW) fork()的目标是推迟为子进程分配和复制物理内存页,直到实际需要复制时才进行分配和复制。
COW fork()仅为子进程创建一个分页表,用户内存的pte指向父进程的物理页。COW fork()将父级和子级的所有用户pte标记为不可写。当任何一个进程尝试写其中一个COW页面时,CPU将出现页面错误。内核页错误处理程序检测到这种情况,为出错进程分配一个物理内存页,将原始页复制到新页,并在出错进程中修改相关的PTE以引用新页,这一次PTE标记为可写。当页面错误处理程序返回时,用户进程就能够写入该页面了。
COW fork()使得释放用户内存的物理页变得更为麻烦。一个给定的物理页可能被多个进程的页表引用,并且只有当最后一个引用消失时才应该被释放。
Implement copy-on write
实现:
在kalloc.c中增加物理内存引用计数
分配内存时初始化引用计数为1
释放内存时查看引用计数是否为0,注意kinit会调用一次kfree
获取和增加引用计数的函数,减少的话可以直接调用kfree。对引用计数全程不加锁,这个lab也可以通过tests
增加COW标志位,可以使用预留的第8位或者第9位
vm.c 里uvmcopy函数修改为只将子进程页表映射到父进程的物理地址,去掉写标志位,增加COW标志位,增加物理内存引用计数
trap.c里usertrap增加页面错误的处理,需判断是否是COW
vm.c里加了个walkpte函数,就是修改了walkaddr函数返回值,这个函数返回pte
最后copyout也要增加页面错误处理,因为copyout是在内核中调用的,缺页不会进入usertrap。
总结
COW在实现上主要问题体现在 物理页面的引用计数实现以及判断是否为cow页面 这两个方面。理清了思路就比较好理解,之后就是一些实现细节方面,最容易出现usertrap错误,这时可以根据sepc查看在哪个地方出现错误然后解决。