在这个实验中,需要基于page fault机制,为xv6实现lazy allocation的功能。
lazy allocation要做的是就是当用户调用sbrk为进程申请增加内存空间时,不立刻为进程分配物理内存,而是等到用户第一次访问到某个没有被分配过的页面时,再为进程分配物理内存并建立映射。
要在xv6中实现这个功能,第一件要做的事就是修改sbrk系统调用的实现,sbrk基本只是调用了下growproc函数,所以修改growproc即可。
这里修改的仅仅是当n大于0的情况,由于现在是lazy allocation,所以不急着马上分配物理内存,而是简单的修改进程的sz大小。对于n小于0的情况不用做修改。
现在一个进程的所拥有的页面可能还没有被分配,在uvmunmap执行时会unmap到不存在的页。在未修改的xv6中(eager allocation)这是不应该发生的情况,会导致内核panic。但在lazy allocation情况下,这是可能发生的正常现象,所以需要修改uvunmap。把原来panic的地方改成continue。
用户在访问到未分配的页面是会发生page fault进入内核,我们需要在usertrap()中处理这一情况。从riscv手册可知,当scause寄存器的值等于13或15时,说明发生了page fault。
在处理page fault时,从stval寄存器获取到引发page fault的虚拟地址va。判断起是否是lazy allocation情景下的合法地址。如果va 大于等于 进程的地址或者 小于 进程的栈顶地址sp,都是不合法的。然后为进程分配物理页并建立映射即可。这里需要注意页面的权限不要忘了加上PTE_U(因为我们是在为用户进程分配页面),如果忘记的话进程会再次在该页面上触发page fault(因为没有访问权限),再次map同一虚拟地址所在页面为导致xv6内核panic: remap。
现在还需要考虑一种情形,在fork时,xv6内核会将父进程的页面复制给子进程。这些页面中有些页面很可能还没被分配,未修改的xv6版本中复制未分配的页会导致panic。所以需要修改uvmcopy函数,在遇到未分配的页面时跳过即可。
还有一种情形是,如果用户是通过系统调用(如write,read)访问一个未分配的页,那么此时不会在用户态发生page fault(因为实际执行访存指令的时候已经在内核态了)。我们需要在这种情形下为进程分配页面。最简单的方法就是修改argaddr函数。
当系统调用的参数是个地址va时,在内核中可以通过walkaddr知道va是否已经分配了物理内存。如果没有,则为其分配物理内存并建立映射。
到这里lab lazy就结束了。lazytests和usertests都能完美通过。