Part One: Eliminate allocation from sbrk()

对于目前的 xv6 来说,调用 sbrk() 函数时会分配物理页和添加相应的 PTE 映射,这样我们就获得了更多的虚拟地址地址空间。可有时候分配来的空间并不会立即使用,甚至是不使用,这就造成了资源浪费。

为了避免这种情况,节省宝贵的系统资源,于是就有了 lazy allocation 的概念,要想实现 lazy allocation,就要求我们在调用 sbrk() 的时候不去真正的分配物理空间和建立 PTE 映射,只有在真正使用到相应的虚拟地址的时候才去分配。

在 OS 中,如果使用到了一块没有建立映射的虚拟地址,这时候系统会产生一个 Page Fault,我们通过这个信号来及时的为需要的地址分配物理页和建立 PTE 映射。

首先第一步,修改 sys_sbrk() 如下:

int sys_sbrk(void)
{
  int addr;
  int n;

  if (argint(0, &n) < 0)
    return -1;
  addr = myproc()->sz;
#if 0
  if (growproc(n) < 0)
    return -1;
#endif
  return addr;
}

这样的话,当程序申请内存的时候,只是“假装”分配内存,修改完程序,编译内核并启动,在命令行中输入 echo hi,得到输出如下:

$ echo hi
pid 3 sh: trap 14 err 6 on cpu 0 eip 0x112c addr 0x4004--kill proc

这段打印来自内核的 trap.c/trap() 函数的这段代码:

// 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;

需要注意的是 rcr2() 函数:

static inline uint
rcr2(void)
{
  uint val;
  asm volatile("movl %%cr2,%0" : "=r" (val));
  return val;
}

这个函数返回的是 %cr2 寄存器内的值,这样就能知道造成 Page Fault 的是哪个虚拟地址。

Part Two: Lazy allocation

trap.c/trap() 中添加代码来处理由于 lazy allocation 造成的 Page Fault:

注意我们将代码添加在上面的打印语句前面。

提示:

  1. 通过 rcr2() 函数来找到出错的地址,并使用 PGROUNDDOWN(va) 将此地址舍入到 Page Boundary
  2. 参考 vm.c/allocuvm() 的代码
  3. 处理完成后使用 break 或者 return 防止继续向下执行 myproc()->killed = 1cprintf()
  4. 使用 mappage() 来完成映射:在 vm.c 中删掉 mappage() 定义时的 static 关键字,并在 trap.c 中添加声明
  5. 使用 tf->trapno == T_PGFLT 来判断是否为 Page Fault

代码如下:

/*处理Page Fault的代码添加在这里*/
if (tf->trapno == T_PGFLT) /*只有Page Fault才处理*/
{
  pde_t *pde;
  pte_t *pgtab;
  pde_t *pte;
  char *mem;
  struct proc *curproc = myproc();
  uint newsz, oldsz;
  uint va;

  /*得到造成Page Fault的虚拟地址,说明这个虚拟地址没有映射到的物理地址*/
  va = PGROUNDDOWN(rcr2()); /*rounddown到虚拟地址页边界*/

  cprintf("sz: %x\n", curproc->sz);
  cprintf("rcr2  va: %x\n", rcr2());
  cprintf("round va: %x\n", va);

  /*分配一页物理内存*/
  mem = kalloc();
  memset(mem, 0, PGSIZE);

  cprintf("pa1: %x\n", V2P(mem));
  pde = &((curproc->pgdir)[PDX(rcr2())]);
  pgtab = P2V(PTE_ADDR(*pde));
  pte = &pgtab[PTX(rcr2())];
  cprintf("Before Map PDE: %x\n", (curproc->pgdir[PDX(rcr2())]));
  cprintf("Before Map PTE: %x\n", *pte );
  cprintf("Before Map cal PA: %x\n",  PTE_ADDR((*pte)) + (va & 0b111111111111) );

  /*建立PTE完成映射*/
  mappages(curproc->pgdir, (char *)va, PGSIZE, V2P(mem), PTE_W|PTE_U);
  
  /*将映射添加进进程的页表*/
  pde = &((curproc->pgdir)[PDX(rcr2())]); /*找到属于页目录的哪一个entry*/
  pgtab = P2V(PTE_ADDR(*pde)); /*找到页表*/
  pte = &pgtab[PTX(rcr2())]; /*在页表中添加映射*/

  cprintf("After Map PDE: %x\n", (curproc->pgdir[PDX(rcr2())]));
  cprintf("After Map PTE: %x\n",  *pte );
  cprintf("After Map cal PA: %x\n",  PTE_ADDR((*pte)) + (va & 0b111111111111) );

  return;    
}