1.eliminate allocation from sbrk()
uint64 sys_sbrk(void) { int addr; int n; if(argint(0, &n) < 0) return -1; addr = myproc()->sz; myproc()->sz += n; //虚拟增加size,而不实际分配物理内存 //if(growproc(n) < 0) //return -1; return addr; }hart 1 starting init: starting sh $ echo hi usertrap(): unexpected scause 0x000000000000000f pid=3 sepc=0x00000000000012ac stval=0x0000000000004008 panic: uvmunmap: not mapped QEMU: Terminatedelse { 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; } //用户态发生异常进入usertrap,对比scause不是系统调用和硬件中断进入这里讲一下异常链路,shell 调用 sbrk 只改 p->sz,无物理页映射,用户写未映射地址 → 硬件抛出 scause=15 写缺页,usertrap 无缺页处理分支,打印 unexpected scause,调用exit(-1)会销毁当前进程全部资源:关闭文件、释放内存、回收 PCB。调用uvmfree->uvmunmap检测是否有效位等,打印not mapped,执行panic。
2.lazy allocation
要求:在usertrap 添加缺页处理分支,在出错地方映射新的物理内存,然后返回用户态继续执行。
- 可以通过查看kernel/trap.c中的usertrap()中的r_scause()是13还是15来检查错误是否是页面错误;
- r_stval()返回stval寄存器的值,该寄存器存放着导致页面错误的虚拟地址;
- 参考vm.c中的uvmalloc()函数代码,通过调用kalloc()和mappages()来实现本实验;
- 使用PGROUNDDOWN(va)将错误虚拟地址舍入到页面边界
- uvmunmap()将崩溃,修改这个函数使得在某些页面未映射的时候不会崩溃
- 如果内核崩溃,在kernel/kernel.asm中查找sepc
- 使用pgtbl实验中vmprint函数打印页表的内容
- 如果产生“incomplete type proc”的错误,请先include “spinklock.h"再"proc.h”
要在usertrap加额外的逻辑处理缺页异常。
syscall(); }else if(r_scause()==13 || r_scause()==15){ //13是读异常,15是写异常 printf("page fault trap: signal %d at address %p\n", r_scause(), r_stval()); uint64 va = PGROUNDDOWN(r_stval());//获取缺页异常的虚拟地址页起始地址 uint64 pa = (uint64)kalloc();//分配物理内存地址 if (pa == 0)//分配失败则杀死进程 { p->killed = 1; }else{ memset((void*)pa, 0, PGSIZE); //将分配的物理内存清零 if (mappages(p->pagetable, va, PGSIZE, pa, PTE_W|PTE_R|PTE_U) != 0) //在页表建立映射,用户态可读可写 { kfree((void*)pa); //映射失败则释放物理内存并杀死进程 p->killed = 1; } } }else if((which_dev = devintr()) != 0){还得在vm.c的uvmunmap()函数无效位,以及walk获取pte去除,因为sbrk可能size虚拟的较大,而惰性分配只在真正访问某一页时才创建映射。而进程退出时会调用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;//添加 //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; } }3.Lazytests and Usertests
要求:继续修改代码
- 修改sbrk()以应对负参数的情况;
- 如果进程出现页面错误的虚拟地址高于sbrk分配的虚拟内存地址,那么中止该进程
- 正确处理fork中的父子内存副本;这里应该是实现写时复制的fork;
- 进程能成功应对当sbrk分配的有效虚拟地址被传递给系统调用但尚未分配该物理地址内存的情况。
- 处理物理内存不足的情况:当klloc()在页面错误情况下内存分配失败,则终止当前进程
- 处理页面错误虚拟地址出现在栈下方时候的情况,这个时候的虚拟地址是无效
1.处理sbrk()负数情况
sys_sbrk(void) { int addr; int n; if(argint(0, &n) < 0) return -1; addr = myproc()->sz; if(n > 0){ myproc()->sz += n; //虚拟增加size,而不实际分配物理内存 }else if (myproc()->sz + n < 0) { return -1; }else{ int sz =uvmdealloc(myproc()->pagetable, myproc()->sz, myproc()->sz + n); //uvmdealloc解除映射,返回新的进程size myproc()->sz = sz; } //if(growproc(n) < 0) //return -1; return addr; }2.usertrap分支再加判断
syscall(); }else if(r_scause()==13 || r_scause()==15){ //13是读异常,15是写异常 //printf("page fault trap: signal %d at address %p\n", r_scause(), r_stval()); uint64 va = PGROUNDDOWN(r_stval());//获取缺页异常的虚拟地址页起始地址 if(va + PGSIZE - 1 <= p->sz && va > PGROUNDDOWN(p->trapframe->sp)){ //保证页面报错的虚拟地址只能在栈上面和p->sz的下面 uint64 pa = (uint64)kalloc();//分配物理内存地址 if (pa == 0)//分配失败则杀死进程 { p->killed = 1; }else{ memset((void*)pa, 0, PGSIZE); //将分配的物理内存清零 if (mappages(p->pagetable, va, PGSIZE, pa, PTE_W|PTE_R|PTE_U) != 0) //在页表建立映射,用户态可读可写 { kfree((void*)pa); //映射失败则释放物理内存并杀死进程 p->killed = 1; } } }else{ p->killed = 1; //不在范围内则杀死进程 } }else if((which_dev = devintr()) != 0){ // ok } 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.fork()里的uvmcopy,出现wlak获取pte为0及无效位,continue
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) //panic("uvmcopy: pte should exist"); continue; //添加 if((*pte & PTE_V) == 0) //panic("uvmcopy: page not present"); 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;