XV6學習 (4)Lab pgtbl

這一個實驗主要是學習XV6的頁表(分頁機制),關於分頁機制的相關內容已經寫在XV6學習 (3)裏面了。
代碼放在Github上。git

這一個就是要實現一個vmprint()函數來遍歷頁表並打印,能夠仿照freewalk()函數來寫。github

void printwalk(pagetable_t pagetable, uint level) {
  char* prefix;
  if (level == 2) prefix = "..";
  else if (level == 1) prefix = ".. ..";
  else prefix = ".. .. ..";

  for(int i = 0; i < 512; i++){
    pte_t pte = pagetable[i];
    if(pte & PTE_V){
      uint64 pa = PTE2PA(pte);
      printf("%s%d: pte %p pa %p\n", prefix, i, pte, pa);
      if((pte & (PTE_R|PTE_W|PTE_X)) == 0){
        printwalk((pagetable_t)pa, level - 1);
      }
    }
  }
}

void
vmprint(pagetable_t pagetable) {
  printf("page table %p\n", pagetable);
  printwalk(pagetable, 2);
}

在這裏是經過pte & (PTE_R|PTE_W|PTE_X)來判斷當前PTE是否是指向下一級頁表。app

A kernel page table per process (hard)

這一題是要爲每一個進程分配一個獨立的內核頁表,而不是使用全局的內核頁表。這一題主要是爲了下一題作準備。函數

所以,首先就是要創建一個函數來建立內核頁表。這個函數內部只要仿照kvminit函數,給對應的頁面建立映射就好了。post

pagetable_t
proc_kpagetable() {
  pagetable_t kpagetable;
  kpagetable = uvmcreate();
  if(kpagetable == 0)
    return 0;

  ukvmmap(kpagetable, UART0, UART0, PGSIZE, PTE_R | PTE_W);
  ukvmmap(kpagetable, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
  ukvmmap(kpagetable, CLINT, CLINT, 0x10000, PTE_R | PTE_W);
  ukvmmap(kpagetable, PLIC, PLIC, 0x400000, PTE_R | PTE_W);
  ukvmmap(kpagetable, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
  ukvmmap(kpagetable, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
  ukvmmap(kpagetable, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);

  return kpagetable;
}

void
ukvmmap(pagetable_t pagetable ,uint64 va, uint64 pa, uint64 sz, int perm)
{
  if(mappages(pagetable, va, sz, pa, perm) != 0)
    panic("ukvmmap");
}

以後,將procinit函數中的內核棧的映射移動到allocproc函數中。在allocproc函數中先建立一個內核頁表,以後將內核棧映射到對應位置上就能夠了。學習

static struct proc*
allocproc(void)
{
  ...
  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }

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

  // create the kernel page table.
  p->kpagetable = proc_kpagetable(p);
  if(p->kpagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // init the kernel stack.
  char *pa = kalloc();
  if(pa == 0)
    panic("kalloc");
  uint64 va = KSTACK((int) (p - proc));
  ukvmmap(p->kpagetable, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
  p->kstack = va;

  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;

  return p;
}

scheduler函數中在進程切換以後對內核頁表也進行切換,記得用sfence_vma刷新TLB。ui

// switch the kernel pagetable.
w_satp(MAKE_SATP(p->kpagetable));
sfence_vma();

最後一步就是在freeproc的時候對內核頁表和內核棧也進行釋放。spa

static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;

  // free kstack
  pte_t *pte = walk(p->kpagetable, p->kstack, 0);
  if(pte == 0)
    panic("freeproc: free kstack");
  kfree((void*)PTE2PA(*pte));
  p->kstack = 0;

  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  if(p->kpagetable)
    proc_freekpagetable(p->kpagetable);
  ...
}

void
proc_freekpagetable(pagetable_t kpagetable)
{
  for (int i = 0; i < 512; i++) {
		pte_t pte = kpagetable[i];
		if (pte & PTE_V) {
			if ((pte & (PTE_R|PTE_W|PTE_X)) == 0) {
				uint64 child = PTE2PA(pte);
				proc_freekpagetable((pagetable_t)child);
			}
		}
	}
	kfree((void*)kpagetable);
}

Simplify copyin/copyinstr (hard)

這一個就是利用上一步的進程內核頁表,將進程的地址空間映射到內核頁表中,來簡化copy_in操做,使得copy_in不須要去查找進程的頁表來進行地址轉換。code

之因此能進行這個映射就是由於進程的地址空間是從 0 開始增加的,而內核須要的地址空間是從PLIC開始增加的(CLINT僅在內核初始化的時候使用,以後就不須要了)。所以,進程的地址空間是能夠從 0 增加到PLIC的,而這裏就須要在growproc中對進程的地址空間進行限制,避免其超出PLIC進程

if (PGROUNDUP(sz + n) >= PLIC) return -1;

在XV6中,會涉及到進程頁表改變的只有三個地方:fork exec sbrk,所以要在對進程頁表改變後,將其同步到內核頁表中。

// copy page table
void
ukvmcopy(pagetable_t pagetable, pagetable_t kpagetable, uint64 oldsz, uint64 newsz)
{
  pte_t *src, *dest;
  uint64 cur;

  if (newsz < oldsz)
    return;

  oldsz = PGROUNDUP(oldsz);
  for(cur = oldsz; cur < newsz; cur += PGSIZE){
    if ((src = walk(pagetable, cur, 0)) == 0)
      panic("ukvmcopy: pte not exist");
    if ((dest = walk(kpagetable, cur, 1)) == 0)
      panic("ukvmcopy: pte alloc failed");

    uint64 pa = PTE2PA(*src);
    *dest = PA2PTE(pa) | (PTE_FLAGS(*src) & (~PTE_U));
  }
}

頁表的同步就經過上面的ukvmcopy函數來實現,在上述三個函數對頁表進行改變後,就須要調用這個函數進行同步。

這裏有一個問題就是在newsz < oldsz的時候,即釋放內存的時候,沒有對頁表項進行刪除,後面須要完善。

相關文章
相關標籤/搜索