xv6-lab3-pgtbl

Lab:page tables

前置知识

  • xv6 book 第三章
  • 熟悉kernel/memlayout.h、vm.c、kalloc.c

Print a page table

实验目标

实现一个函数 vmprintf(pagetable_t)

该函数能按照以下格式打印页表

page table 0x0000000087f6e000
..0: pte 0x0000000021fda801 pa 0x0000000087f6a000
.. ..0: pte 0x0000000021fda401 pa 0x0000000087f69000
.. .. ..0: pte 0x0000000021fdac1f pa 0x0000000087f6b000
.. .. ..1: pte 0x0000000021fda00f pa 0x0000000087f68000
.. .. ..2: pte 0x0000000021fd9c1f pa 0x0000000087f67000
..255: pte 0x0000000021fdb401 pa 0x0000000087f6d000
.. ..511: pte 0x0000000021fdb001 pa 0x0000000087f6c000
.. .. ..510: pte 0x0000000021fdd807 pa 0x0000000087f76000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000

在exec.c/return argc 前插入

if(p->pid==1) vmprint(p->pagetable);

实验实现

话不多说,先看提示

  • vmprintf() 放在 kernel/vm.c

  • 使用 kernel/riscv.h 底部的宏定义

  • 可以参考函数 freewalk

  • defs.h 中声明该函数

  • 使用 %p 打印64位的十六进制 PTEs 和 地址

再看提示中提到的 freewalk 函数

void
freewalk(pagetable_t pagetable)
{
  // there are 2^9 = 512 PTEs in a page table.
  for(int i = 0; i < 512; i++){
    pte_t pte = pagetable[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);
      //递归查找
      freewalk((pagetable_t)child);
      pagetable[i] = 0;
    } else if(pte & PTE_V){//到达页表
      panic("freewalk: leaf");
    }
  }
  kfree((void*)pagetable);
}

仿写一个 travel

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



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

A kernel page table per process

实验目标

xv6 拥有一个单独的内核页表供所有进入内核的进程使用,该页表与实际物理内存直接映射,不需要转换地址。

但如果想使用用户态的一个地址时,当内核态需要使用一个用户态指针时,需要翻译转换虚拟地址为物理地址。因此,本实验的目标是给每个进程创建一个内核态页表

实验实现

接下来根据提示一步一步完成

  • struct proc 中添加一个字段表示内核页表
/*	proc.h	*/
struct proc {
  struct spinlock lock;
......
  pagetable_t kernel_pagetable;     // Kernel page table

};
  • 创建一个类似 kvminit 的函数来初始化每个页表并在 allocproc 中调用
  • 确保你的进程内核页表拥有到进程内核栈的映射。未修改前的代码在 procinit 完成这一映射动作

请添加图片描述

对于trampoline 和 trapframe 的部分补充(xv6 book page.26)

At the top of the address space xv6 reserves a page for a trampoline and a page mapping the process’s trapframe to switch to the kernel, as we will explain in Chapter 4.

综上,我们进行初始化页表函数的实现

/*	defs.h	*/
void            each_kvmmap(pagetable_t, uint64, uint64, uint64, int);
pagetable_t     each_kvminit();


/*	vm.c	*/
void
each_kvmmap(pagetable_t my, uint64 va, uint64 pa, uint64 sz, int perm)
{
  if(mappages(my, va, sz, pa, perm) != 0)
    panic("kvmmap");
}

pagetable_t
each_kvminit()
{
  pagetable_t each  = (pagetable_t) kalloc();
  memset(each, 0, PGSIZE);

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

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

  // CLINT
  each_kvmmap(each, CLINT, CLINT, 0x10000, PTE_R | PTE_W);

  // PLIC
  each_kvmmap(each, PLIC, PLIC, 0x400000, PTE_R | PTE_W);

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

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

  return each;
}


/*	proc.c	*/

void
procinit(void)
{
  struct proc *p;
  
  initlock(&pid_lock, "nextpid");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");
      
/*
	注释掉这一部分,在allocproc中初始化了内核页表后再进行内核栈的初始化和映射
*/
      // 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;

  ......

  //creat a kernel page table and dispatch a kernel stack
  p->kernel_pagetable = each_kvminit();
  char *pa = kalloc();
  if(pa == 0) panic("allocpron:kalloc\n");
/*
	原来的分配根据第几个进程,在全局内核页表中分配,而现在拥有了单独的内核页表
	这里根据图3.4和补充得知,内核栈只要在 MAXVA-2*PGSIZE-heap.size 就行
	我假设堆大小为 2*PGSIZE
*/
  uint64 va = MAXVA - 4*PGSIZE;
  mappages(p->kernel_pagetable, va, PGSIZE,(uint64)pa, PTE_R|PTE_W);
  p->kstack = va;

    ......
        
  return p;
}
  • 修改 scheduler() , 将你的内核页表加载到内核寄存器 satp(可以仿照kvminithart). 在调用 w_satp() 后必须调用 sfence_vma(). 并且在没有进程运行时,内核应该使用全局内核页表 kernel_pagetable
void
scheduler(void)
{

    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state == RUNNABLE) {
        
		...
        //切换到进程内核页表
        w_satp(MAKE_SATP(p->kernel_pagetable));
        sfence_vma();
        
        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;

        found = 1;
      }
      release(&p->lock);
    }
  }
}

  • freeproc() 中释放进程内核页表
  • 对于某些地址你需要释放页目录和页表而不释放物理内存。(这里某些地址指所有内核页表共享的那一部分物理内存,此处只有内核栈是不共享的,需要释放其物理内存)
/*	proc.c	*/
//模仿freewalk,但对叶子节点只做取消映射操作
void 
free_kernel_pagetable(pagetable_t pagetable)
{
  for(int i = 0; i < 512; ++i)
  {
    pte_t pte = pagetable[i];
    if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0)//page direct
    {
      pagetable[i] = 0;
      free_kernel_pagetable((pagetable_t)PTE2PA(pte));
    } else if(pte & PTE_V){
      pagetable[i] = 0;
    }
  }
  kfree((void *)pagetable);
}

void 
proc_free_kernel_pagetable(struct proc *p)
{
  //release the phycial memory first
  if(p->kstack)
  {
    pte_t *pte = walk(p->kernel_pagetable, p->kstack, 0);
    kfree((void *)PTE2PA(*pte));
    p->kstack = 0;
  }
  free_kernel_pagetable(p->kernel_pagetable);
}

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

  if(p->kernel_pagetable)
    proc_free_kernel_pagetable(p);
  p->kernel_pagetable = 0;
    
 ......
}


Simplify copyin/copyinstr(hard)

实验目标

本实验将测试上一实验中所构建的内核页表是否可用。你的目标是将用户页表映射全部复制到内核页表

实验实现

先看提示

  • copyin_new 替换 copyin, 用 copyinstr_new 替换 copyinstr
  • 页表标志位 PTE_U 为真时,内核态无法访问该页表
  • 当内核更改用户页表映射时,同步更改到内核页表。如 fork(),exec(),sbrk()
  • 别忘记在 userinit() 中复制第一个用户态页表
  • 用户态页表地址不能高于 PLIC

综上,代码实现如下

/*	vm.c	*/
/*
	先仿照uvmcopy创建一个从用户态页表复制到内核态页表的函数
*/
void
uvm_user2ker_copy(pagetable_t u, pagetable_t k, uint64 start, uint64 end)
{
  
  pte_t *user;
  pte_t *kernel;
  for(uint64 i = start; i < end; i += PGSIZE)
  {
    user = walk(u, i, 0);
    kernel = walk(k, i, 1);
/*
	根据内核态页表的特点--直接映射到物理内存
	我们无需使用mappage建立映射
	记得消除PTE_U标志位
*/
    *kernel = (*user) & (~PTE_U);
  }

}



/*	proc.c	*/
void
userinit(void)
{
  struct proc *p;
  ......
  uvminit(p->pagetable, initcode, sizeof(initcode));
  p->sz = PGSIZE;

  uvm_user2ker_copy(p->pagetable, p->kernel_pagetable, 0, p->sz);
  ......
}


int
fork(void)
{
  int i, pid;
  	....
  np->sz = p->sz;
  uvm_user2ker_copy(np->pagetable, np->kernel_pagetable, 0, np->sz);
    ....
  return pid;
}


/*	exec.c	*/
int
exec(char *path, char **argv)
{
  char *s, *last;
  ......
  // Load program into memory.
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    ......
    uint64 sz1;
    if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
/*
	判断是否越界
*/
    if(sz1 > PLIC)
      goto bad;
	......
  }
  ......
  uvm_user2ker_copy(p->pagetable, p->kernel_pagetable, 0, sz);

  if(p->pid == 1)
    vmprint(p->pagetable);
  
  return argc; // this ends up in a0, the first argument to main(argc, argv)
  ......
}


/*	syproc.c	*/
uint64
sys_sbrk(void)
{
  .....
  if(growproc(n) < 0)
    return -1;
  if(n > 0)
  {
    uvm_user2ker_copy(myproc()->pagetable, myproc()->kernel_pagetable, addr, addr + n);
  }
  //考虑到内核页表内容是根据用户页表改变,所以只增加/覆盖内容,不删除内容(我猜可行

  return addr;
}

实验结果

在这里插入图片描述

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值