Lab3

页表实验。

这里我觉得主要关注的就是页表的初始化以及页表如何工作,涉及的函数较多,也比较复杂。

初始化流程

下面是 kernel/main.c 的 main() 代码。

初始化的流程是:

kree() 将所有物理空间初始化为一页一页,并将底部地址加入 freelist ->

kvminit() 初始化kernel_pgtbl ->

kvminithart() 初始化 satp寄存器,在riscv中satp存储一级页表的位置 ->

procinit() 初始化每一个进程的内核栈

void
main()
{
  if(cpuid() == 0){
    consoleinit();
#if defined(LAB_PGTBL) || defined(LAB_LOCK)
    statsinit();
#endif
    printfinit();
    printf("\n");
    printf("xv6 kernel is booting\n");
    printf("\n");
    kinit();         // physical page allocator
    kvminit();       // create kernel page table
    kvminithart();   // turn on paging
    procinit();      // process table
    trapinit();      // trap vectors
    trapinithart();  // install kernel trap vector
    plicinit();      // set up interrupt controller
    plicinithart();  // ask PLIC for device interrupts
    binit();         // buffer cache
    iinit();         // inode cache
    fileinit();      // file table
    virtio_disk_init(); // emulated hard disk
#ifdef LAB_NET
    pci_init();
    sockinit();
#endif    
    userinit();      // first user process
    __sync_synchronize();
    started = 1;
  } else {
    while(started == 0)
      ;
    __sync_synchronize();
    printf("hart %d starting\n", cpuid());
    kvminithart();    // turn on paging
    trapinithart();   // install kernel trap vector
    plicinithart();   // ask PLIC for device interrupts
  }

  scheduler();        
}

kvminit() 内核页表

kvminit() 首先通过kalloc() 为 内核页表分配一页的空间 并初始化为 *kernel_pagetable = 0.

kvmmap() 其实就是调用了一个 mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm).

mappages()的功能就是将 va虚拟地址 映射到 pa物理地址。需要注意的是,此时xv6还没开启地址翻译,因此这几个 kvmmap() 传入的参数映射都是直接映射,也就是说 va是多少,pa就是多少。

比较有趣的就是

KERNBASE:0x80000000

Trampoline: Va位于 MaxVa - PGSIZE

void
kvminit()
{
  kernel_pagetable = (pagetable_t) kalloc();
  memset(kernel_pagetable, 0, PGSIZE);

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

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

  // CLINT
  kvmmap(CLINT, CLINT, 0x10000, PTE_R | PTE_W);

  // PLIC
  kvmmap(PLIC, PLIC, 0x400000, PTE_R | PTE_W);

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

  // map kernel data and the physical RAM we'll make use of.
  kvmmap((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.
  kvmmap(TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
}

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

procinit() 进程内核栈

值得注意的是 KSTACK()返回值每次间隔两页,因为有一页作为 kstack 的空间,有一页用在Guard Page(利用页错误防止溢出导致其他页被污染,该页不会映射到物理空间,因此不会造成浪费)

每一个用户进程都有一个对应的kernel stack,在xv6里是最多 64个进程,procinit() 为每一个进程都初始化好了 kstack 。

初始化完要重新将新的内核页表存入satp寄存器。

这里就有一个疑问,就是 kernel_pagetable 里面存着每个进程的 kstack 有什么用呢?

kstack是进程的内核栈

进程栈分为用户栈和进程栈

在 user mode 下运行时使用用户栈,在 superior mode 下运行时使用内核栈

那么既然设置了satp寄存器,就意味着地址翻译开始了,那每个进程的首级页表存在哪里呢?

在proc 结构体中:

struct proc {
  ...
  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  ...
};
void
procinit(void)
{
  struct proc *p;
  
  initlock(&pid_lock, "nextpid");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");

      // 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;
  }
  kvminithart();
}

用户进程创建

用户进程的页表是如何创建的?

static struct proc*
allocproc(void)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state == UNUSED) {
      // 找到一个 UNUSED的进程分配给它
      goto found;
    } else {
      release(&p->lock);
    }
  }
  return 0;

found:
  p->pid = allocpid();

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

  // An empty user page table.创建一个用户页表,赋值给 p->pagetable
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // 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->kstack 已经在 procinit() 里面初始化了
  p->context.sp = p->kstack + PGSIZE;

  return p;
}

通过调用 proc_pagetable() 函数,创建一个用户页表

uvmcreate() 给用户页表分配了物理空间

然后在va顶部映射 trampoline,在trampoline底下映射trapfame

pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;

  // An empty page table.
  pagetable = uvmcreate();
  if(pagetable == 0)
    return 0;

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

  return pagetable;
}

Last updated