MIT 6.828 - 5. Lab 05: Copy-on-Write Fork for xv6


實驗總結

  1. 本次實驗用時約 11 個小時。
  2. 收獲是對 Copy-on-Write 機制的理解更深入了。

遇到的困難包括:

  1. 懶。
  2. 中間把代碼寫掛了兩次,經過 soha 提示,恍然大悟,原因是相同的:在子進程退出內存回收時把共享的 physical page 給回收了,經過修改已經解決。

測試結果:

running cowtest: 
$ make qemu-gdb
(6.4s) 
  simple: OK 
  three: OK 
  file: OK 
usertests: 
$ make qemu-gdb
OK (87.4s) 
time: OK 
Score: 100/100

0. 實驗准備

實驗指導鏈接

上來直接:

$ cd xv6-riscv-fall19
$ git checkout cow

可以將實驗分為幾個子任務:
0. 給內存頁面管理系統 kalloc.c 加上引用計數機制。(這里應該加一些 assert ,就可以有效避免上述 困難2 不會調的問題,我沒加,故調不出來)

  1. 修改 uvmcopy() 使其創建 cow 頁面而不是立即復制。cow 頁面的特點:可讀不可寫,有一個 PTE_COW 標記。(這個標記復用的 rv 定義的 pte 中第一個 custom 標志位)
  2. 修改 usertrap() 使其處理 store page fault 硬件異常(查 rv 手冊知,是 15 號),此時創建新頁面,更新頁表。
  3. 確認引用為 0 時,物理頁面會被回收。
  4. 對系統調用進行檢查,在他們寫入 cow 頁面前創建新頁面。

0. 引用計數

struct {
  struct spinlock lock;
  struct run *freelist;
  uint *ref_count;
} kmem;

void
kinit()
{
  initlock(&kmem.lock, "kmem");
  kmem.ref_count = (uint*)end;
  uint64 rc_pages = ((((PHYSTOP - (uint64)end) >> 12) + 1) * sizeof(uint) >> 12) + 1;
  uint64 rc_offset = (uint64)rc_pages << 12;
  freerange(end + rc_offset, (void*)PHYSTOP);
}

// ...

void
kref(void* pa) {
  uint64 idx = ((uint64)pa - (uint64)end) >> 12;

  acquire(&kmem.lock);
  kmem.ref_count[idx]++;
  release(&kmem.lock);
}

void
kderef(void* pa) {
  uint64 idx = ((uint64)pa - (uint64)end) >> 12;
  char shall_free = 0;

  acquire(&kmem.lock);
  kmem.ref_count[idx]--;
  if(kmem.ref_count[idx] == 0)
    shall_free = 1;
  release(&kmem.lock);

  if(shall_free)
    kfree(pa);
}

1. 實現基於 cow 的 uvmcopy

非常好改,其中 PTE_COW = (1L << 8),詳見 rv 手冊。

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags, newflags;

  for(i = 0; i < sz; i += PGSIZE){
    if((pte = walk(old, i, 0)) == 0)
      panic("uvmcopy: pte should exist");
    if((*pte & PTE_V) == 0)
      panic("uvmcopy: page not present");
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    kref((void*)pa);
    newflags = flags | PTE_COW;
    newflags &= (~PTE_W);
    if(mappages(new, i, PGSIZE, (uint64)pa, newflags) == 0) {
      uvmunmap(old, i, PGSIZE, 0);
      if(mappages(old, i, PGSIZE, pa, newflags) != 0) {
        panic("Bad mapping");
      }
    } else {
      kderef((void*)pa);
      goto err;
    }
  }
  return 0;

 err:
  uvmunmap(new, 0, i, 1);
  return -1;
}

2. 處理頁面異常

  if(r_scause() == 15) {
    // 15: load page fault
    uint64 fault_addr = r_stval();
    uint64 vpage_head = PGROUNDDOWN(fault_addr);

    pte_t *pte;
    if((pte = walk(p->pagetable, vpage_head, 0)) == 0) {
      printf("usertrap(): page not found\n");
      p->killed = 1;
      goto end;
    }

    if((*pte | PTE_V) && (*pte | PTE_U) && (*pte | PTE_COW)) {
      char *mem = kalloc();
      if(mem == 0) {
        printf("usertrap(): no more physical page found, exit due to OOM\n");
        p->killed = 1;
        goto end;
      }

      char *pa = (char *)PTE2PA(*pte);
      memmove(mem, pa, PGSIZE);
      uint flags = PTE_FLAGS(*pte);
      uint newflags = flags & (~PTE_COW);
      newflags |= PTE_W;

      uvmunmap(p->pagetable, vpage_head, PGSIZE, 0);
      kderef((void*)pa);
      if(mappages(p->pagetable, vpage_head, PGSIZE, (uint64)mem, newflags) != 0) {
        panic("usertrap(): cannot map page\n");
      }
    }
  }

4 & 5. 各種檢查

模擬頁表和缺頁的檢查代碼實現如下。把它插到 sys_read sys_write sys_pipe 這三個系統調用中,即可通過 usertests

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

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

int uvmchkaddr(struct proc *p, uint64 addr, uint64 size, int write) {
  pagetable_t pagetable = p->pagetable;
  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;
    }
  }

  a = PGROUNDDOWN(addr);
  end = PGROUNDUP(a + size);
  for(; a < end; a += PGSIZE) {
    if((pte = walk(pagetable, a, 1)) == 0)
      panic("uvmchkaddr(): bad pte");

    if(write && (*pte & PTE_COW)) {
      uint64 pa0 = PTE2PA(*pte);
      char *mem = kalloc();
      if(mem == 0) {
        printf("uvmchkaddr(): no more physical page found, exit due to OOM\n");
        return -1;
      }
      memmove(mem, (void *)pa0, PGSIZE);
      uint flags = PTE_FLAGS(*pte);
      uint newflags = flags & (~PTE_COW);
      newflags |= PTE_W;

      uvmunmap(pagetable, a, PGSIZE, 0);
      kderef((void *)pa0);
      if(mappages(pagetable, a, PGSIZE, (uint64)mem, newflags) != 0) {
        panic("uvmchkaddr(): cannot map page\n");
      }
    } else {
      if(!((*pte & PTE_U) && (*pte & PTE_V)))
        return -1;
    }
  }

  return 0;
}


免責聲明!

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



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