MIT 6.S081 Fall 2020 Lab 5

lazy page allocation

学!

课里面老师写过了…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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;
}

暂时没写 n < 0 的情况.

也是课上写了的()

这里实现就直接用 uvmalloc 了.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void
usertrap(void)
{
  ...
  else if((which_dev = devintr()) != 0){
    // ok
  } else if (r_scause() == 13 || r_scause() == 15) {
    uint64 va = r_stval();
    if (uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 0)
      p->killed = 1;
  } else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
    p->killed = 1;
  }
  ...
}

然后处理因为 lazy allocate 但是没有使用的 page 被 free 的情况. 因为没有使用 map, 所以 pte 是 0, 所以直接 continue, 在宏观层面看起来就是先 allocate 然后被 free 了.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void
uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
{
  ...
    if((pte = walk(pagetable, a, 0)) == 0)
      // panic("uvmunmap: walk");
      continue;
    if((*pte & PTE_V) == 0)
      // panic("uvmunmap: not mapped");
      continue;
  ...
}

处理 sbrk 缩小 heap

很简单, 只需要把剔除掉的 page dealloc 一下就行, 用 uvmdealloc 这个函数即可.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
uint64
sys_sbrk(void)
{
  ...
  argint(0, &n);
  addr = p->sz;
  p->sz += n;

  if (n < 0)
    uvmdealloc(p->pagetable, addr, p->sz);
  ...
}

处理虚拟地址不合法 (包括越界, 访问 stack guard page), 内存溢出

越界判断一下 va >= p->sz 即可, 内存溢出由于用 uvmalloc, 失败会返回 0, 所以判断一下返回值, 是 0 就溢出了.

guard page 是没有映射的一个部分, 它检查栈溢出的机制就是利用访问非法地址造成 page fault. 在处理的时候不能当作 lazy page, 要检查一下.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void
usertrap(void)
{
  ...
  else if (r_scause() == 13 || r_scause() == 15) {
    uint64 va = r_stval();
    if (va >= p->sz ||
        (p->trapframe->sp - PGSIZE <= va && va < p->trapframe->sp) ||
        uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 0)
      p->killed = 1;
  }
  ...
}

处理内核态时访问未映射的页面

用户态可以通过 page falut 陷入内核, 内核在 trap 里处理. 但是内核态访问内存如 fork 需要复制所有页面 (现在还没写 cow), read 或 write 或者其他时候, 会访问一个暂时没分配的用户的 page.

fork 的情况比较简单, 因为父进程没有分配的页面一定是没有使用的, 那么子进程也不需要立马分配页面. 在 vmcopy 中把 panic 去掉即可.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  ...
    if((pte = walk(old, i, 0)) == 0)
      // panic("uvmcopy: pte should exist");
      continue;
    if((*pte & PTE_V) == 0)
      // panic("uvmcopy: page not present");
      continue;
  ...
}

read 或 wirte 系统调用访问 page 的时候, 是通过用户的页表来找对应的物理地址的. walkaddr(pagetable_t pagetable, uint64 va) 实现了这个功能.

在 walkaddr 中, 可能就会碰到 va 合法, 但是由于没有分配页面, 导致 pte 为空或者 invalid 的情况. 需要判断并分配页面. 注意需要判断合法, 即 va 是否超过 p->sz, 是否访问的是 guard page. 同时也需要判断内存溢出. 不合法返回 0, 而没内存了只能 kill 了.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
uint64
walkaddr(pagetable_t pagetable, uint64 va)
{
  ...
  struct proc *p = myproc();

  pte = walk(pagetable, va, 0);
  if(pte == 0 || (*pte & PTE_V) == 0) {
    // return 0;
    if (va >= p->sz || (p->trapframe->sp - PGSIZE <= va && va < p->trapframe->sp))
      return 0;
    if (uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 0) {
      p->killed = 1;
      exit(-1);
    }
  }
  // if((*pte & PTE_V) == 0)
  //   return 0;
  ...
}

有点麻烦, 但是和之前的选做比应该是更简单有点. 再说.