xv6 中 sbrk 的实现默认是 eager allocation,也就是一旦用户进程申请了内存,那么内核马上就会分配。但实际上,用户进程难以估量自己需要多少内存,所以往往会额外申请,导致内存消耗增加,并且有部分内存永远不会被用到。
所以可以用 lazy page allocation 来解决这个问题,sbrk 只用来记住分配了哪些用户地址(即更新 sz),而不先分配内存,直到产生了 page fault 再分配内存。
Lazytests and Usertests
partⅠ和 partⅡ 的话,Frans 教授在课上讲过了,这里就不重复了。partⅢ 就是实现 Frans 教授说的要对 xv6 做进一步的修改,这些修改都已经写在 Hints 中了:
- 处理 sbrk 的参数为负数的情况;
- 如果发生 page fault 的地址比 sbrk 分配的地址还大的时候,杀死进程;
- 处理用户进程通过系统调用传递了一个正确的地址,但是这个地址还没有被分配内存的情况(即修改copyin 和 copyout 等函数)。
- 如果发生 page fault 后没有可用内存了,杀死进程;
- 如果发生 page fault 的地址访问到了 guard page,杀死进程。
第一个要修改的地方在 kernel/sysproc.c#sys_sbrk 中:
1 | uint64 |
在前两个部分中,我们在 kernel/trap.c#usertrap 中添加了代码,那么对它进行进一步的修改,添加了一个 pgfhandler 函数,用来处理指定虚拟地址发生的 page fault:
1 | // ... |
在这个函数中,我们判断是否能够进行 page fault handle,如果不行就杀死这个进程。做了以下判断:
- 该虚拟地址是否在堆中;
- 该虚拟地址是否访问到了 guard page;
- 是否还有物理内存可以分配;
- 新分配的页是否已经映射了,防止报 remap。
如果可以处理,那么就进行处理,否则返回 0,在 usertrap 中将 p->killed 标识为 1(如果在这里面标识为 1 的话会杀死初始进程)。
1 | int |
注意要把 kernel/vm.c#uvmunmap 和 kernel/vm.c/uvmcopy 函数中的 panic 去掉,直接 continue 即可,因为使用了 lazy allocation 之后,在 unmap 的时候会出现 walk 不出来或者本来就没有 map 的情况,不能 panic,在 uvmcopy 的时候也会出现 walk 不出来,或者复制到了一个无效的 pte。代码就不贴出来了。
最后一步,修改 kernel/vm.c#copyin, kernel/vm.c#copyinstr, kernel/vm.c#copyout,将需要访问的用户空间地址做一个预处理,也就是调用 pgfhandler 先进行一波缺页处理,否则这些函数可能访问到没有分配的内存。
总结
本次 lab 相对简单,尤其是 Frans 教授上课已经把前两部分讲了,也很详细的讲了 page fault 是如何处理的。就是有一些细节要注意,我最开始就没想到要处理 copyin 和 copyout。