MIT6.s081 lab pagetable

lab pagetable

1、print a pagetable

void vmprint_helper(pagetable_t pagetable, int depth) {
    static char* indent[] = {
            "",
            "..",
            ".. ..",
            ".. .. .."
    };
    if (depth <= 0 || depth >= 4) {
        panic("vmprint_helper: depth not in {1, 2, 3}");
    }

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

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

2、A kernel page table per process

0.记得把所有新添加的函数的定义写到defs.h中

1.在proc.h的struct proc中添加kpagetable

2.模仿kvminit写一个创建每个进程的内核页表的函数ukvminit

// vm.c

// 为每个进程创建一个内核页表
pagetable_t
ukvminit()
{
    pagetable_t kpagetable = (pagetable_t) kalloc();
    if(kpagetable == 0)
      return kpagetable;
    memset(kpagetable, 0, PGSIZE);

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

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

    // CLINT
    ukvmmap(kpagetable, CLINT, CLINT, 0x10000, PTE_R | PTE_W);

    // PLIC
    ukvmmap(kpagetable, PLIC, PLIC, 0x400000, PTE_R | PTE_W);

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

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

    return kpagetable;
}

// 为每个进程的内核页表添加mapping
void
ukvmmap(pagetable_t kpagetable, uint64 va, uint64 pa, uint64 sz, int perm)
{
    if(mappages(kpagetable, va, sz, pa, perm) != 0)
        panic("ukvmmap");
}

3.修改allocproc函数,并将内核栈mapping到每个进程的内核页表中

// proc.h
static struct proc*
allocproc(void)
{
  ...

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

  // 分配一个内核页表
  p->kpagetable = ukvminit();
  if (p->kpagetable == 0) {
      freeproc(p);
      release(&p->lock);
      return 0;
  }

  // 将每个进程的kernel stack添加mapping到内核页表中
  // 此处的pa无需分配内存,原因是在全局内核页表映射内核栈时已经分配了,所以物理地址可以通过全局内核页表直接计算
  uint64 va = KSTACK((int)(p - proc));
  pte_t pa = kvmpa(va);
  memset((void *)pa, 0, PGSIZE);
  ukvmmap(p->kpagetable, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
  p->kstack = va;


 ...
}

4.修改scheduler代码

//proc.c
void
scheduler(void)
{
  ...
        // 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;
    
        // 切换到要运行的进程的内核页表并刷新TLB
        w_satp(MAKE_SATP(p->kpagetable));
        sfence_vma();
    
	    // 将进程cpu的上下文切换成当前进程的
        swtch(&c->context, &p->context);

        // 切换回全局的内核页表
        kvminithart();
        ...
}

5.释放内核页表

对比释放进程的用户页表可以发现,用户页表在释放时需要清理对应的物理内存,而释放内核页表时不需要

// proc.c
static void
freeproc(struct proc *p)
{
  ...
  p->state = UNUSED;
  if (p->kstack) {
      p->kstack = 0;
  }
  if (p->kpagetable) {
      freeprockvm(p);
      p->kpagetable = 0;
  }
}

//vm.c
// 清除进程p的内核页表的mapping
void
freeprockvm(struct proc* p)
{
    pagetable_t kpagetable = p->kpagetable;

    ukvmunmap(kpagetable, p->kstack, PGSIZE/PGSIZE);
    ukvmunmap(kpagetable, TRAMPOLINE, PGSIZE/PGSIZE);
    ukvmunmap(kpagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE);
    ukvmunmap(kpagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE);
    ukvmunmap(kpagetable, PLIC, 0x400000/PGSIZE);
    ukvmunmap(kpagetable, CLINT, 0x10000/PGSIZE);
    ukvmunmap(kpagetable, VIRTIO0, PGSIZE/PGSIZE);
    ukvmunmap(kpagetable, UART0, PGSIZE/PGSIZE);
    ufreewalk(kpagetable);
}

// 给每个进程的内核页表解除mapping
void
ukvmunmap(pagetable_t pagetable, uint64 va, uint64 npages)
{
    uint64 a;
    pte_t *pte;

    if((va % PGSIZE) != 0)
        panic("ukvmunmap: not aligned");

    for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
        if((pte = walk(pagetable, a, 0)) == 0)
            goto clean;
        if((*pte & PTE_V) == 0)
            goto clean;
        if(PTE_FLAGS(*pte) == PTE_V)
            panic("ukvmunmap: not a leaf");

        clean:
          *pte = 0;
    }
}

// 将3级PTES全部置成0并且回收他们的内存
void
ufreewalk(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){
            uint64 child = PTE2PA(pte);
            ufreewalk((pagetable_t)child);
            pagetable[i] = 0;
        }
        pagetable[i] = 0;
    }
    kfree((void*)pagetable);
}

6.注意在vm.c中导入spinlock.h和proc.h(spinlock在前),因为在freeprockvm的形参中定义了struct proc

3、Simplify copyin/copyinstr

1.替换copyin和copyinstr

2.写两个函数用来复制用户页表的mapping到内核页表中

// 复制old页表mapping从虚拟地址start到end至new页表中
// 返回0表示成功,-1表示失败
int 
pagecopy(pagetable_t old, pagetable_t new, uint64 start, uint64 end)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;

  start = PGROUNDUP(start);
  for (i = start; i < end; i += PGSIZE) {
    if ((pte = walk(old, i, 0)) == 0) 
      panic("pagecopy: pte should exist");
    if ((*pte & PTE_V) == 0)
      panic("pagecopy: page not present");
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte) & (~PTE_U);// 把PTE_U去除
    if (umappages(new, i, PGSIZE, pa, flags) != 0) {
      uvmunmap(new, 0, i / PGSIZE, 1);
      return -1;
    }
  }
  return 0;
}

// 复制mapping
int
umappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{
  uint64 a, last;
  pte_t *pte;

  a = PGROUNDDOWN(va);
  last = PGROUNDDOWN(va + size - 1);
  for(;;) {
    if ((pte = walk(pagetable, a, 1)) == 0)
      return -1;
    *pte = PA2PTE(pa) | perm | PTE_V;
    if (a == last)
      break;
    a += PGSIZE;
    pa += PGSIZE;
  }
  return 0;
}

3.修改fork、exec、growproc、userinit

// proc.c

int
fork(void)
{
  ...

  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;

  // 复制用户内存mapping到子进程的内核页表
  if (pagecopy(np->pagetable, np->kpagetable, 0, np->sz) != 0) {
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  ...
}

int
growproc(int n)
{
  ...
  if(n > 0){
    if(sz + n >= PLIC || (sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
      return -1;
    }
      // 扩大时更新
    if (pagecopy(p->pagetable, p->kpagetable, p->sz, sz) != 0)
      return -1;
      
  } else if(n < 0){
    sz = uvmdealloc(p->pagetable, sz, sz + n);
      // 缩小时更新
      if (sz != p->sz) {
      uvmunmap(p->kpagetable, PGROUNDUP(sz), (PGROUNDUP(p->sz) - PGROUNDUP(sz)) / PGSIZE, 0);
     }
  }
  p->sz = sz;
  return 0;
}

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

  // 复制用户页表的用户内存mapping到内核页表
  pagecopy(p->pagetable, p->kpagetable, 0, p->sz);

  ...
}

// exec.c
int
exec(char *path, char **argv)
{
  ...
  // Commit to the user image.
  oldpagetable = p->pagetable;
  p->pagetable = pagetable;
  p->sz = sz;
  p->trapframe->epc = elf.entry;  // initial program counter = main
  p->trapframe->sp = sp; // initial stack pointer
  proc_freepagetable(oldpagetable, oldsz);

  // 将用户页表的用户内存空间的mapping复制到内核页表中
  if (pagecopy(p->pagetable, p->kpagetable, 0, p->sz) != 0) {
    goto bad;
  }
  ukvminithart(p->kpagetable);

 ...
}

4、评测

posted @   silly19  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示