这个实验主要是让我们实现内存的延迟分配用户空间堆内存,当我们需要分配堆空间时候,不是立即分配内存给它,而是记录分配了哪些用户地址,并在用户页表中将这些地址标记为无效。当进程第一次尝试使用延迟分配中给定的页面时,CPU生成一个页面错误,内核通过分配物理内存、置零并添加映射来处理该错误。
Eliminate allocation from sbrk
你的首项任务是删除sbrk(n)系统调用中的页面分配代码(位于sysproc.c中的函数sys_sbrk())。sbrk(n)系统调用将进程的内存大小增加n个字节,然后返回新分配区域的开始部分(即旧的大小)。新的sbrk(n)应该只将进程的大小(myproc()->sz)增加n
//sysproc.c
uint64
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
if(n > 0) myproc()->sz += n;
if(myproc()->sz + n >= 0 && n < 0){
myproc()->sz = uvmdealloc(myproc()->pagetable, myproc()->sz, myproc()->sz + n);//n小于零则删除多余的内存。
}
if(myproc()->sz + n < 0 && n < 0) return -1;
//if(growproc(n) < 0)
//return -1;
return addr;
}
我们将sbrk()中分配内存给删除,在sbrk中我们只将要分配的内存进行标记,并不实际分配物理内存。
Lazy allocation
修改trap.c中的代码以响应来自用户空间的页面错误,方法是新分配一个物理页面并映射到发生错误的地址,然后返回到用户空间,让进程继续执行
//trap.c
else if(r_scause() == 13 || r_scause() == 15){ //当出现页面错误中断时
uint64 addr;
char *mem;
addr = r_stval();//获取当前出错的地址
if(addr >= p->sz) p->killed = 1;//如果大于已分配的内存,则杀死进程
else if(addr < p->trapframe->sp) p->killed = 1;//如果地址在栈指针的下面,则杀死内存
else {
addr = PGROUNDDOWN(addr);
mem = kalloc();
if(mem == 0) {
p->killed = 1;
} else {
memset(mem, 0, PGSIZE);
if(mappages(p->pagetable, addr, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U|PTE_V) != 0) {
kfree(mem);
p->killed = 1;
}
}
}
}
Lazytests and Usertests
进行一些细节的修改
//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;
}
} //注释掉panic,当发现PTE无效时,跳过。
//在uvmcopy中也进行相同的操作。
//syscall.c
//当我们在内核空间时,不会触发页错误,但是我们需要调用地址时需要使用argaddr函数,所以当我们调用argaddr函数时,如果发现用户地址被标记但未被分配,我们为此地址分配页面。
int
argaddr(int n, uint64 *ip)
{
char *mem;
*ip = argraw(n);
struct proc *p = myproc();
if(walkaddr(p->pagetable, *ip) == 0) {
if(*ip >= myproc()->sz) return -1;
if(*ip < PGROUNDUP(p->trapframe->sp)) return -1;
mem = kalloc();
if(mem == 0) return -1;
memset(mem, 0, PGSIZE);
if(mappages(myproc()->pagetable, PGROUNDDOWN(*ip), PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0) {
kfree(mem);
return -1;
}
}
return 0;
}
当我们分配内存时,有很多内存也许短时间内不会用到,而马上分配实际内存会浪费过多的时间,我们可以到需要时才分配实际内存。