前面两题完成得比较顺利,但最后一题又花费了我很多时间来debug,感到自己对OS的理解和debug能力还有所欠缺。
Eliminate allocation from sbrk() (easy)
Try to guess what the result of this modification will be: what will break?
使用sbrk()申请了一片空间,但没有实际为其分配PTE和物理内存。因此到后续需要使用到这片空间的内容时,应该会发生页错误。
正确完成代码后,终端输出:
usertrap(): unexpected scause 0x000000000000000f pid=3
sepc=0x0000000000001258 stval=0x0000000000004008
va=0x0000000000004000 pte=0x0000000000000000
panic: uvmunmap: not mapped
几个小细节:
- "usertrap(): ..." 信息是来自trap.c的usertrap(),它捕捉了一个它无法处理的异常。
scause 0x000000000000000f说明scause寄存器低四位全为1,具体含义见下图;pid = 3说明进程id是3,pid=0的进程是init,pid=1的是,pid=2的是shell;stval=0x0..04008说明导致page fault的虚拟地址是0x4008。在shell中输入echo hi,运行echo程序,这会调用exec()系统调用,exec()丢弃刚刚fork()得到的内存空间,并使用sbrk()申请。
2.不改变进程的大小p->sz,则只发生了页错误而不会出现
panic: uvmunmap: not mapped,反之则会出现。
待补充
3. scause寄存器的内容是什么时候加载进去的?
待补充
Lazy allocation (moderate)
跟着讲义的hints就能完成,没有什么难度。
一个尚未找到答案的问题:
在lazy allocation的实现中,对于一个进程而言,其所使用的虚拟内存并没有全部映射到物理内存中(若在其运行过程中没有用到所有的页),且uvmunmap()仍使用原来的实现,则在释放该进程时,会出现panic: not mapped。
我使用的解决方案是,将这个条件分支的处理由panic改为continue;
if((*pte & PTE_V) == 0)
continue;
问题是这样的处理方式是否会导致原先应该发生panic的情况(即非lazy allocation引起的)没有发生panic,而引起一些系统的安全问题?
这个问题可以转换为“使用egear allocation时,在什么情况下,会触发这个panic?”是不是如果能够保证内核其余部分的实现是完全正确的,就无论如何都不应该会触发这个panic?
Lazytests and Usertests (moderate)
我在完成本题时,感觉讲义中的以下两条hints不太好理解:
Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write, but the memory for that address has not yet been allocated.
在一些系统调用中(如write和read)会使用到一些有效地址(在sbrk中已经注册为合法,即小于p->sz),但还没为这部分的地址分配相应物理内存与建立映射。例如下面这个语句就会从文件描述符fd2中读取sizeof(buf)个字符到buf这个数组中,但内核还没有为buf这个数组分配相应的物理内存与建立映射。
read(fd2, buf, sizeof(buf));
Handle faults on the invalid page below the user stack.
在user stack之下是guard page,text segment与data segment,sbrk系统调用与用户都不应该会使用这部分的虚拟地址。若使用到这部分虚拟地址,则说明用户栈溢出了,需要杀死该进程。 那么怎么判断该虚拟地址在是不是guard page中呢?sp寄存器保存着当前进程的用户栈的地址。我们可以由r_sp()和p->trapframe->sp两种方式获取到sp寄存器中存储的值。
遇到的bugs
1.panic: uvmunmap: walk
现象:
$ lazytests
lazytests starting
running test lazy alloc
panic: uvmunmap: walk
分析:
L2或L1级page directory的pte的PTE_V为0,则walk()函数会返回0,然后uvmunmap()就会panic: uvmunmap: walk
解决:
将uvmunmap()修改为
if((pte = walk(pagetable, a, 0)) == 0)
continue;
// panic("uvmunmap: walk");
2.内核陷入
现象:
$ lazytests
lazytests starting
running test lazy alloc
test lazy alloc: OK
running test lazy unmap
scause 0x000000000000000d
sepc=0x0000000080000d98 stval=0x0000000000000000
panic: kerneltrap
分析:
可以看见这是在内核中发生的陷入,在kernel.asm中搜索80000d98,知道这个陷入是在memmove()中发生的。搜索memmove(),发现在copyin(),copyout()和uvmcopy()中使用了该函数。再仔细检查了这三个函数,发现问题是出在uvmcopy()上。我直接将(*pte & PTE_V) == 0的条件分支注释掉了,也就是说没有建立映射的pte也会执行for循环体中剩余的指令,包括memmove()。
解决:
int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) {
...
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");
...
}
...
}
修改为:
int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) {
...
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
continue;
if((*pte & PTE_V) == 0)
continue;
...
}
...
}
3.panic: walk
现象:
$ lazytests
lazytests starting
running test lazy alloc
test lazy alloc: OK
running test lazy unmap
panic: walk
分析:
查看walk()函数,这个panic的直接原因是va变量的值大于了MAXVA。
if(va >= MAXVA)
panic("walk");
通过为walk添加一个参数cause(如下代码所示),并在不同的调用walk函数的地方传入不同cause值,再次运行lazytests,终端输出"cause: 3",由此可以确定是由uvmunmap()导致的该panic。
pte_t * walk(pagetable_t pagetable, uint64 va, int alloc, int cause) {
if(va >= MAXVA){
printf("cause: %d\n", cause);
panic("walk");
}
...
}
继续使用上述方法,确定是uvmfree()调用uvmunmap()导致的该panic:
uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 1, 2);
在释放进程时会调用uvmfree(),此时sz = p->sz,因此有可能是p->sz大于了MAXVA。导致该问题的原因可能是在为
解决:
在sys_sbrk()中限制只有当 p->sz+n <= MAXVA 时,才更新p->sz的大小。
4.test rwsbrk: panic: freewalk: leaf
现象:
test rwsbrk: panic: freewalk: leaf
分析:
该panic在Lab3 page table 中也曾遇到过:若L0页表项的PTE_V标志位没有置0,则会输出该错误信息。
$ usertests rwsbrk
usertests starting
test rwsbrk: pte:208c5c0f pid: 3
panic: freewalk: leaf
修改freewalk,让其输出出错对应的pte,可以看到其低四位标志位均为1(X,W,R,V),U为0。另外,出错的进程pid为3,使用vmprint()将pid=3的进程的页表打印出来,可以看到这个pte并不在页表之中,这似乎是一个非法的pte。
page table 0x0000000087f55000
..0: pte 0x0000000021fd4401 pa 0x0000000087f51000
.. ..0: pte 0x0000000021fd4001 pa 0x0000000087f50000
.. .. ..0: pte 0x0000000021fd481f pa 0x0000000087f52000
.. .. ..1: pte 0x0000000021fd3c1f pa 0x0000000087f4f000
.. .. ..2: pte 0x0000000021fd381f pa 0x0000000087f4e000
.. .. ..3: pte 0x0000000021fd341f pa 0x0000000087f4d000
.. .. ..4: pte 0x0000000021fd301f pa 0x0000000087f4c000
.. .. ..5: pte 0x0000000021fd2c1f pa 0x0000000087f4b000
.. .. ..6: pte 0x0000000021fd281f pa 0x0000000087f4a000
.. .. ..7: pte 0x0000000021fd241f pa 0x0000000087f49000
.. .. ..8: pte 0x0000000021fd201f pa 0x0000000087f48000
.. .. ..9: pte 0x0000000021fd1c1f pa 0x0000000087f47000
.. .. ..10: pte 0x0000000021fd181f pa 0x0000000087f46000
.. .. ..11: pte 0x0000000021fd141f pa 0x0000000087f45000
.. .. ..12: pte 0x0000000021fd101f pa 0x0000000087f44000
.. .. ..13: pte 0x0000000021fd0c1f pa 0x0000000087f43000
.. .. ..14: pte 0x0000000021fd081f pa 0x0000000087f42000
.. .. ..15: pte 0x0000000021fd040f pa 0x0000000087f41000
.. .. ..16: pte 0x0000000021fd001f pa 0x0000000087f40000
..255: pte 0x0000000021fd5001 pa 0x0000000087f54000
.. ..511: pte 0x0000000021fd4c01 pa 0x0000000087f53000
.. .. ..510: pte 0x0000000021fd9007 pa 0x0000000087f64000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000
解决:
相应代码修改为:
(va < p->sz)
&& (va >= p->trapframe->sp)
&& (((pte = walk(p->pagetable, va, 0, 0))==0) || ((*pte & PTE_V)==0));