Lab:xv6 lazy page allocation
Eliminate allocation from sbrk()
实验目标
本实验你的任务是修改 sys_sbrk() 使得其只增加/减少进程地址空间大小,而不真正地分配页面
实验实现
/* sysproc.c */
uint64
sys_sbrk(void)
{
uint64 addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
uint64 newaddr = addr + n;
if(newaddr >= MAXVA || newaddr <= 0)
return addr;
// if(n < 0)
// {
// if (uvmdealloc(myproc()->pagetable, addr, newaddr) != newaddr)
// return -1;
// }
myproc()->sz = newaddr;
return addr;
}
Lazy allocation + Lazytests and Usertests
这里建议两个一起做了,免得回头还要修改
实验目标
本实验中,你的目标是修改trap.c和其他地方的代码,使得在你的代码能够在缺页时分配一块新的内存并建立映射
实验实现
先看提示
- 你可以用 r_scause() 来查看错误类型,13为读缺页,15为写缺页
- r_stval() 返回寄存器 staval 的值, 该寄存器存储着引起缺页的虚拟地址
- 对于页面分配,你可以参考 growproc() 和 uvmalloc() . 你将会调用 kalloc() 和 mappages()
- 使用 PGROUNDDOWN(va) 将出错的虚拟地址向下舍入到页面边界。
- 如果一个缺页发生在高于 sys_sbrk() 分配的虚拟地址,杀死这个进程
- 只处理用户空间下的缺页错误
根据以上提示,我们可以先完成trap.c 中缺页处理这部分的代码
主要思路如下
-
先检查是否为缺页错误
-
检查
-
若为缺页错误则使用 r_stval() 获取虚拟地址并检查是否越界
-
仿照 uvmalloc() 分配内存并建立虚拟地址到内存的映射
/* trap.c */
void
usertrap(void)
{
if(r_scause() == 8){
......
} else if(r_scause() == 13 || r_scause() == 15){
uint64 va = r_stval();
if(va >= p->sz || va <= PGROUNDDOWN(p->trapframe->sp)) p->killed = 1;
else
{
uint64 pa = (uint64)kalloc();
/*
此处踩坑!!!!!!!!!!!!!!!!!!!
误把p->killed = 1当return 用了
直接
if(pa == 0) p->killed = 1;
memset((void *)pa, 0, PGSIZE);
if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, (uint64)pa, PTE_W|PTE_X|PTE_R|PTE_U) != 0)
p->killed = 1;
导致有的内存没分配到,下面的memset出错,好久没找到。。。
*/
if(pa == 0) p->killed = 1;
else
{
memset((void *)pa, 0, PGSIZE);
if(mappages(p->pagetable, PGROUNDDOWN(va), PGSIZE, (uint64)pa, PTE_W|PTE_X|PTE_R|PTE_U) != 0)
p->killed = 1;
}
}
}......
}
继续看提示,还有各种小情况要处理
- 修改uvmunmap() ,使它不会在虚拟地址未映射的情况下报错
/* vm.c */
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
uint64 a;
pte_t *pte;
if((va % PGSIZE) != 0)
panic("uvmunmap: not aligned");
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
continue;// panic("uvmunmap: walk");
if((*pte & PTE_V) == 0)
continue;// panic("uvmunmap: not mapped");
if(PTE_FLAGS(*pte) == PTE_V)
panic("uvmunmap: not a leaf");
if(do_free){
uint64 pa = PTE2PA(*pte);
kfree((void*)pa);
}
*pte = 0;
}
}
- 处理用户空间地址减少的情况
这里我们直接仿照growproc() 的做法减少就行
/* sysproc.c */
uint64
sys_sbrk(void)
{
uint64 addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
uint64 newaddr = addr + n;
if(newaddr >= MAXVA || newaddr <= 0)
return addr;
if(n < 0)
{
if (uvmdealloc(myproc()->pagetable, addr, newaddr) != newaddr)
return -1;
}
myproc()->sz = newaddr;
return addr;
}
- 处理 fork() 中子进程复制父进程内存的情况
在 fork() 中我们可以看到使用 uvmcopy() 这个函数来复制父进程的页表,而在复制父进程页表的过程中自然会发现未映射的情况从而报错,我们同uvmunmap() 中修改即可
/* vm.c */
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
pte_t *pte;
uint64 pa, i;
uint flags;
char *mem;
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
continue;
if((*pte & PTE_V) == 0)
continue;
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte);
if((mem = kalloc()) == 0)
goto err;
memmove(mem, (char*)pa, PGSIZE);
if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
kfree(mem);
goto err;
}
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}
- 当调用系统调用如 read,write 的时候,内核会访问未被分配的页表,此时不会进入 usertrap, 这时也需要分配
(这个找了好久)
sys_read()->readi()->either_copyout()->copyout()->walkaddr()
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
pte_t *pte;
uint64 pa;
if(va >= MAXVA)
return 0;
pte = walk(pagetable, va, 0);
if(pte == 0 || (*pte & PTE_V) == 0)
{
struct proc *p = myproc();
if (va >= p->sz || va < PGROUNDUP(p->trapframe->sp)) return 0;
uint64 pa = (uint64)kalloc();
if (pa == 0) return 0;
if (mappages(p->pagetable, va, PGSIZE, pa, PTE_W|PTE_X|PTE_R|PTE_U) != 0)
{
kfree((void*)pa);
return 0;
}
return pa;
}
if((*pte & PTE_U) == 0)
return 0;
pa = PTE2PA(*pte);
return pa;
}