1. 程式人生 > 其它 >6.s081 Lab5 lazy

6.s081 Lab5 lazy

Lab: xv6 lazy page allocation

sbrk是xv6提供的系統呼叫, 通過sbrk, 使用者程式可以擴大自己的heap.

一開始時, sbrk指向heap底端, 同時也是stack的頂端(p->sz).

當呼叫sbrk, 會擴充套件heap上界, 核心會分配一些實體記憶體, 並將這些實體記憶體對映到heap中.

xv6實現的是eager allocation, 一旦呼叫sbrk就會立即分配實體記憶體. 這樣會使記憶體消耗增加(應用程式不知道自己需要多少實體記憶體, 會申請多於自己所需的記憶體).

但通過虛擬記憶體和page fault handler可以實現lazy alloction. lazy allocation的核心思想是: sbrk

系統呼叫基本不做任何事, 只需要增加p->sz(將p->sz增加n, n是所需要分配的實體記憶體的page數), 但並不實際分配實體記憶體. 當使用相應的那部分記憶體時, 會觸發page fault(因為沒有分配新的記憶體到page table). 在handler中處理, 並分配新的實體記憶體, 將記憶體對映到page table中, 再重新執行指令.

Eliminate allocation from sbrk() (easy)

第一個任務是修改sys_sbrk()函式, 使其並不實際分配實體記憶體.

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;
}

由於不再分配實體記憶體, 只要把growproc註釋掉, 但程序的size需要增加n.

此時執行echo hi會得到一個page fault. 因為shell中執行程式時, shell會先fork一個子程序, 子程序執行exec來執行echo. shell fork時會申請一些記憶體, 所以呼叫了sys_sbrk, sys_sbrk不實際分配實體記憶體所以就page fault了.

usertrap中會列印一些資訊:

  • scause暫存器中的內容, 值是15.

  • 程序id是3, 該pid是shell的pid.

  • sepc中的值是0x12ac. 在sh.asm的該位置處, 可以看到是一個store

    指令.

  • stval中的值是0x4008, 該值是出錯的虛擬記憶體的地址. xv6中的shell有4個page, 在第5個page也就是0x4008出錯.

Lazy allocation (moderate)

修改在trap.c中的程式碼來處理page fault, 之後返回到指令出錯位置來重新執行指令.

  1. 先在trap.c中的usertrap函式中處理. 當r_scause為13或15時, 通過r_stval獲取stval中的值, 也就是虛擬地址, 然後呼叫kalloc分配實體記憶體, 當無法分配實體記憶體時, 殺死程序, 否則就將實體地址對映到虛擬地址.

        else if (r_scause() == 15 || r_scause() == 13) {
    	  uint64 va = r_stval();
    		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;
    			}
    		}
    	} 
    
  2. 此時執行echo hipanic, 是uvmunmap在報錯, 在unmap page時, page並不存在, 因為之前lazy allocation並沒有實際分配記憶體. 所以當遇到標誌位V為0的page時, 是一個預期行為, 只需要continue.

    void
    uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free)
    {
      uint64 a;
      pte_t *pte;
    
      if((va % PGSIZE) != 0)
        panic("uvmunmap: not aligned");
    
      for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
        if((pte = walk(pagetable, a, 0)) == 0)
          panic("uvmunmap: walk");
        if((*pte & PTE_V) == 0)
          //panic("uvmunmap: not mapped");
    			continue;
        if(PTE_FLAGS(*pte) == PTE_V)
          panic("uvmunmap: not a leaf");
        if(do_free){
          uint64 pa = PTE2PA(*pte);
          kfree((void*)pa);
        }
        *pte = 0;
      }
    }
    

Lazytests and Usertests (moderate)

需要通過lazytests和usertests測試.

  1. 處理sbrk函式, 當引數為負數時, 直接呼叫growproc.

    uint64
    sys_sbrk(void)
    {
      int addr;
      int n;
    
      if(argint(0, &n) < 0)
        return -1;
      addr = myproc()->sz;
    	if (n >= 0) {
    		myproc()->sz += n;
    	} else if (n < 0) {
    		if (growproc(n) < 0) {
          return -1;
    		}
    	}
    //  if(growproc(n) < 0)
    //    return -1;
      return addr;
    }
    
  2. 當虛擬記憶體地址大於p->sz或小於user stack時, 殺死程序, 在殺死程序前, 記得釋放之前分配的實體記憶體. 需要修改trap.c中的usertrap函式, 對va的值判定.

      else if (r_scause() == 15 || r_scause() == 13) {
    	  uint64 va = r_stval();
    		uint64 ka = (uint64) kalloc();
    		if (ka == 0) {
    		  p->killed = 1;
    		} else if (va >= p->sz || PGROUNDDOWN(va) < PGROUNDDOWN(p->trapframe->sp)) {
    			kfree((void *) ka);
    		  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;
    			}
    		}
    	}
    
  3. 由於沒有實際分配實體記憶體, fork函式會出現一些問題, 原因是呼叫了uvmcopy, uvmcopy會呼叫walk和判定父程序的pte是否有效, 為了能夠正常工作, 當沒有找到pte和pte無效時, continue.

    int
    uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
    {
      pte_t *pte;
      uint64 pa, i;
      uint flags;
      char *mem;
    
      for(i = 0; i < sz; i += PGSIZE){
        if((pte = walk(old, i, 0)) == 0)
          //panic("uvmcopy: pte should exist");
    			continue;
        if((*pte & PTE_V) == 0)
          //panic("uvmcopy: page not present");
    			continue;
        pa = PTE2PA(*pte);
        flags = PTE_FLAGS(*pte);
        if((mem = kalloc()) == 0)
          goto err;
        memmove(mem, (char*)pa, PGSIZE);
        if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
          kfree(mem);
          goto err;
        }
      }
    }
    
  4. read/write等系統呼叫時, 傳入的虛擬地址沒有分配實際的實體記憶體時, 由於系統會先系統呼叫, 此時頁表被替換為kernel pagetable, 就無法直接訪問虛擬地址, 需要通過walkaddr將虛擬地址翻譯為實體地址, 如果這時候虛擬地址沒有被對映實體地址, 就會panic. 需要處理在uvmcopy中continue的過程, 手動分配一個實體地址並對映到相應的虛擬地址處.

    uint64
    walkaddr(pagetable_t pagetable, uint64 va)
    {
      pte_t *pte;
      uint64 pa;
    	struct proc *p = myproc();
    
      if(va >= MAXVA)
        return 0;
    
      pte = walk(pagetable, va, 0);
    /*
      if(pte == 0)
        return 0;
      if((*pte & PTE_V) == 0)
        return 0;
    */
    	if (pte == 0 || (*pte & PTE_V) == 0) {
    		pa = (uint64) kalloc();
    		if (pa == 0) {
    			p->killed = 1;
    			return 0;
    		} else if (PGROUNDDOWN(va) > p->sz || PGROUNDDOWN(va) < PGROUNDDOWN(p->trapframe->sp)) {
    			kfree((void *) pa);
    			return 0;
    		} else {
    		  memset((void *) ka, 0, PGSIZE);
    			va = PGROUNDDOWN(va);
    			if (mappages(pagetable, va, PGSIZE, pa, PTE_W | PTE_U | PTE_R) != 0) {
    			  kfree((void *) pa);
    				return 0;
    			}
    		}
    	}
      if((*pte & PTE_U) == 0)
        return 0;
      pa = PTE2PA(*pte);
      return pa;
    }