1. 程式人生 > 其它 >6.S081-2021-Lab3 Pgtbl學習筆記

6.S081-2021-Lab3 Pgtbl學習筆記

Speed up system calls

根據hints檢視kernel/proc.c中的函式proc_pagetable

// kernel/proc.c
// Create a user page table for a given process,
// with no user memory, but with trampoline pages.
pagetable_t
proc_pagetable(struct proc *p)
{
  // map the trampoline code (for system call return)
  // at the highest user virtual address.
  // only the supervisor uses it, on the way
  // to/from user space, so not PTE_U.
  if(mappages(pagetable, TRAMPOLINE, PGSIZE,
              (uint64)trampoline, PTE_R | PTE_X) < 0){
    uvmfree(pagetable, 0);
    return 0;
  }

  // map the trapframe just below TRAMPOLINE, for trampoline.S.
  if(mappages(pagetable, TRAPFRAME, PGSIZE,
              (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }
  ....
}

結合程式碼以及手冊,USYSCALL頁面的位置在heap之前,trapfram之後

only read 對應賦予PTE_R | PTE_U

// kernel/proc.c
...
  // map the trapframe just below TRAMPOLINE, for trampoline.S.
  if(mappages(pagetable, TRAPFRAME, PGSIZE,
              (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }
  // map the usyscall just below TRAPFRAME
  if (mappages(pagetable, USYSCALL, PGSIZE,
              (uint64) (p->usyscall), PTE_R | PTE_U) < 0) {
      uvmunmap(pagetable, USYSCALL, 1, 0);
      uvmunmap(pagetable, TRAMPOLINE, 1, 0);
      uvmfree(pagetable, 0);
      return 0;
  }
  return pagetable;

分配和初始化頁面

  • Don't forget to allocate and initialize the page in allocproc().

allocate函式中參考empty user page的方式依葫蘆畫瓢為usyscall分配空間

同時要記得將pid返回到使用者態,這樣才能在使用者態直接使用pid

kernel/proc.hstruct proc中新增 struct usyscall* usyscall

  ....
  // Allocate a USYSCALL page.
  if((p->usyscall = (struct usyscall *)kalloc()) == 0)  {
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  p->usyscall->pid = p->pid;

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  ...

解除對映的部分有兩個函式要修改

  • Make sure to free the page in freeproc().

這裡一定要釋放,不然後面的test過不了

在函式proc_freepagetable新增程式碼

  // Free a process's page table, and free the
  // physical memory it refers to.
  void
  proc_freepagetable(pagetable_t pagetable, uint64 sz)
  {
    // free USYSCALL
    uvmunmap(pagetable, USYSCALL, 1, 0);
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmunmap(pagetable, TRAPFRAME, 1, 0);
    uvmfree(pagetable, sz);
  }

在函式freeproc新增程式碼

  static void
  freeproc(struct proc *p)
  {
    if(p->trapframe)
      kfree((void*)p->trapframe);
    p->trapframe = 0;
    if (p->usyscall)
        kfree((void *) p->usyscall);
    if(p->pagetable)
      proc_freepagetable(p->pagetable, p->sz);
    ...
  }

exec.c檔案的return argc語句前插入if(p->pid==1) vmprint(p->pagetable)

參考freewalk

void
freewalk(pagetable_t pagetable)
{
  // 一張頁表由512個頁表項(PTE)組成
  for(int i = 0; i < 512; i++){
    // 取出當前頁表項
    pte_t pte = pagetable[i];
    // 當該條目有效,但卻無法讀、寫、執行的時候
    // 說明這是一條指向子頁面的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);
      freewalk((pagetable_t)child);
      pagetable[i] = 0;
    } else if(pte & PTE_V){
      panic("freewalk: leaf");
    }
  }
  kfree((void*)pagetable);
}

我們的vmprint基本就是copy freewalk只不過不需要free頁面

照著格式來

kernel/vm.c編寫函式vmprint,別忘了在kernel/defs.h中宣告

void
backtrace(pagetable_t pagetable, int level)
{
  for(int i = 0; i < 512; i++){
    pte_t pte = pagetable[i];
    if ((pte & PTE_V) && (pte & (PTE_R | PTE_W | PTE_X)) == 0) {
      uint64 child = PTE2PA(pte);
      for (int j=0; j <= level; j++) {
        printf("..");
        if ((j+1) <= level) {
          printf(" ");
        }
      }
      printf("%d: pte %p pa %p\n", i, pte, child);
      backtrace((pagetable_t)child, level+1);
    }
    else if (pte & PTE_V) {
      uint64 child = PTE2PA(pte);
      printf(".. .. ..%d: pte %p pa %p\n", i, pte, child);
    }
  }
}
   
void vmprint(pagetable_t pagetable)
{
  printf("page table %p\n", pagetable);
  backtrace(pagetable, 0);
}

Detecting which pages have been accessed

這個實驗需要我們判斷頁面是否被訪問過,為此需要新增一個標誌位PTE_A來標識頁面是否被訪問過

  • You'll need to define PTE_A, the access bit, in kernel/riscv.h. Consult the RISC-V manual to determine its value.

根據提示查閱手冊

kernel/riscv.h中新增

#define PTE_A (1L << 6) // access bit

  • You'll need to parse arguments using argaddr() and argint()
  • First, it takes the starting virtual address of the first user page to check. Second, it takes the number of pages to check. Finally, it takes a user address to a buffer to store the results into a bitmask (a datastructure that uses one bit per page and where the first page corresponds to the least significant bit)

根據這兩個提示我們可以猜測出三個引數的型別應該分別為uint64,int,uint64,分別是第一個需要檢查的使用者頁的虛擬地址,要檢查的頁表數,以及輸出結果的使用者態地址(因為核心態跟使用者態的空間是不互通的,所以要藉助 copyout 將 bitmask 傳給使用者態程式)

根據引數可以知道我們是要檢視n個頁的,問題是隻給了第一個使用者頁的虛擬地址,之後的n個頁怎麼檢視。其實只需要每次迴圈加上頁表大小PGSIZE就能到下一個頁了

同時我們來看一下walk,它的作用是根據虛擬地址返回頁表中對應的PTE

pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
  if(va >= MAXVA)
    panic("walk");

  for(int level = 2; level > 0; level--) {
    // 主要注意這裡,請結合下圖理解
    pte_t *pte = &pagetable[PX(level, va)];
    if(*pte & PTE_V) {
      pagetable = (pagetable_t)PTE2PA(*pte);
    } else {
      if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
        return 0;
      memset(pagetable, 0, PGSIZE);
      *pte = PA2PTE(pagetable) | PTE_V;
    }
  }
  return &pagetable[PX(0, va)];
}
#define PGSHIFT 12  // 對應圖中的offset
#define PXSHIFT(level)  (PGSHIFT+(9*(level))) //L2 L1 L0 都是9位,+9*level是為了定位到對應的level上
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) //獲得L2或L1,L0的bits

明白了walk的功能就可以完成這一部分的任務了

  • 通過虛擬地址VA確定PTE(walk來完成)
  • 檢查PTE的標誌位PTE_A是否為1,1表示被訪問過
    • 記得將PTE_A清0,以防影響再次呼叫sys_pgaccess時影響判斷
  • VA+=PGSIZE得到下一個頁面的虛擬地址
#ifdef LAB_PGTBL
uint64
sys_pgaccess(void)
{ 
  uint64 va;
  int page_num;
  uint64 user_bitmask_addr;
  if(argaddr(0, &va) < 0)
    return -1;
  if(argint(1, &page_num) < 0)
    return -1;
  if(argaddr(2, &user_bitmask_addr) < 0)
    return -1;
  // 32是看test得出來的
  // It's okay to set an upper limit on the number of pages that can be scanned.
  if(page_num < 0 || page_num > 32)
    return -1;
  
  uint32 bitmask = 0;
  pte_t *pte;
  struct proc *p = myproc();

  for(int i = 0; i < page_num; i++){
    if(va >= MAXVA)
      return -1;
	// alloc不為0,walk就會為找不到對應pte地址的va申請一個頁
    // 顯然不存在,就表示沒有被訪問過
    // 我們無需建立頁,這不是當前函式的職責
    pte = walk(p->pagetable, va, 0);
    
    if(pte == 0)
      return -1;
    if(*pte & PTE_A){
      bitmask |= (1 << i);
      // Be sure to clear PTE_A after checking if it is set.
      *pte &= (~PTE_A);
    }
    va += PGSIZE;    
  }

  if(copyout(p->pagetable, user_bitmask_addr, (char*)&bitmask, sizeof(bitmask)) < 0)
    return -1;
  return 0;
}
#endif