MIT6.S081Lab5:xv6 lazy page allocation

Eliminate allocation from sbrk()

这个相当简单,在sys_sbrk中对growproc的调用注释掉就行,然后把sz加上n。这样应该分配的页面没有分配,同时也没有映射到页表,所以使用这些分配的页面的时候会报错panic: uvmunmap: not mapped

sys_sbrk如下:

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

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

运行echo hi结果如下

$ echo hi
usertrap(): unexpected scause 0x000000000000000f pid=3
            sepc=0x00000000000012ac stval=0x0000000000004008
panic: uvmunmap: not mapped

Lazy allocation

这个实验也还是比较简单,尤其是课程上已经有一些展示了,按照提示所说,在usertrap的最后一个else前加上处理页面错误的语句,分配一个新页面,置零,映射到页表上,然后重新执行指令,重新执行指令不需要做什么,因为此时p->trapframe->epc存储的就是引发错误的指令。不过这个实验好像没有考虑sbrk减少堆空间的情况。

usertrap新增如下

else if (r_scause() == 13 || r_scause() == 15) {
    uint64 va = r_stval();
    uint64 begin = PGROUNDDOWN(va);
    pagetable_t pagetable = p->pagetable;
    char *mem = kalloc();
    if(mem == 0){
      kfree(mem);
      exit(-1);
    }
    memset(mem, 0, PGSIZE);
    if(mappages(pagetable, begin, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
      kfree(mem);
      exit(-1);
    }
    vmprint(pagetable);
  }

因为释放进程内存的时候会通过页表释放堆空间,而很多堆空间并没有实际分配,即有效位为0,所以在uvmunmap中会panic,我们把相应的改为continue即可。

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)
      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;
  }
}

运行echo hi,可以看到

$ echo hi
page table 0x0000000087f75000
..0: pte 0x0000000021fdc801 pa 0x0000000087f72000
.. ..0: pte 0x0000000021fd9401 pa 0x0000000087f65000
.. .. ..0: pte 0x0000000021fdc05f pa 0x0000000087f70000
.. .. ..1: pte 0x0000000021fd98df pa 0x0000000087f66000
.. .. ..2: pte 0x0000000021fdc40f pa 0x0000000087f71000
.. .. ..3: pte 0x0000000021fd68df pa 0x0000000087f5a000
.. .. ..4: pte 0x0000000021fd641f pa 0x0000000087f59000
..255: pte 0x0000000021fdd001 pa 0x0000000087f74000
.. ..511: pte 0x0000000021fdcc01 pa 0x0000000087f73000
.. .. ..510: pte 0x0000000021fd90c7 pa 0x0000000087f64000
.. .. ..511: pte 0x0000000020001c4b pa 0x0000000080007000
page table 0x0000000087f75000
..0: pte 0x0000000021fdc801 pa 0x0000000087f72000
.. ..0: pte 0x0000000021fd9401 pa 0x0000000087f65000
.. .. ..0: pte 0x0000000021fdc05f pa 0x0000000087f70000
.. .. ..1: pte 0x0000000021fd98df pa 0x0000000087f66000
.. .. ..2: pte 0x0000000021fdc40f pa 0x0000000087f71000
.. .. ..3: pte 0x0000000021fd68df pa 0x0000000087f5a000
.. .. ..4: pte 0x0000000021fd64df pa 0x0000000087f59000
.. .. ..19: pte 0x0000000021fd601f pa 0x0000000087f58000
..255: pte 0x0000000021fdd001 pa 0x0000000087f74000
.. ..511: pte 0x0000000021fdcc01 pa 0x0000000087f73000
.. .. ..510: pte 0x0000000021fd90c7 pa 0x0000000087f64000
.. .. ..511: pte 0x0000000020001c4b pa 0x0000000080007000
hi

有两次页面错误。

Lazytests and Usertests

这个我觉得有一定难度,如果没有提示的话很难考虑这么仔细。

按照提示一条条来

  • 处理sbrk()参数为负的情况。这个好处理,为负的情况依旧调用growproc处理即可:

    uint64
    sys_sbrk(void)
    {
      int addr;
      int n;
      struct proc * p = myproc(); 
      if(argint(0, &n) < 0)
        return -1;
      addr = myproc()->sz;
      if(n > 0){
        p->sz = p->sz + n;
      }else{
        if (growproc(n) == -1) {
          return -1;
        }
      }
      return addr;
    }
    
  • 如果某个进程在高于sbrk()分配的任何虚拟内存地址上出现页错误,则终止该进程:页面错误中断的时候在分配新页面前检查是否高于p->sz。

  • fork()中正确处理父到子内存拷贝:这里的拷贝主要指的是uvmcopy,很简单,把这个函数里面前两个panic注释掉或者改成continue即可,因为在父进程中这些页也是没有实际存在的,子进程只要p->sz保持好的父进程一致就可以了。

  • 处理这种情形:进程从sbrk()向系统调用(如readwrite)传递有效地址,但尚未分配该地址的内存。这个是最难的,我们可以发现read或write的系统调用最终用的是copyout或者copyin,而这两者在walkaddr中如果找的是还没有实际分配页面的地址,那么发挥的物理地址就是0,从而系统调用返回-1,系统调用失败,在这个过程中并没有产生页面错误直接失败了,所以不会进入陷阱机制处理页面错误。所以要处理这种情况的话我们就要在walkaddr里面直接把没有实际分配的页面分配了,就像在trap中处理页面错误一样。

  • 正确处理内存不足:如果在页面错误处理程序中执行kalloc()失败,则终止当前进程。:如果kalloc返回的是0,则终止进程。

  • 处理用户栈下面的无效页面上发生的错误。如果虚拟地址va小于了用户栈,说明访问了不该访问的内存,终止进程。

综上,我们可以在vm.c里面写一个函数来分配映射lazy allocation的页面,如下:

// 注意:如果要在vm.c中使用proc结构体及其字段,要先加上"spinlock.h",再加上"proc.h"
int lazyalloc(uint64 va) {
  struct proc *p = myproc();
  // 如果高于sbrk的地址或者低于用户栈地址,终止进程
  if (va >= p->sz || va <= PGROUNDDOWN(p->trapframe->sp)) {
      return -1;
    }
    // printf("page fault:%p\n", va);
    uint64 begin = PGROUNDDOWN(va);
    pagetable_t pagetable = p->pagetable;
    char *mem = kalloc();
    if(mem == 0){
      return -1;
    }
    memset(mem, 0, PGSIZE);
    if(mappages(pagetable, begin, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
      kfree(mem);
      return -1;
    }
    return 0;
}
// 需要在defs.h中加上这个函数的声明才能在其他地方使用这个函数

然后usertrap和walkaddr中调用这个函数

// usertrap
else if (r_scause() == 13 || r_scause() == 15) {
    uint64 va = r_stval();
    if (lazyalloc(va) == -1) {
      p->killed = 1;
    }
  }
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;
  uint64 pa;
  if(va >= MAXVA)
    return 0;

  pte = walk(pagetable, va, 0);
  if(pte == 0 || (*pte & PTE_V) == 0) {
    if (lazyalloc(va) == -1) {
      return 0;
    } else {
      pte = walk(pagetable, va, 0);
    }
  }
  //   return 0;
  // if((*pte & PTE_V) == 0)
  //   return 0;
  if((*pte & PTE_U) == 0)
    return 0;
  pa = PTE2PA(*pte);
  return pa;
}

如果运行时出现了panic,把相应地方注释掉或者改为continue即可。

运行lazytests

$ lazytests
lazytests starting
running test lazy alloc
test lazy alloc: OK
running test lazy unmap
test lazy unmap: OK
running test out of memory
test out of memory: OK
ALL TESTS PASSED

运行usertests,可以看到所有测试通过,如果walkaddr编写的有问题的话,sbrkarg不会通过。

总结

这个实验总的来说我觉得还是比lab3简单一点,最需要注意的就是最后一个中关于系统调用涉及到lazy allocation不会触发页面错误需要手动编写代码来分配页面。

  • 27
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值