MIT 6.828 - 4. Lab 04: Xv6 lazy page allocation


實驗總結

  1. 本次實驗用時約八個小時。
  2. 收獲是對線性地址的理解更深入了。

遇到的困難包括:

  1. 懶。
  2. xv6-riscv 默認開了 kpti(內核和用戶態頁表分離) ,故需要在各種系統調用頭部手動模擬 traverse 頁表的過程,以及模擬處理缺頁異常。(我現在覺得這不是一個很好的設計)

測試結果:

$ make grade
running lazytests: 
(3.7s) 
  lazy: pte printout: OK 
  lazy: map: OK 
  lazy: unmap: OK 
usertests: 
(95.8s) 
  usertests: pgbug: OK 
  usertests: sbrkbugs: OK 
  usertests: argptest: OK 
  usertests: sbrkmuch: OK 
  usertests: sbrkfail: OK 
  usertests: sbrkarg: OK 
  usertests: stacktest: OK 
  usertests: all tests: OK 
Score: 100/100

0. 實驗准備

實驗指導鏈接

上來直接:

$ cd xv6-riscv-fall19
$ git checkout lazy

實驗分為四個子任務(實際更多個):

  1. 設計一個輸出頁表的調試程序 vmprint(pagetable_t)
  2. 實現不立即分配內存的 sbrk 調用。
  3. 冒煙。
  4. 各種修復。

1. vmprint

void printwalk(pagetable_t pagetable, int depth)
{
  // there are 2^9 = 512 PTEs in a page table.
  for (int i = 0; i < 512; i++)
  {
    pte_t pte = pagetable[i];

    if (pte & PTE_V)
    {
      for (int j = 0; j < depth; j++)
        printf(" ..");
      // printf("%d: pte %p pa %p %s%s%s\n", i, pte, PTE2PA(pte), (pte) & PTE_R ? "r" : "-", (pte) & PTE_W ? "w" : "-", (pte) & PTE_X ? "x" : "-");
      printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte));
    }

    if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0)
    {
      // this PTE points to a lower-level page table.
      uint64 child = PTE2PA(pte);
      printwalk((pagetable_t)child, depth + 1);
    }
  }
}

void vmprint(pagetable_t t)
{
  printf("page table %p\n", t);
  printwalk(t, 1);
}

2. 實現 lazy sbrk

非常好改,但通過 sbrk 輸入負數來歸還線性空間時,需要及時 demalloc

uint64
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;
  if(n < 0)
    uvmdealloc(myproc()->pagetable, addr, myproc()->sz);
  
  return addr;
}

3 & 4. 冒煙 & 修復

首先需要正確處理缺頁異常,完成必要的檢查(訪問非法的線性地址 和 棧溢出):

//
// handle an interrupt, exception, or system call from user space.
// called from trampoline.S
//
void
usertrap(void)
{
  int which_dev = 0;

  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();
  
  // save user program counter.
  p->tf->epc = r_sepc();
  
  if(r_scause() == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->tf->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } else if((which_dev = devintr()) != 0){
    // ok
  } else if(r_scause() == 13 || r_scause() == 15) {
    // ------------------------------------- here -------------------------------------
    // 13: Page load fault, 15: Page store fault
    // printf("usertrap(): page fault, scause %p pid=%d\n", r_scause(), p->pid);
    // printf("            sepc=%p vaddr=%p\n", r_sepc(), r_stval());

    struct proc *p = myproc();
    pagetable_t pagetable = p->pagetable;
    uint64 fault_vaddr = (uint64) r_stval();
    uint64 vpage_base = PGROUNDDOWN(fault_vaddr);

    if(fault_vaddr >= p->sz) {
      // printf("usertrap(): page fault: invalid memory access to vaddr %p\n", fault_vaddr);
      p->killed = 1;
      goto end;
    }

    if(fault_vaddr < p->ustack) {
      // printf("usertrap(): page fault: segfault on vaddr %p below stack %p\n", fault_vaddr, p->ustack);
      p->killed = 1;
      goto end;
    }

    char *mem = kalloc();
    if(mem == 0) {
      // printf("usertrap(): page fault: no more physical page available, killing process due to a OOM\n");
      p->killed = 1;
      goto end;
    }

    memset(mem, 0, PGSIZE);

    if(mappages(pagetable, vpage_base, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
      // printf("usertrap(): page fault: cannot map a page");
      kfree(mem);
      p->killed = 1;
      goto end;
    }
  } 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;
  }

end:
  if(p->killed)
    exit(-1);

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
    yield();

  usertrapret();
}

注意到為了能夠正確找到棧空間頂部的位置,我給 proc 數據結構加了一個新的 entry 叫做 ustack
對應需要修改 fork 和 exec 的代碼,因為在這里有新 proc 結構的生成,故需要維護這個屬性。

模擬頁表和缺頁的檢查代碼實現如下:

注意到需要特判 pid = 1 的情況,是因為 xv6 的 initcode 中引用了大於用戶線性空間最大值 proc->sz(這個屬性由 sbrk 維護) 的內存地址,會觸發一個誤判,此為特例

經過與萬呆呆討論,無需特判,但我很懶我不改了。

int uvmchkaddr(pagetable_t pagetable, uint64 addr, uint64 size) {
  struct proc* p = myproc();
  char *mem;
  pte_t *pte;
  uint64 a, end;

  if(p->pid > 1) {
    if(addr >= p->sz) {
      printf("uvmchkaddr(): page fault: invalid memory access to vaddr %p\n", addr);
      return -1;
    }

    if(addr < p->ustack && addr >= p->ustack - PGSIZE) {
      printf("uvmchkaddr(): page fault: segfault on vaddr %p on stack guard\n", addr);
      return -1;
    }
  }

  a = PGROUNDDOWN(addr);
  end = PGROUNDUP(a + size);
  for(; a < end; a += PGSIZE) {
    if((pte = walk(pagetable, a, 1)) != 0 && (*pte & PTE_V))
      continue;

    mem = kalloc();
    if(mem == 0)
      return -1;
    memset(mem, 0, PGSIZE);
    if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
      kfree(mem);
      return -1;
    }
  }

  return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM