xv6应用程序使用sbrk()系统调用向内核请求堆内存。在我们给出的内核中,sbrk()分配物理内存并将其映射到进程的虚拟地址空间。有些程序分配内存,但从不使用它,例如实现大型稀疏数组。复杂的内核会延迟对每个内存页的分配,直到应用程序尝试使用该页面——这是由页面错误发出的信号。
就是只修改进程大小值实际上不分配页面(让进程误以为分配了),等需要使用对应页面再来分配
Part One
修改sbrk(n),本来是将进程的内存大小增加n个字节,然后返回新分配区域的开始;改成只需将进程的大小(myproc()->sz)增加n不分配区域,并返回原来的大小。比较简单
//将进程的大小(myproc()->sz)增加n不分配区域,其中n为用户传入的参数
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;
}
改之前
改之后
“addr 0x4004”表示导致页面错误的虚拟地址是0x4004。“pid 3 sh: trap…”消息来自trap.c中的内核陷阱处理程序。它捕获了一个页面错误(trap 14,或T_PGFLT),所以为什么发生这个错误?
//traps.h中
#define T_PGFLT 14 // page fault
这个错误肯定是因为代码改成了只增加大小,不分配物理区域导致找不到虚拟地址对应的物理页引发报错。
但我有个疑问,echo hi只是把hi输出而已,需要用到物理页吗?还是说hi作为参数存在argv[1]中,也就是需要物理页保存。
Part Two
将新分配的物理内存页映射到故障地址然后返回到用户空间,让进程继续执行。
有很多提示,就相对容易。
Hint: look at the cprintf arguments to see how to find the virtual address that caused the page fault.
(找到引起错误的虚拟地址)
Hint: steal code from allocuvm() in vm.c, which is what sbrk() calls (via growproc()).
Hint: use PGROUNDDOWN(va) to round the faulting virtual address down to a page boundary.
(4k对齐)
Hint: break or return in order to avoid the cprintf and the myproc()->killed = 1.
(及时返回防止再次输出那段提示)
Hint: you'll need to call mappages().
In order to do this you'll need to delete the static in the declaration of mappages() in vm.c, and you will need to declare mappages() in trap.c.
Add this declaration to trap.c before any call to mappages():
int mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm);
Hint: you can check whether a fault is a page fault by checking if tf->trapno is equal to T_PGFLT in trap().
//#define T_PGFLT 14 // page fault
//将引发缺页异常的线性地址保存在address变量里面
__asm__("movl %%cr2,%0":"=r" (address));
让程序继续执行缺的是什么,如何让程序返回用户空间?
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()); //将引起错误的虚拟地址向下4K对齐
for(; a < myproc()->sz; a += PGSIZE){ //从a开始对大小为myproc()->sz-a的区域分配并映射物理页
mem = kalloc(); //分配物理页
if(mem == 0){
cprintf("allocuvm out of memory\n");
//这里的 myproc()->tf->eax瞎写的,我以为sys_sbrk()返回值即oldsz存在tf->eax中
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("allocuvm out of memory (2)\n");
deallocuvm(myproc()->pgdir, myproc()->sz, myproc()->tf->eax);
kfree(mem);
return;
}
}
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;
}
我随便改了下,就成功运行了???什么情况?
我看到别人的代码好像没有for循环,只是加了分配了一个PGSIZE大小的页,难道出错的只是cr2内那一个虚拟地址吗?我先不改,等后面出错了再回来改
,至少现在可以运行