6.S081 lab5 lazy
#lazy page allocation
-
首先修改
sbrk系统调用,找到sys_sbrk函数。可以发现此函数增加了用户进程的堆空间并调用growproc分配物理页。由于我们需要实现lazy策略,只让p->sz增加就行了,并不直接分配物理页,而是在缺页中断分配真正的物理页。这里需要注意sbrk的参数可以是一个负数(也就是回收一部分堆空间),如果这种情况出现,可以直接调用growproc函数来处理。代码如下:growproc(int n) { uint sz; struct proc *p = myproc(); sz = p->sz; if(n > 0){ if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) { return -1; } } else if(n < 0){ sz = uvmdealloc(p->pagetable, sz, sz + n); } p->sz = sz; return 0; } -
在
usertrap中增加代码以处理缺页中断。r_stval读取了导致缺页中断发生的虚拟地址值。加入lazy_alloc函数实现懒分配,若该函数返回-1,则懒分配失败,应该杀掉进程。void usertrap(void) { ... if(r_scause() == 8){ // system call if(p->killed) exit(-1); // sepc points to the ecall instruction, // but we want to return to the next instruction. p->trapframe->epc += 4; // an interrupt will change sstatus &c registers, // so don't enable until done with those registers. intr_on(); syscall(); } else if(r_scause() == 13 || r_scause() == 15){ uint64 va = r_stval(); if (lazy_alloc(p, va) != 0){ 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; } ... }实现
lazy_alloc函数:int lazy_alloc(struct proc *p, uint64 va) { uint64 new_sz = p->sz; if (va >= new_sz || va < PGROUNDDOWN(p->trapframe->sp)){ return -1; } /* printf("page fault: %p\n", va); */ uint64 pa = (uint64)kalloc(); if (pa == 0){ return -1; } memset((void*)pa, 0, PGSIZE); va = PGROUNDDOWN(va); if (mappages(p->pagetable, va, PGSIZE, pa, PTE_W | PTE_U | PTE_R) != 0){ kfree((void*)pa); p->killed = 1; } return 0; }函数的
4~6行检查了虚拟地址的合法性,va不能超过堆空间的大小,也不能超过栈顶。分配一个物理页后,把映射关系添加到进程的页表中。 -
当进程回收时,会调用
uvmunmap,更改uvmunmap函数。该函数的作用是清理页表中的映射关系。由于我们实现了懒分配(并没有真正分配物理页),所以页表可能中找不到pte,或者找到的pte并不是有效的,此时不应该panic,因为这两种情况是可能出现的。void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) { ... for(a = va; a < va + npages*PGSIZE; a += PGSIZE){ if((pte = walk(pagetable, a, 0)) == 0) /* panic("uvmunmap: walk"); */ continue; if((*pte & PTE_V) == 0) /* panic("uvmunmap: uvmunmap: not mapped"); */ continue; ... } } -
fork系统调用会调用uvmcopy函数拷贝父进程的内存到子进程中,需要做与uvmunmap类似的处理。int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) { ... 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; ... } return 0; err: uvmunmap(new, 0, i / PGSIZE, 1); return -1; } -
如
write等系统调用可能会使用未分配物理页的虚拟地址。查看filewrite函数,当文件是管道时,函数会调用pipewrite,在pipewrite中,会调用copyin把用户空间的内存拷贝到内核中。copyin中调用了walkaddr从用户页表中得到物理地址,此时物理页可能没有分配。修改walkaddr以分配物理页。代码如下:uint64 walkaddr(pagetable_t pagetable, uint64 va) { ... pte = walk(pagetable, va, 0); if(pte == 0 || (*pte & PTE_V) == 0){ if (lazy_alloc(myproc(), va) < 0) return 0; } ... return pa; }

浙公网安备 33010602011771号