文章目录
- Eliminate allocation from sbrk()
- Lazy allocation
- task
- hints
- 实现
- Lazytests and Usertests
- task
- hints
- 实现
Eliminate allocation from sbrk()
第一个任务是去阻止sysproc.c
中的sys_sbrk()
函数真的分配内存,只需要增p->sz
即可
一行代码+注释即可
uint64
sys_sbrk(void) {
int addr;
int n;
if (argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
myproc()->sz += n;
// if(growproc(n) < 0)
// return -1;
return addr;
}
这时候会发生两个错误
- usertrap的错误
- 页面错误是由硬件触发的,当硬件发现pte中的有效位为0时,就会陷入内核,并且设置
r_scause
为页错误相关的标志
- 页面错误是由硬件触发的,当硬件发现pte中的有效位为0时,就会陷入内核,并且设置
- uvmunmap错误
- 有可能一个进程申请了一块内存,但是却没有真正使用,所以页表中是没有对应的一项的
- 结果在之后销毁这块内存的时候,还是会去页表中删除这一项,这一删就发现, 根本没有被mapped
Lazy allocation
task
-
修改
trap.c
中的代码,使其能够对用户空间的页错误进行反应通过映射一个新分配的物理内存页到出错的地址,并返回到用户空间使得进程继续执行
-
视情况修改其他的内核代码
hints
- 你可以通过检查
r_scause
是13还是15来判断是否是一个页错误 r_stval()
存储了引起错误的虚拟地址- 从
uvmalloc
函数学习,你需要调用kalloc
和mappages
- 使用
PGROUNDDOWN(va)
去得到虚拟页面的地址 - 修改
uvmunmap()
使其在碰到一个页面没有被mapped时不要panic - 如果内核崩了,在汇编代码中查看sepc
- 使用你的
vmprint
函数去打印页表 - 如果出现
incomplete type proc
,去includespinlock.h
和proc.h
实现
usertrap
中增加一个else if分支,关键操作就是kalloc和mappages,即申请一个操作,再增加一个映射
} else if (r_scause() == 13 || r_scause() == 15) {
uint64 va = r_stval();
// 虚拟地址本来就不合法
if (va >= p->sz) {
p->killed = 1;
} else {
uint64 pa = (uint64)kalloc();
// 内存用完了, 杀死进程
if (pa == 0) {
p->killed = 1;
} else {
va = PGROUNDDOWN(va);
// 增加映射,如果失败了
if (mappages(p->pagetable, va, PGSIZE, pa, PTE_W | PTE_X | PTE_R | PTE_U) != 0) {
kfree((void *)pa);
p->killed = 1;
}
}
}
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
uvmunmap
中修改为
if ((*pte & PTE_V) == 0)
continue;
就成功了
Lazytests and Usertests
task
- 通过
lazytests
和usertests
hints
- 处理sbrk的参数为负数的情况
- 如果一个进程访问的虚拟地址超过了通过sbrk分配的
- 正确处理
fork
中父进程到子进程的内存拷贝 - 正确处理一个进程通过系统调用read或者write传递了一个合法的虚拟地址,但是对应的内存地址却没有被分配
- 正确处理内存用完的限制,即kalloc如果失败,则杀死进程
- 处理用户堆栈下面无用的页面错误,即guardpage
一个一个hins来
实现
第1个是参数为负数,特判一下就行,如果是负数,就按原来的方式处理
if (n > 0) {
myproc()->sz += n;
} else {
if (growproc(n) < 0)
return -1;
}
第2个和第6个通过这个判断就可以搞定
va >= p->sz || va < p->trapframe->sp
有个之前没搞清楚的点,那就是通过p->trapframe->sp取得的是栈顶指针,栈指针又是向下走的。而栈指针往下,除了一个guardpage是没有映射之外,其他的page都应该是有映射的,即pte的有效位为0。因此,如果引发了页故障并且地址还低于栈顶指针,说明肯定是碰到了guardpage的
第3个其实说的是uvmcopy
函数,这个函数就是将父进程的页表原模原样的拷贝给子进程,但是有的虚拟页面连父节点自己都没有映射,子节点哪来的映射,因此碰到找不到pte或者pte有问题的情况,直接continue就行了
if ((pte = walk(old, i, 0)) == 0)
continue;
// panic("uvmcopy: pte should exist");
if ((*pte & PTE_V) == 0)
continue;
// panic("uvmcopy: page not present");
第4个的问题在于,如果是系统调用,那它会先在usertrap中进入syscall的处理流程,不会进入我们新添加的流程,所以会有问题
通过对write函数的不断追踪,可以发现,牵扯到的是walkaddr函数。其实就是现在需要使用这个地址了,但是原始的walkaddr不能正确返回一个物理地址,因为压根就没有。所以在walkaddr中增加创建映射的代码即可
if ((*pte & PTE_V) == 0) {
struct proc *p = myproc();
// 地址不合法
if (va >= p->sz || va < p->trapframe->sp) {
return 0;
}
uint64 pa = (uint64)kalloc();
if (pa == 0) {
panic("memory run out\n");
return 0;
}
if (mappages(p->pagetable, va, PGSIZE, pa, PTE_W | PTE_X | PTE_R | PTE_U) != 0) {
kfree((void *)pa);
return 0;
}
// *pte = PA2PTE(pa) | PTE_W | PTE_X | PTE_R | PTE_U | PTE_V;
}
第5点,只要你用了kalloc后判断了它的返回值,然后panic就行