Skip to content

A kernel page table per process (hard)

根据提示一步一步走

在struct proc中为进程的内核页表增加一个字段

diff --git a/kernel/proc.h b/kernel/proc.h
index 9c16ea7..481118f 100644
--- a/kernel/proc.h
+++ b/kernel/proc.h
@@ -98,6 +98,7 @@ struct proc {
   uint64 kstack;               // Virtual address of kernel stack
   uint64 sz;                   // Size of process memory (bytes)
   pagetable_t pagetable;       // User page table
  pagetable_t prockpt;         // 进程的内核页表
   struct trapframe *trapframe; // data page for trampoline.S
   struct context context;      // swtch() here to run process
   struct file *ofile[NOFILE];  // Open files

实现一个修改版的kvminit,你将会考虑在allocproc中调用这个函数

原先的内核映射kvmmap写成的是固定参数, 只为kernel_pagetable作映射

添加一个功能一样的uvmmap函数,能传入不同的页表作为参数,不再是固定

diff --git a/kernel/vm.c b/kernel/vm.c
index 8137b05..0984442 100644
--- a/kernel/vm.c
+++ b/kernel/vm.c

@@ -121,6 +161,13 @@ kvmmap(uint64 va, uint64 pa, uint64 sz, int perm)
     panic("kvmmap");
 }
 
void
uvmmap(pagetable_t pagetable, uint64 va, uint64 pa, uint64 sz, int perm)
{
  if(mappages(pagetable, va, sz, pa, perm) != 0)
    panic("uvmmap");
}

 // translate a kernel virtual address to
 // a physical address. only needed for
 // addresses on the stack.

@@ -47,6 +49,37 @@ kvminit()
   kvmmap(TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
 }
 
pagetable_t
prockptinit()
{
  pagetable_t prockpt = uvmcreate();
  if(prockpt == 0) return 0;    //空指针

  // uart registers
  uvmmap(prockpt, UART0, UART0, PGSIZE, PTE_R | PTE_W);

  // virtio mmio disk interface
  uvmmap(prockpt, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);

  // CLINT
  uvmmap(prockpt, CLINT, CLINT, 0x10000, PTE_R | PTE_W);

  // PLIC
  uvmmap(prockpt, PLIC, PLIC, 0x400000, PTE_R | PTE_W);

  // map kernel text executable and read-only.
  uvmmap(prockpt, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);

  // map kernel data and the physical RAM we'll make use of.
  uvmmap(prockpt, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);

  // map the trampoline for trap entry/exit to
  // the highest virtual address in the kernel.
  uvmmap(prockpt, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);

  return prockpt;
}

 // Switch h/w page table register to the kernel's page table,
 // and enable paging.
 void

初始化一个进程的内核页表

确保每一个进程的内核页表都关于该进程的内核栈有一个映射。

在未修改的XV6中,所有的内核栈都在procinit中设置。

把这个功能部分或全部的迁移到allocproc中

diff --git a/kernel/proc.c b/kernel/proc.c
index dab1e1d..ad394df 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c

@@ -34,14 +34,14 @@ procinit(void)
       // Allocate a page for the process's kernel stack.
       // Map it high in memory, followed by an invalid
       // guard page.
      char *pa = kalloc();
      if(pa == 0)
        panic("kalloc");
      uint64 va = KSTACK((int) (p - proc));
      kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
      p->kstack = va;
      // char *pa = kalloc();
      // if(pa == 0)
      //   panic("kalloc");
      // uint64 va = KSTACK((int) (p - proc));
      // kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
      // p->kstack = va;
   }
  kvminithart();
  // kvminithart();
 }
 
 // Must be called with interrupts disabled,

@@ -121,6 +121,21 @@ found:
     return 0;
   }
 
  // 初始化一个进程的内核页表
  p->prockpt = prockptinit();
  if(p->prockpt == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  char *pa = kalloc();
  if(pa == 0)
    panic("kalloc");
  uint64 va = KSTACK((int) (p - proc));
  uvmmap(p->prockpt, 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));

修改scheduler()来加载进程的内核页表到核心的satp寄存器(参阅kvminithart来获取启发)

改写一个新函数uvminithart,同样是之前的问题,修改以能传入页表参数

diff --git a/kernel/vm.c b/kernel/vm.c
index 8137b05..0984442 100644
--- a/kernel/vm.c
+++ b/kernel/vm.c

@@ -56,6 +89,13 @@ kvminithart()
   sfence_vma();
 }
 
void
uvminithart(pagetable_t pagetable)
{
  w_satp(MAKE_SATP(pagetable));
  sfence_vma();
}

 // Return the address of the PTE in page table pagetable
 // that corresponds to virtual address va.  If alloc!=0,
 // create any required page-table pages.

没有进程运行时scheduler()应当使用kernel_pagetable

diff --git a/kernel/proc.c b/kernel/proc.c
index dab1e1d..ad394df 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c

@@ -473,8 +513,13 @@ scheduler(void)
         // before jumping back to us.
         p->state = RUNNING;
         c->proc = p;

        uvminithart(p->prockpt);

         swtch(&c->context, &p->context);
 
        kvminithart();

         // Process is done running for now.
         // It should have changed its p->state before coming back.
         c->proc = 0;

你需要一种方法来释放页表,而不必释放叶子物理内存页面, 在freeproc中释放一个进程的内核页表

diff --git a/kernel/proc.c b/kernel/proc.c
index dab1e1d..ad394df 100644
--- a/kernel/proc.c
+++ b/kernel/proc.c

@@ -130,6 +145,26 @@ found:
   return p;
 }
 
void
freeprockpt(pagetable_t prockpt)
{
  // similar to the freewalk method
  // there are 2^9 = 512 PTEs in a page table.
  for(int i = 0; i < 512; i++){
    pte_t pte = prockpt[i];
    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);
      freeprockpt((pagetable_t)child);
      prockpt[i] = 0;
    } else if(pte & PTE_V){
      continue;
    }
  }
  kfree((void*)prockpt);
}


 // free a proc structure and the data hanging from it,
 // including user pages.
 // p->lock must be held.

@@ -143,6 +178,11 @@ freeproc(struct proc *p)
     proc_freepagetable(p->pagetable, p->sz);
   p->pagetable = 0;
   p->sz = 0;
  if(p->prockpt){
    uvmunmap(p->prockpt, p->kstack, 1, 1);
    freeprockpt(p->prockpt);
  }
  p->prockpt = 0;
   p->pid = 0;
   p->parent = 0;
   p->name[0] = 0;

将需要的函数定义添加到 kernel/defs.h 中

diff --git a/kernel/defs.h b/kernel/defs.h
index ebc4cad..2e302d5 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -159,9 +159,12 @@ int             uartgetc(void);
 
 // vm.c
 void            kvminit(void);
pagetable_t     prockptinit(void);
 void            kvminithart(void);
void            uvminithart(pagetable_t);
 uint64          kvmpa(uint64);
 void            kvmmap(uint64, uint64, uint64, int);
void            uvmmap(pagetable_t, uint64, uint64, uint64, int);
 int             mappages(pagetable_t, uint64, uint64, uint64, int);
 pagetable_t     uvmcreate(void);
 void            uvminit(pagetable_t, uchar *, uint);

修改 kvmpa() 函数

kvmpa() 函数用于将内核虚拟地址转换为物理地址, 其中调用 walk() 函数时使用了全局的内核页表. 此时需要换位当前进程的内核页表

若不进行改动会出现 panic: kvmpa 或者 virtio_disk_intr status 的错误

diff --git a/kernel/vm.c b/kernel/vm.c
index 8137b05..0984442 100644
--- a/kernel/vm.c
+++ b/kernel/vm.c
@@ -5,6 +5,8 @@
 #include "riscv.h"
 #include "defs.h"
 #include "fs.h"
#include "spinlock.h"
#include "proc.h"
 
 /*
  * the kernel's page table.

@@ -132,7 +179,8 @@ kvmpa(uint64 va)
   pte_t *pte;
   uint64 pa;
   
  pte = walk(kernel_pagetable, va, 0);
  // pte = walk(kernel_pagetable, va, 0);
  pte = walk(myproc()->prockpt, va, 0);
   if(pte == 0)
     panic("kvmpa");
   if((*pte & PTE_V) == 0)