MIT 6.S081---Lab: page tables

1. Print a page table (easy)

改动:

  1. 变动参考freewalk,采用了递归的方法,还是比较容易理解的 :
//vm.c
void vmprint2(pagetable_t pagetable, int depth) {
  for(int i = 0; i < 512; i++){
    pte_t pte = pagetable[i];
    if(pte & PTE_V){
      // this PTE points to a lower-level page table.
      uint64 child = PTE2PA(pte);
      int j;
      printf("..");
      for (j = 1; j < depth; ++j) printf(" ..");
      printf("%d: pte %p pa %p\n", i, pte, child);
      if (depth < 3) vmprint2((pagetable_t)child, depth + 1);
    }
  }
}

void vmprint(pagetable_t pagetable) {
  printf("page table %p\n", pagetable);
  // there are 2^9 = 512 PTEs in a page table.
  vmprint2(pagetable, 1);
}
  1. 新增声明
//defs.h
void            vmprint(pagetable_t);
  1. 并在exec.c中添加调用
//exec.c
...
if (p->pid == 1) vmprint(p->pagetable);

最终结果如下图所示:
在这里插入图片描述

2. A kernel page table per process (hard)

在XV6原先的设计下,只有一张内核表,而每个进程都维护了一张自己的进程用户页表。
这样做的话有个问题,因为内核的页表不包含这些映射,进行系统调用时必须传入物理地址而不能传入虚拟地址,而本处要做的便是在每个用户进程内都维护一个内核页表,进程切换的时候在硬件维度就切换内核页表,分配时也要用这个内核页表。(也就是用新的内核页表替代用户页表)
改动:

  1. 在proc.h中新增内核页表字段:
//proc.h
// Per-process state
struct proc {
  ...
  pagetable_t kernel_pagetable;// kernel page table
  ...
};
  1. 新增一个创建内核页表的方法(不包含CLINT的映射,具体见第3题,用户的地址空间只到PLIC)
//vm.c
pagetable_t 
proc_kernel_pagetable() {
  pagetable_t kernel_page_table = (pagetable_t) kalloc();
  memset(kernel_page_table, 0, PGSIZE);
  // uart registers
  mappages(kernel_page_table, UART0, PGSIZE, UART0, PTE_R | PTE_W);

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

  // CLINT
  // mappages(kernel_page_table, CLINT, 0x10000, CLINT, PTE_R | PTE_W);

  // PLIC
  mappages(kernel_page_table, PLIC, 0x400000, PLIC, PTE_R | PTE_W);

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

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

  // map the trampoline for trap entry/exit to
  // the highest virtual address in the kernel.
  mappages(kernel_page_table, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X);
  return kernel_page_table;
}
  1. 修改kvminit,可调用上面的方法,通过复用减少代码量:
//vm.c
/*
 * create a direct-map page table for the kernel.
 */
void
kvminit()
{
  kernel_pagetable = proc_kernel_pagetable();

  mappages(kernel_pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);
}

  1. 注释原有的内核页栈映射代码(不再需要使用此方法了):
//proc.c
// initialize the proc table at boot time.
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();
}
  1. 修改allocproc(),创建内核页表并分配内核栈:
//proc.h
...
found:
  p->pid = allocpid();

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

    //Add seperated kernel page table
    // allocate a kernel page for it 
    char *pa = kalloc();
    if(pa == 0)
      panic("kalloc");
    p->kernel_pagetable = proc_kernel_pagetable();
    if(p->kernel_pagetable == 0){
      freeproc(p);
      release(&p->lock);
      return 0;
    }
    uint64 va = TRAMPOLINE - 2*PGSIZE;
    mappages(p->kernel_pagetable, va, PGSIZE, (uint64)pa, PTE_R | PTE_W);
	p->kstack = va;

  // An empty user page table.
...
  1. 修改scheduler(),在切换进程钱修改satp,切换完后再切回来:
//proc.c
...
int found = 0;
    for(p = proc; p < &proc[NPROC]; p++) {
      acquire(&p->lock);
      if(p->state == RUNNABLE) {
        // Switch to chosen process.  It is the process's job
        // to release its lock and then reacquire it
        // before jumping back to us.
        p->state = RUNNING;
        c->proc = p;
        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);
    }
    ...
  1. 修改kvmpa()
//vm.c
#include "spinlock.h"
#include "proc.h"
uint64
kvmpa(uint64 va)
{
  uint64 off = va % PGSIZE;
  pte_t *pte;
  uint64 pa;
  
  pte = walk(myproc()->kernel_pagetable, va, 0);//修改
  if(pte == 0)
    panic("kvmpa");
  if((*pte & PTE_V) == 0)
    panic("kvmpa");
  pa = PTE2PA(*pte);
  ret
  1. 修改freeproc()方法
//proc.c
static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  if(p->kernel_pagetable)
    proc_free_kernelpagetable(p->kstack, p->kernel_pagetable);
  p->kernel_pagetable = 0;
  ...
  1. 新增proc_free_kernelpagetable()方法
//proc.c
void proc_free_kernelpagetable(uint64 kstack, pagetable_t pagetable) {
  uvmunmap(pagetable, UART0, 1, 0);
  uvmunmap(pagetable, VIRTIO0, 1, 0);
  uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0);
  uvmunmap(pagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE, 0);
  uvmunmap(pagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE, 0);
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);
  uvmunmap(pagetable, kstack, 1, 1);
  freewalk(pagetable);
}
  1. 新增一些声明:
//defs.h
void            freewalk(pagetable_t);
pagetable_t     proc_kernel_pagetable(void);
void            proc_freepagetable(pagetable_t, uint64);
void            proc_free_kernelpagetable(uint64, pagetable_t);

最终执行usertests,结果如下:
在这里插入图片描述

3. Simplify copyin/copyinstr (hard)

本题的目标是要用copyin_new 来取代copyin,用copyinstr_new取代copyinstr,其中需要在修改原用户页表时也同步修改新建的内核页表。
具体改动如下:

  1. 新建pageCopyUser2Kernel函数,代表将用户页表复制到内核
//proc.c
//copy from user page table to kernel page table
void pageCopyUser2Kernel(pagetable_t upagetable, pagetable_t kpagetable, uint64 va) {
  pte_t *pte = walk(upagetable, va, 0);
  pte_t *kernelPte = walk(kpagetable, va, 1);
  *kernelPte = (*pte) & (~PTE_U);
}
//defs.h
void pageCopyUser2Kernel(pagetable_t, pagetable_t, uint64);
  1. 修改userinit方法,新增拷贝操作:
//proc.c
// Set up first user process.
void
userinit(void)
{
  struct proc *p;

  p = allocproc();
  initproc = p;
  
  // allocate one user page and copy init's instructions
  // and data into it.
  uvminit(p->pagetable, initcode, sizeof(initcode));
  p->sz = PGSIZE;

  pageCopyUser2Kernel(p->pagetable, p->kernel_pagetable, 0);

  ...
  1. 修改fork方法
//proc.c
int
fork(void)
{
  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }

  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  // Copy user page table to kernel page table.
  int j;
  for (j = 0; j < p->sz; j += PGSIZE) {
    pageCopyUser2Kernel(np->pagetable, np->kernel_pagetable, j);
  }
  np->sz = p->sz;

...
  1. 修改exec函数,释放旧的内核页表映射,并建立新的页表映射,同时增加用户空间地址不能大于PLIC的判定:
//exec.c
int
exec(char *path, char **argv)
{
  ...

  // Load program into memory.
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    uint64 sz1;
    if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
    //新增此判断
    if (sz1 >= PLIC) {
      goto bad;
    }
    sz = sz1;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }
  ...

  int j;
  uvmunmap(p->kernel_pagetable, 0, PGROUNDUP(p->sz), 0);
  for (j = 0; j < sz; j += PGSIZE) {
    pageCopyUser2Kernel(p->pagetable, p->kernel_pagetable, j);
  }
  ...
  1. 修改proc_free_kernelpagetable和freeproc函数,增加参数sz,并修改defs.h中相关的声明:
//proc.c
static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  if(p->kernel_pagetable)
    proc_free_kernelpagetable(p->kstack, p->kernel_pagetable, p->sz);
    
    ...
    
void proc_free_kernelpagetable(uint64 kstack, pagetable_t pagetable, uint64 sz) {
  uvmunmap(pagetable, UART0, 1, 0);
  uvmunmap(pagetable, VIRTIO0, 1, 0);
  uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0);
  uvmunmap(pagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE, 0);
  uvmunmap(pagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE, 0);
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);
  uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 0);
  uvmunmap(pagetable, kstack, 1, 1);
  freewalk(pagetable);
}
  1. 修改sys_sbrk函数:
uint64
sys_sbrk(void)
{
  int addr;
  int n, j;
  struct proc* p = myproc();

  if(argint(0, &n) < 0)
    return -1;
  addr = p->sz;
  if (n + addr >= PLIC)
    return -1;
  if(growproc(n) < 0)
    return -1;
  if (n > 0) {
    for (j = addr; j < addr + n; j += PGSIZE) {
      pageCopyUser2Kernel(p->pagetable, p->kernel_pagetable, j);
    }
  } else {
    for (j = addr - PGSIZE; j >= addr + n; j -= PGSIZE) {
      uvmunmap(p->kernel_pagetable, j, 1, 0);
    }
  }
  return addr;
}
  1. 在defs.h增加copyin_new和copyinstr_new的声明,并替换:
//defs.h
int             copyin_new(pagetable_t, char *, uint64, uint64);
int             copyinstr_new(pagetable_t, char *, uint64, uint64);

int
copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
{
  return copyinstr_new(pagetable, dst, srcva, max);
}

int
copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
{
  return copyinstr_new(pagetable, dst, srcva, max);
}

输入make grade结果如下:
在这里插入图片描述

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值