Lab 5:Lazy Page Allocation
课程地址:https://pdos.csail.mit.edu/6.828/2020/schedule.html
Lab 地址:https://pdos.csail.mit.edu/6.828/2020/labs/lazy.html
代码仓库:https://github.com/ajackchan/my-xv6-riscv/tree/lazy
Lazytests and Usertests
实现一个内存页懒惰分配机制,在调用 sbrk() 的时候,不立即分配内存,而是只作记录。在访问到这一部分内存的时候才进行实际的物理内存分配。
1.修改 sys_sbrk()
函数
在 kernel/sysproc.c
中修改 sys_sbrk()
函数,只修改虚拟内存大小而不立即分配物理内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| uint64 sys_sbrk(void) { int addr; int n; struct proc *p = myproc(); if(argint(0, &n) < 0) return -1; // 获取参数失败,返回错误 addr = p->sz; // 获取当前进程的地址空间大小 if(n < 0) { uvmdealloc(p->pagetable, p->sz, p->sz+n); } p->sz += n; // 调整虚拟地址空间大小 return addr; // 返回原始大小 }
|
2.用户态异常处理 (usertrap()
)
在 kernel/trap.c
中处理缺页异常,并且在需要时分配物理内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void usertrap(void) { ...... syscall(); } else if((which_dev = devintr()) != 0){ // ok } else { uint64 va = r_stval();
if((r_scause() == 13 || r_scause() == 15) && uvmshouldtouch(va)){ // 缺页异常,并且发生异常的地址进行过懒分配 uvmlazytouch(va); // 分配物理内存,并在页表创建映射 } 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; } } ...... }
|
3.物理内存分配 (uvmlazytouch()
)
在 kernel/vm.c
中实现物理内存的分配和映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // 触摸一个懒惰分配的页面,将其映射到一个实际的物理页面。 void uvmlazytouch(uint64 va) { struct proc *p = myproc(); char *mem = kalloc(); if (mem == 0) { // 分配物理内存失败 printf("lazy alloc: out of memory\n"); p->killed = 1; } else{ memset(mem, 0, PGSIZE); // 初始化内存
uint64 rounded_va = PGROUNDDOWN(va); if (mappages(p->pagetable, rounded_va, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0) { printf("lazy alloc: failed to map page\n"); kfree(mem); // 映射失败时释放已分配的内存 p->killed = 1; } } }
|
4.地址验证 (uvmshouldtouch()
)
在 kernel/vm.c
中,添加一个函数来验证是否需要懒加载处理。
1 2 3 4 5 6
| int uvmshouldtouch(uint64 va) { struct proc *p = myproc(); return va < p->sz // 地址在进程内存范围内 && PGROUNDDOWN(va) != r_sp() // 非栈的保护页 && (walk(p->pagetable, va, 0) == 0 || (*(walk(p->pagetable, va, 0)) & PTE_V) == 0); }
|
5.修改其他内核函数
对其他内核函数做适当修改,以支持懒加载机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| // uvmunmap: 取消虚拟地址映射 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; if((*pte & PTE_V) == 0) continue; if(PTE_FLAGS(*pte) == PTE_V) panic("uvmunmap: not a leaf"); if(do_free){ uint64 pa = PTE2PA(*pte); kfree((void*)pa); } *pte = 0; } }
|
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 30
| // uvmcopy: 将父进程的页表以及内存拷贝到子进程 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; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| // copyin() 和 copyout(): 内核/用户态之间互相拷贝数据 int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) { uint64 n, va0, pa0;
if(uvmshouldtouch(dstva)) uvmlazytouch(dstva);
// ......
}
int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len) { uint64 n, va0, pa0;
if(uvmshouldtouch(srcva)) uvmlazytouch(srcva);
// ......
}
|
测试结果
lazytests测试
usertests测试