Lab Lazy
前置知识
一、原理
- 因为绝大多数情况,程序并不会全部使用它一开始申请的内存,所以eager allocation常常会出现浪费,为了避免这种情况,可以使用lazy allocation。
- 在lazy allocation中,每次程序申请page的时候,并不会直接给它分配page,而是表面上给它在列表中分配相应虚拟page,但并不给它分配实际物理page。这样,当程序使用这个page时,一定会发生找不到物理page的page fault。此时便可以分配page了。
task: Eliminate allocation from sbrk
- 这是一个很简单的task,只需要实现sbrk中只增加sz不分配page的操作就好了。
- 因为后续task会增加更多的限制,不妨在这里就先考虑周全。
- 首先,n可正可负,当n为负数时,需要释放page
- 其次,sz在变化n后,不能超过堆顶MAXVA,也不能把不属于该进程的负数区释放掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//sysproc.c
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
struct proc *p = myproc();
addr = p->sz;
// if(growproc(n) < 0)
// return -1;
if (p->sz + n < 0 || p->sz + n > MAXVA)//是否在堆中
return -1;
if (n < 0) {
uvmdealloc(p->pagetable, p->sz, p->sz + n);
}
p->sz += n;
return addr;
}
task: Lazy allocation
- 在上一个task,我们只修改了sz,并没有真正分配物理页,因此首先需要在page fault跳转到的usertrap中分配page。注意,从xv6book中我们可以知道,只有load和store page fault的出现才意味着lazy需要allocation
- 在这个lab中,无论是内存耗尽还是映射失败,都需要立刻kill进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//trap.c/ usertrap()
...
else if((which_dev = devintr()) != 0){
// ok
} else if ((r_scause() == 13) || (r_scause() == 15)) {//load 13; store 15
// 返回出错的虚拟地址
uint64 va = r_stval();
// 不能添加printf, 否则答案不正确
if ((va >= p->sz) || (va < p->trapframe->sp)) {//越过了申请页表的指针或不在堆的范围内
p->killed = 1;
} else {
uint64 ka = (uint64) kalloc();
if (ka == 0) {//物理page申请失败
p->killed = 1;
} else {
memset((void*)ka, 0, PGSIZE);
va = PGROUNDDOWN(va);//page取整
if(mappages(p->pagetable, va, PGSIZE, ka, PTE_W|PTE_R|PTE_U) != 0) {//映射失败
kfree((void*)ka);
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;
}
... - 这时候,echo hi 会报panic,这是因为在没有lazy的xv6中,取消映射一个没有分配物理地址的page或者一个没有映射的页表显然是不合法的,但是加上lazy后,这些情况便会出现在lazy中,(比如,你增加了sz却没有分配page,最后释放的这些假page一定没有物理地址),因此我们需要取消这些panic生成。注意,头文件该加就加,小心顺序
1
2
3
4
5
6
7
8
9
10//vm.c/ uvmunmap()
...
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;//just ok
// panic("uvmunmap: not mapped");
... - 关于为何需要continue walk出错后还需要continue页表无效,可以看到walk函数的实现,for循环中只循环前两层页表,因此当前两层页表索引出现无效时会返回0,但是它不会检查最后一层指向的PTE是否无效而是会直接返回,因此需要做两层检查。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//walk
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va >= MAXVA)
panic("walk");
for(int level = 2; level > 0; level--) {//2 level
pte_t *pte = &pagetable[PX(level, va)];
if(*pte & PTE_V) {
pagetable = (pagetable_t)PTE2PA(*pte);
} else {
if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
return 0;
memset(pagetable, 0, PGSIZE);
*pte = PA2PTE(pagetable) | PTE_V;
}
}
return &pagetable[PX(0, va)];
}
Task: Lazytests and Usertests
- 当我们调用fork函数时,在将parent页表拷贝给child时,当遇到和uvmunmap中的“假page”的时候,不需要拷贝,也不能触发panic,直接跳过就好。因为你会发现如果继续执行的话,uvmcopy函数会为child的”假page”分配物理页,这显然是不对的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//vm.c/ uvmcopy()
for(i = 0; i < sz; i += PGSIZE){
if((pte = walk(old, i, 0)) == 0)
continue;
//panic("uvmcopy: pte should exist");
if((*pte & PTE_V) == 0)
continue;
//panic("uvmcopy: page not present");
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte);
if((mem = kalloc()) == 0)//this is why we continue
goto err;
memmove(mem, (char*)pa, PGSIZE);
if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
kfree(mem);
goto err;
}
}
return 0; - 最后,在调用syscall read, write时,会进入到kernel中,此时不能直接使用user虚拟地址,需要通过walkaddr函数来模拟一个MMU,此时如果找到一个没有被分配物理页的page,那么就给它分配一个。注意,按理说,sys_read()->readi()->either_copyout()->copyout()->walkaddr(),因此可以修改walkaddr,也可以修改copyout和copyin,我尝试修改walkaddr,但不知为何usertests不过,后来只能退而求其次。(难道是没有及时kill进程吗,等有空了试试)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//vm.c/ copyin(),copyout()
while(len > 0){
va0 = PGROUNDDOWN(dstva);
pa0 = walkaddr(pagetable, va0);
if (pa0 == 0) {
if ((va0 >= p->sz) || (va0 < p->trapframe->sp)) {
return -1;
} else {
pa0 = (uint64)kalloc();
if (pa0 == 0) {
p->killed = 1;
} else {
memset((void *)pa0, 0, PGSIZE);
va0 = PGROUNDDOWN(va0);
if(mappages(p->pagetable, va0, PGSIZE, pa0, PTE_W|PTE_R|PTE_U) != 0) {
kfree((void *)pa0);
p->killed = 1;
}
}
}
}