操作系统可以在页表硬件上玩的许多巧妙的把戏之一是堆内存的惰性分配。Xv6应用程序使用sbrk()系统调用向内核请求堆内存。在我们给你的内核中,sbrk()分配物理内存并将其映射到进程的虚拟地址空间。有些程序分配内存,但从不使用它,例如实现大型稀疏数组。复杂的内核会延迟每个内存页面的分配,直到应用程序尝试使用该页面——这是由页面错误发出的信号。在本练习中,您将把这个惰性分配特性添加到xv6中。
Part One: Eliminate allocation from sbrk()
实验内容
修改sbrk(n)。sbrk()本来是将进程的内存大小增加n个字节,并且返回新分配区域的起始地址。现在需要改成只将进程的大小(myproc()->sz)增加n但不分配区域,并返回原来的大小。
实验过程
修改前,make qemu, 在其中输出echo hi
修改代码
int
sys_sbrk(void)
{
int addr;
int n;
if(argint(0, &n) < 0)
return -1;
addr = myproc()->sz;
/* if(growproc(n) < 0)
return -1;*/
myproc()->sz+=n;
return addr;
}
修改后
"pid 3 sh:trap …"消息来自trap.c中的陷入内核处理程序; 它捕获了页面错误(陷入14或T_PGFLT),xv6内核不知道如何处理。addr 0x4004
表示导致页面错误的虚拟地址为0x4004。
Part Two: Lazy allocation
实验内容
修改trap.c中的代码,通过将新分配的物理内存页面映射到错误地址来响应用户空间的页面错误,然后返回到用户空间,让进程继续执行。在产生“pid 3 sh: trap 14”消息的cprintf调用之前添加代码。
实验过程
基本上就是按照官方给的提示来就可以。仿照vm.c中allocuvm的代码(基本上就是一模一样,只是注意一下参数)。下面的代码还需注意一下mappages()函数的声明, 按照提示来即可,就没有展示出来了。另外关于deallocuvm(pgdir,newsz,oldsz)中oldsz这个参数,结合sys_sbrk()系统调用的实现可知sys_sbrk()返回的就是oldsz,我在网上查了一下发现系统调用返回的值是存储在%eax里的,所以oldsz这个参数对应的值就是myproc()->tf->eax。
default:
if(myproc() == 0 || (tf->cs&3) == 0){
// In kernel, it must be our mistake.
cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n",
tf->trapno, cpuid(), tf->eip, rcr2());
panic("trap");
}
// char *mem;
// uint a;
a = PGROUNDDOWN(rcr2());
for(;a<myproc()->sz;a+=PGSIZE){
mem = kalloc();
if(mem==0){
cprintf("allocuvm out of memory\n");
deallocuvm(myproc()->pgdir,myproc()->sz,myproc()->tf->eax);
return ;
}
memset(mem,0,PGSIZE);
if(mappages(myproc()->pgdir,(char *)a, PGSIZE, V2P(mem), PTE_W|PTE_U)<0){
cprintf("alloccuvm out of memory(2)\n");
deallocuvm(myproc()->pgdir,myproc()->sz,myproc()->tf->eax);
kfree(mem);
return ;
}
// cprintf("mem\n");
}
break;
// In user space, assume process misbehaved.
cprintf("pid %d %s: trap %d err %d on cpu %d "
"eip 0x%x addr 0x%x--kill proc\n",
myproc()->pid, myproc()->name, tf->trapno,
tf->err, cpuid(), tf->eip, rcr2());
myproc()->killed = 1;
}