XV6學習(7)Lab lazy

代碼在github上。git

這一個實驗是要利用缺頁異常來實現懶分配(lazy allocation)。用戶態程序經過sbrk系統調用來在堆上分配內存,而sbrk則會經過kalloc函數來申請內存頁面,以後將頁面映射到頁表當中。github

當申請小的空間時,上述過程是沒有問題的。可是若是當進程一次申請很大的空間,如數GB的空間,再使用上述策略來一頁頁地申請映射的話就會很是的慢(1GB/4KB=262,144)。這時候就引入了lazy allocation技術,當調用sbrk時不進行頁面的申請映射,而是僅僅增大堆的大小,當實際訪問頁面時,就會觸發缺頁異常,此時再申請一個頁面並映射到頁表中,這是再次執行觸發缺頁異常的代碼就能夠正常讀寫內存了。app

經過lazy allocation技術,就能夠將申請頁面的開銷平攤到讀寫內存當中去,在sbrk中進行大量內存頁面申請的開銷是不能夠接受的,可是將代價平攤到讀寫操做當中去就能夠接受了。函數

整體來講這一個實驗的難度並不大,理解了上一個trap的實驗以及缺頁異常就能比較輕鬆地完成了。ui

Eliminate allocation from sbrk() (easy)

這一個就是要修改sbrk函數,使其不調用growproc函數進行頁面分配,關鍵就是p->sz += n將堆大小增大,而後註釋掉growprocif(n < 0)是後面部分的內容。this

uint64
sys_sbrk(void)
{
  int addr;
  int n;
  if(argint(0, &n) < 0)
    return -1;

  struct proc *p = myproc();
  addr = p->sz;
  p->sz += n;
  if(n < 0) {
    p->sz = uvmdealloc(p->pagetable, addr, addr + n);
  }
  // if(growproc(n) < 0)
  //  return -1;
  return addr;
}

Lazy allocation (moderate)

接下來就是真正實現Lazy allocation:當系統發生缺頁異常時,就會進入到usertrap函數中,此時scause寄存器保存的是異常緣由(13爲page load fault,15爲page write fault),stval是引起缺頁異常的地址。code

usertrap判斷scause爲13或15後,就能夠讀取stval獲取引起異常的地址,以後調用lazy_alloc對該地址的頁面進行分配便可。在這裏不須要進行p->trapframe->epc += 4操做,由於咱們要返回發生異常的那條指令並從新執行。進程

void
usertrap(void)
{
  ...
  } else if((which_dev = devintr()) != 0){
    // ok
  } else if (r_scause() == 13 || r_scause() == 15) {
    // 13: page load fault; 15: page write fault
    // printf("page fault\n");
    uint64 addr = r_stval();
    if (lazy_alloc(addr) < 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_alloc函數中,首先判斷地址是否合法,以後經過PGROUNDDOWN宏獲取對應頁面的起始地址,而後調用kalloc分配頁面,memset將頁面內容置0,最後調用mappages將頁面映射到頁表中去。內存

int
lazy_alloc(uint64 addr) {
  struct proc *p = myproc();
  // page-faults on a virtual memory address higher than any allocated with sbrk()
  // this should be >= not > !!!
  if (addr >= p->sz) {
    // printf("lazy_alloc: access invalid address");
    return -1;
  }

  if (addr < p->trapframe->sp) {
    // printf("lazy_alloc: access address below stack");
    return -2;
  }
  
  uint64 pa = PGROUNDDOWN(addr);
  char* mem = kalloc();
  if (mem == 0) {
    // printf("lazy_alloc: kalloc failed");
    return -3;
  }
  
  memset(mem, 0, PGSIZE);
  if(mappages(p->pagetable, pa, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0){
    kfree(mem);
    return -4;
  }
  return 0;
}

Lazytests and Usertests (moderate)

這一部分就是要強化上面寫的的lazy allocation,使其能在一些特殊狀況下工做。get

Handle negative sbrk() arguments.

這一個就是在上面的sys_sbrk函數中的if(n < 0)部分,當參數爲負數時,調用uvmdealloc取消分配。

Kill a process if it page-faults on a virtual memory address higher than any allocated with sbrk().

這一個即lazy_alloc函數中的addr >= p->sz部分,當訪問的地址大於堆的大小時就說明訪問了非法地址,注意這裏是>=而不是>

Handle the parent-to-child memory copy in fork() correctly.

fork函數中經過uvmcopy進行地址空間的拷貝,咱們只要將其中panic的部分改成continue就好了,當頁表項不存在時並非說明出了問題,直接跳過就能夠了。

Handle the case in which a process passes a valid address from sbrk() to a system call such as read or write, but the memory for that address has not yet been allocated.

當進程經過readwrite等系統調用訪問未分配頁面的地址時,並不會經過頁表硬件來訪問,也就是說不會發生缺頁異常;在內核態時是經過walkaddr來訪問用戶頁表的,所以在這裏也要對缺頁的狀況進行處理。
當出現pte == 0 || (*pte & PTE_V) == 0時,就說明發生了缺頁,這時只要調用lazy_alloc進行分配,以後再次使用walk就能正確獲得頁表項了。

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 (lazy_alloc(va) == 0) {
      pte = walk(pagetable, va, 0);
    } else {
      return 0;
    }
  }
  if((*pte & PTE_U) == 0)
    return 0;
  pa = PTE2PA(*pte);
  return pa;
}

Handle out-of-memory correctly: if kalloc() fails in the page fault handler, kill the current process.

kalloc失敗時,lazy_alloc就會返回負值,此時判斷返回值而後p->killed = 1就好了。

Handle faults on the invalid page below the user stack.

這一個能夠經過addr < p->trapframe->sp判斷,當地址小於棧頂地址時就說明發生了非法訪問。

相關文章
相關標籤/搜索