2020 MIT6.s081 os Lab: page tables

友情链接:全部实验哟


实验链接

https://pdos.csail.mit.edu/6.S081/2020/labs/pgtbl.html


Print a page table

代码更改如下:

  1. kernel/defs.h

    // vm.c
    void            vmprint(pagetable_t  pagetable);
    
  2. kernel/exec.c

    // 函数exec的return argc之前
    if (p->pid == 1) {
          vmprint(p->pagetable);
      }
    
  3. kernel/vm.c

    void dotprinter(int level, int indexid) {
        int dotcnt = 3 - level;
        for (int j = 0; j < dotcnt - 1; j++) {
            printf(".. ");
        }
        printf("..%d: ");
    }
    
    void vmprinthelper(pagetable_t pagetable, int level) {
        if (level < 0) {
            return;
        }
    
        for (int i = 0; i < 512; i++) {
            pte_t pte = pagetable[i];
            if ((pte & PTE_V) == 0) // invalid pte
                continue;
            dotprinter(level, i);
            // printf(" pte %p pa %p useraccessable: %d \n", pte, PTE2PA(pte), (pte & PTE_U));
            printf("pte %p pa %p\n", pte, PTE2PA(pte));
            vmprinthelper((pagetable_t)PTE2PA(pte), level - 1);
        }
    }
    
    void
    vmprint(pagetable_t  pagetable)
    {
        printf("page table %p\n", pagetable);
        vmprinthelper(pagetable, 2);
    }
    

A kernel page table per process

代码更改如下:

  1. 在文件kernel/proc.h中,为结构体proc添加如下字段

    pagetable_t kernel_pagetable;
    
  2. 在文件kernel/vm.c中,添加如下函数;

    // 注意添加头文件,否则编译失败
    #include "spinlock.h"
    #include "proc.h"
    
    pagetable_t
    kvmbuild(void)
    {
        pagetable_t  pagetable = (pagetable_t) kalloc();
        memset(pagetable, 0, PGSIZE);
    
        // uart registers
        mappages(pagetable, UART0, PGSIZE, UART0, PTE_R | PTE_W);
    
        // virtio mmio disk interface
        mappages(pagetable, VIRTIO0, PGSIZE, VIRTIO0, PTE_R | PTE_W);
    
        // CLINT
        mappages(pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);
    
        // PLIC
        mappages(pagetable, PLIC, 0x400000, PLIC, PTE_R | PTE_W);
    
        // map kernel text executable and read-only.
        mappages(pagetable, KERNBASE, (uint64)etext-KERNBASE, KERNBASE, PTE_R | PTE_X);
    
        // map kernel data and the physical RAM we'll make use of.
        mappages(pagetable, (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(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X);
    
        return pagetable;
    }
    
  3. 在文件kernel/proc.c中,做如下更改,主要作用是为进程创建内核页表,并建立内核页表与kstack的映射。

    void
    procinit(void)
    {
      struct proc *p;
      
      initlock(&pid_lock, "nextpid");
      for(p = proc; p < &proc[NPROC]; p++) {
          initlock(&p->lock, "proc");
          // 注释如下内容,因为此时全局的内核页表已经不需要kstack
          // kstack需在process私有的内核页表中进行映射
          // 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) {
          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 = proc_pagetable(p);
      if(p->pagetable == 0){
        freeproc(p);
        release(&p->lock);
        return 0;
      }
    
      // 以下为新添加内容
      // 为process创建私有的内核页表
      p->kernel_pagetable = kvmbuild();
      // 为内核页表建立与kstack的映射  
      char *pa = kalloc();
      if(pa == 0)
          panic("kalloc");
      uint64 va = KSTACK((int) (p - proc));
      mappages(p->kernel_pagetable, va, PGSIZE, (uint64)pa, PTE_R | PTE_W);
      p->kstack = va;
      // 结束,注意,上边添加的代码必须在下面代码之前
      // 因为下边的代码会用到p->kstack的值,而在此之前,我们
      // 必须对其进行赋值  
    
      // 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->context.sp = p->kstack + PGSIZE;
      return p;
    }
    
    
  4. 在文件kernel/proc.c中,对函数scheduler进行如下更改:

    void
    scheduler(void)
    {
      struct proc *p;
      struct cpu *c = mycpu();
      
      c->proc = 0;
      for(;;) {
        // Avoid deadlock by ensuring that devices can interrupt.
        intr_on();
        
        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;
    
            // begin +++++++++,
            //  切换页表地址为该进程私有页表的地址
            w_satp(MAKE_SATP(p->kernel_pagetable));
            sfence_vma();
            // end -----------    
            swtch(&c->context, &p->context);
    
            // Process is done running for now.
            // It should have changed its p->state before coming back.
            // begin +++++++++     
            // 切换为全局的kernel页表地址
            kvminithart(); // use kernel page table
            // end -----------    
            c->proc = 0;
    
    
            found = 1;
          }
          release(&p->lock);
        }
    #if !defined (LAB_FS)
        if(found == 0) {
          intr_on();
          asm volatile("wfi");
        }
    #else
        ;
    #endif
      }
    }
    
  5. 在文件kernel/proc.c的函数freeproc函数中,增加如下内容

    // begin +++++
    extern char etext[];  // kernel.ld sets this to end of kernel code.
    // end-------
    
    static void
    freeproc(struct proc *p)
    {
      if(p->trapframe)
         kfree((void*)p->trapframe);
      p->trapframe = 0;
    
      // begin ++++++  
      // 取消kstack映射,并销毁kstack对应的物理内存  
      if (p->kstack)
          uvmunmap(p->kernel_pagetable, p->kstack, 1, 1);
      p->kstack = 0;
      // 释放私有页表  
      if(p->kernel_pagetable)
          proc_free_kernel_pagetable(p->kernel_pagetable);
      p->kernel_pagetable = 0;
      // end ---------
    
      if(p->pagetable)
        proc_freepagetable(p->pagetable, p->sz);
      p->pagetable = 0;
    
      p->sz = 0;
      p->pid = 0;
      p->parent = 0;
      p->name[0] = 0;
      p->chan = 0;
      p->killed = 0;
      p->xstate = 0;
      p->state = UNUSED;
    }
    
    // 新增函数,用于取消映射和销毁页表
    void
    proc_free_kernel_pagetable(pagetable_t pagetable)
    {
        uvmunmap(pagetable, UART0, 1, 0);
        uvmunmap(pagetable, VIRTIO0, 1, 0);
        uvmunmap(pagetable, CLINT, 0x10000/PGSIZE, 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);
    
        freewalk(pagetable);
    }
    
    
  6. 在kernel/def.h中,增加我们新添加的函数

    +void            proc_free_kernel_pagetable(pagetable_t);
    +void            freewalk(pagetable_t);
    +pagetable_t     kvmbuild(void);
    
  7. 在kernel/vm.c中,更改以下函数(注意,该函数的更改在作业提示中并未给出)

    uint64
    kvmpa(uint64 va)
    {
      uint64 off = va % PGSIZE;
      pte_t *pte;
      uint64 pa;
    
      // 将walk函数的第一个参数改为进程私有的内核页表  
      struct proc* p = myproc();
      pte = walk(p->kernel_pagetable, va, 0);
      if(pte == 0)
        panic("kvmpa");
      if((*pte & PTE_V) == 0)
        panic("kvmpa");
      pa = PTE2PA(*pte);
      return pa+off;
    }
    

Simplify

还是按照题目的步骤来:

  1. 在文件kernel/vm.c中,更改函数copyincopyinstr

    int
    copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len)
    {
        return copyin_new(pagetable, dst, srcva, len);
    }
    
    // Copy a null-terminated string from user to kernel.
    // Copy bytes to dst from virtual address srcva in a given page table,
    // until a '\0', or max.
    // Return 0 on success, -1 on error.
    int
    copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max)
    {
      return copyinstr_new(pagetable, dst, srcva, max);
    }
    
  2. 在文件kernel/defs.h中,添加如下内容

    // vmcopyin.c
    int copyin_new(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len);
    int copyinstr_new(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max);
    
  3. 在文件kernel/vm.c中,添加用户态pagetable拷贝至kernel态pagetable的函数。

    void
    uvm2kvm(pagetable_t pagetable, pagetable_t kpagetable, uint64 old_size, uint64 new_size)
    {
        if (new_size < old_size)
            panic("new size lower than old size");
    
        if (PGROUNDUP(new_size) >= PLIC)
            panic("new size too big");
    
        uint64 begin = PGROUNDUP(old_size);
        uint64 end = PGROUNDUP(new_size);
        // printf("begin: %x, end: %x\n", begin, end);
        for (uint64 va = begin; va < end; va += PGSIZE) {
            pte_t* pte = walk(pagetable, va, 0);
            if (pte == 0)
                panic("user page table not found");
            pte_t* kpte = walk(kpagetable, va, 1);
            if (kpte == 0)
                panic("kernel page table not found");
            *kpte = (*pte) & (~PTE_U);
        }
    }
    
    
  4. 在文件kernel/defs.h中添加步骤三函数的声明

    void             uvm2kvm(pagetable_t, pagetable_t, uint64, uint64);
    
    
  5. 在kernel/proc.c中的函数userinit中添加映射

      p->state = RUNNABLE;
    
      // begin ++++++++
      uvm2kvm(p->pagetable, p->kernel_pagetable, 0, p->sz);
      // end ++++++++
    
      release(&p->lock);
    
  6. 在kernel/proc.c中的函数fork中添加映射

      // increment reference counts on open file descriptors.
      for(i = 0; i < NOFILE; i++)
        if(p->ofile[i])
          np->ofile[i] = filedup(p->ofile[i]);
      np->cwd = idup(p->cwd);
      // begin ++++++++
      uvm2kvm(np->pagetable, np->kernel_pagetable, 0, np->sz);
      // end ++++++++
    
      safestrcpy(np->name, p->name, sizeof(p->name));
    
  7. 在kernel/exec.c中的函数exec中添加映射

     .....
       // 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;
        sz = sz1;
        if(ph.vaddr % PGSIZE != 0)
          goto bad;
        if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
          goto bad;
        // begin ++++++++  
        if (sz1 >= PLIC)
          goto bad;
        // end ++++++++  
      }    
     .....    
    
     if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
        goto bad;
    
      // begin ++++++++
      // 因为exec函数会重新装载新进程,因此我们需要重新映射其内核的pagetable
      uvmunmap(p->kernel_pagetable, 0, PGROUNDUP(oldsz)/PGSIZE, 0);
      // 将新的pagetable拷贝至kernel的pagetable
      uvm2kvm(pagetable, p->kernel_pagetable, 0, sz);
      // end ++++++++
    
  8. 在kernel/proc.c中的函数growproc中添加映射(也就是sys_sbrk函数)

    int
    growproc(int n)
    {
      uint sz;
      struct proc *p = myproc();
    
      sz = p->sz;
      if(n > 0) {
        if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
          return -1;
        }
         // begin ++++++++  
        uvm2kvm(p->pagetable, p->kernel_pagetable, sz - n, sz);
         // end ++++++++  
      } else if(n < 0) {
        sz = uvmdealloc(p->pagetable, sz, sz + n);
         // begin ++++++++  
        uvmunmap(p->kernel_pagetable, PGROUNDUP(sz), (-n)/PGSIZE, 0);
         // end ++++++++  
      }
      p->sz = sz;
      return 0;
    }
    
    
  9. 修改kernel/vm.c文件中为每个进程创建内核页表的函数时,注意将Client的映射注释掉。(因为client的映射地址为0x10000, 而PLIC的映射地址为0x400000,即client低于PLIC的地址,而进程的pagetable拷贝至内核时的pagetable时,会导致原本0x10000多次被映射)

        // CLINT
    //    mappages(pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);
    
  10. 在kernel/proc.c文件中,修改释放kernel_pagetable的函数.(将client相关的代码也删掉,同时释放低地址空间的映射)

    void
    proc_free_kernel_pagetable(pagetable_t pagetable, uint64 sz)
    {
        uvmunmap(pagetable, UART0, 1, 0);
        uvmunmap(pagetable, VIRTIO0, 1, 0);
    //    uvmunmap(pagetable, CLINT, 0x10000/PGSIZE, 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);
        // begin++++++++
        uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 0);
        // end++++++++++
    
        freewalk(pagetable);
    }
    

实验结果

在这里插入图片描述


提交实验

$ git commit -m "lab page tabls"
$ make handin

查看结果

登录网站https://6828.scripts.mit.edu/2020/handin.py/student,可以看到提交的结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hdax6MS0-1658384528631)()]
在这里插入图片描述


参考链接

https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081

https://blog.csdn.net/u012419550/article/details/114701482

https://blog.csdn.net/u013577996/article/details/109582932

https://blog.csdn.net/rocketeerLi/article/details/121524760


github地址

https://github.com/aerfalwl/mit-xv6-labs-2020.git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值