xv6-labs-2020.lab4.lazy page allocation
lab4: lazy page allocation
1.作业链接
- https://pdos.csail.mit.edu/6.828/2020/labs/cow.html
2. 实习内容
准备工作
子进程 + 阅读给定文档的 Chapter 03/04
P1: Eliminate allocation from sbrk()
- 修改系统调用
sbrk()
,让其只增加进程的大小myproc()->sz
,返回未增长前的的地址,不分配新的内存空间 - 直接修改文件
kernel/sysproc.c
中的函数sys_sbrk()
即可
1 | // kernel/sysproc.c:sys_sbrk() |
- 这样的修改势必会导致在读取地址的时候导致 page fault,因为没有分配内存,虚拟映射失败
1 | echo hi |
1 | usertrap(): unexpected scause 0x000000000000000f pid=3 |
P2: Lazy allocation
- 响应上面的 page fault,从而能够让程序良好运行
- 需要在
usertrap()
调用printf()
之前加入一些代码进行 page fault 的判断 - 可能需要修改一些其它地方的代码
(1) 一些提示
- 可以通过
r_scause()
为 13 或者 15 来判断当前是否触发的是pagefault
r_stval()
返回导致异常的虚拟地址- 仿照
vm.c
中的uvmalloc()
代码进行分配内存- 原来
sbrk()
通过调用growproc()
间接调用到uvmalloc()
- 需要调用
kalloc()
以及mappages()
- 原来
- 使用
PGROUNDDOWN(va)
可以获取到当前虚拟页的最小地址 uvmunmap()
会调用panic()
,如果是因为没有映射造成的话,我们应该不让他调用kernel/kernel.asm
中保存着汇编代码,如果发生错误可以查看这部分代码- 如果报错
incomplete type proc
- include "spinlock.h" then "proc.h".
(2) 修改 usertrap()
- 判断异常是否为 pagefault
- 如果是则正确分配一页物理页
1 | // kernel/trap.c:usertrap() |
- 同时我们要把由于 pagefault 导致的
panic()
取消- 注释掉如下代码
1 | // kernel/vm.c:uvmunmap() |
- 由于我们现在只是实现了 lazy
的分配,还需要解决这样一个问题,如果申请了一块内存,但是尚未分配映射,此时我们不需要进行
kfree()
操作 - 整段代码修改如下
1 | // kernel/vm.c:uvmunmap() |
- 此时运行
echo hi
,能够正确的输出hi
P3: Lazytests and Usertests
- 处理一些细节问题
- 实现
sbrk()
中参数为负的情况 - 如果请求的虚拟地址,比当前进程通过
sbrk()
申请的最高地址要高的话,kill
掉 - 内核态访问的的内存已申请,但是尚未分配映射
- You can fix it by add code in
walkaddr()
inkernel/vm.c:104
, as any r/w syscall will invokewalkaddr
to get physical address.
- You can fix it by add code in
- 能够处理
fork()
中父子进程的内存复制 kalloc()
失败的时候,kill
掉当前进程- 已经实现
- 解决栈溢出的问题,栈的大小只有一页,不能访问栈之外的空间
(1) sbrk() 参数为负
- 直接调用
uvmunmap()
释放即可 - 简单实现,我们可以直接调用
growproc()
实现
1 | // kernel/sysproc.c:sys_sbrk() |
(2) 访问到未申请的空间
- 判断访问的虚拟地址是否已经申请
1 | // kernel/trap.c:usertrap() |
(3) 栈溢出
- 因为栈的大小只有一页,我们可以通过
myproc()->trapframe->sp
获取到当前栈所在的页 - 然后使用
PGROUNDDOWN
以及PGROUNDUP
获取到边界
1 | // kernel/trap.c:usertrap() |
(4) fork 问题
- 调用 fork 的时候,如果父进程存在一些申请了但是尚未分配映射的内存,子进程就不需要 copy 了
- 在进行
uvmcopy()
的时候进行修改,如果 pte 不存在,则不复制,而不是报错 - 修改如下
1 | if((pte = walk(old, i, 0)) == 0) |
(5) 内核态的内存操作
- 如果内核态需要操作一个申请了但是尚未分配映射的空间时,需要进行处理
- 例如
write()
的内存尚未分配映射 - 在
walkaddr()
中加入判断- 如果出现找不到页的情况,马上分配
- 具体的分配页的判断和
usertrap()
中的一样,于是我们将其封装为一个函数
(6) 最终的修改情况
kernel/defs.h
1 | // vm.c |
kernel/vm.c
1 | // 增加一个 lazy allocation 的函数 |
kernel/sysproc.c
1 | uint64 sys_sbrk(void) { |
kernel/trap.c
1 | void usertrap(void) { |
3. 实验结果
1 | $ make qemu-gdb |
4. 遇到的困难以及收获
- 在
sys_sbrk()
函数中的部分addr
被声明成了int
,但是实际上应该是uint64
,这个 bug 改了好久,一直都在报错panic("walk")
,看来对原始的代码也不能都相信,还得是自己重新修改下 - 有些问题的设计还是挺巧妙的,例如在内核态下遇到了 pagefault 的处理,这个在之前都没有仔细思考过
- 在学习完这一部分的 lab 之后,对整个多级页表的立即更加深入了,对操作系统在底层内存空间实现也有了更好的理解
5. 对课程或 lab 的意见和建议
- 希望中文版本能把 hint 也翻译过来(当然不是必要的)
6. 参考文献
- https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf