1. 程式人生 > 其它 >MIT 6.S081 作業系統 LAB5:Lazy allocation

MIT 6.S081 作業系統 LAB5:Lazy allocation

Lab: xv6 lazy page allocation

實驗的三個部分,逐步實現一個lazy allocation,我就按照自己的思路寫,不分三個部分了

修改sbrk()

不直接分配實體記憶體,這也就是lazy allocation最本質的地方

  • n>0 只改變p->sz
  • n<0 改變p->sz同時呼叫uvmdealloc()取消對應的對映

uvmalloc()中呼叫uvmunmap()取消對映

uint64
sys_sbrk(void)
{
  int addr;
  int n;
  struct proc* p=myproc();

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

handle page fault

判斷r_scause()的值等於13或15則發生了page fault
此時r_stval()的值就是發生page fault的虛擬地址
分配對應的實體記憶體,邏輯類似於uvmalloc()
先呼叫kalloc()分配記憶體,再呼叫mappages()對映
這裡如果記憶體資源已經用完了,則kalloc()返回0,直接殺死程序就行

uint64 ka=(uint64)kalloc();
if(ka==0)
    p->killed=1;
else{
    memset((void*)ka,0,PGSIZE);
    va=PGROUNDDOWN(va);
    if(mappages(p->pagetable,va,PGSIZE,ka,PTE_W|PTE_U|PTE_R)!=0){
        kfree((void*)ka);
        p->killed=1;
    }
}

modify uvmunmap() and uvmcopy()

因為lazy allocation允許了未對映的地址也是合法的
所以修改對應的兩個函式,讓它們在發現未對映的時候直接跳過就行了

if((pte = walk(old, i, 0)) == 0 || (*pte & PTE_V) == 0)
    continue;

fork()裡面會呼叫uvmcopy()複製記憶體,按如上修改後就沒問題了

negative sbrk() arguments

如果sbrk()中n是負數,我們直接呼叫uvmunmap()就可以了
因為我們已經修改了uvmunmap()函式,不用管那部分地址有沒有對映
如果沒有對映,裡面直接跳過就是了,如果映射了,就釋放掉

if(n<0)
{
    uvmdealloc(p->pagetable,p->sz,p->sz+n);
}

illegal address

有兩種情況地址是不合法的

  • va>=p->sz 超出了分配的最大空間
  • 訪問的是stack下面的guard page

所以在處理缺頁中斷的時候要先判斷是否合法,如果不合法,則殺死程序
通過p->killed=1殺死程序

if(va>=p->sz||is_guarded(p->pagetable,va))
    p->killed=1;

is_guarded()

加入is_guarded()函式判斷是否是guard page
guard page有個特點,對應的虛擬地址分配了實體記憶體,但是pte中的flag沒有設定PTE_U

就像這裡的虛擬地址0x1000一樣,有對應的實體地址,flag為0x000f
所以參考walkaddr()中的寫法,如果發現一個虛擬地址,映射了對應的物理頁,但沒有設定PTE_U,則是guard page

int
is_guarded(pagetable_t pagetable, uint64 va)
{
  pte_t *pte;

  if(va >= MAXVA)
    return 0;

  pte = walk(pagetable, va, 0);
  if(pte == 0)
    return 0;
  if((*pte & PTE_V) == 0)
    return 0;
  if((*pte & PTE_U) == 0)
    return 1;
  return 0;
}

valid system call arguements

lazy allocation的一個特點是你不能讓使用者程式察覺到記憶體還未分配
因此當程式往system call傳遞一個合法的地址時,要分配對應記憶體
傳遞地址都要通過argaddr()這個函式,所以在這裡截胡

int
argaddr(int n, uint64 *ip)
{
  *ip = argraw(n);
  struct proc* p=myproc();
  uint64 va=*ip,pa=walkaddr(p->pagetable,va);
  if(va<p->sz&&!is_guarded(p->pagetable,va)&&pa==0)
  {
    uint64 ka=(uint64)kalloc();
    if(ka==0)
      p->killed=1;
    else{
      memset((void*)ka,0,PGSIZE);
      va=PGROUNDDOWN(va);
      if(mappages(p->pagetable,va,PGSIZE,ka,PTE_W|PTE_U|PTE_R)!=0){
        kfree((void*)ka);
        p->killed=1;
      }
    }
  }
  return 0;
}

只有在傳遞的地址合法,且對應記憶體未分配的情況下,我們才分配記憶體
通過walkaddr()判斷是否分配

這裡我們只檢查合法性,而不用檢查非法情況
因為按原xv6程式碼中的註釋,argaddr()不檢查合法性,交給copyin/copyout去檢查

Retrieve an argument as a pointer.
Doesn't check for legality, since
copyin/copyout will do that.

test