• 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);
      
      • 1
      • 2
    2. kernel/exec.c

      // 函数exec的return argc之前
      if (p->pid == 1) {
            vmprint(p->pagetable);
        }
      
      • 1
      • 2
      • 3
      • 4
    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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

    A kernel page table per process

    代码更改如下:

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

      pagetable_t kernel_pagetable;
      
      • 1
    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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
    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;
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
    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
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
    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);
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
    6. 在kernel/def.h中,增加我们新添加的函数

      +void            proc_free_kernel_pagetable(pagetable_t);
      +void            freewalk(pagetable_t);
      +pagetable_t     kvmbuild(void);
      
      • 1
      • 2
      • 3
    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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

    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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    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);
      
      • 1
      • 2
      • 3
    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);
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    4. 在文件kernel/defs.h中添加步骤三函数的声明

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

        p->state = RUNNABLE;
      
        // begin ++++++++
        uvm2kvm(p->pagetable, p->kernel_pagetable, 0, p->sz);
        // end ++++++++
      
        release(&p->lock);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    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));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    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 ++++++++
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
    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;
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
    9. 修改kernel/vm.c文件中为每个进程创建内核页表的函数时,注意将Client的映射注释掉。(因为client的映射地址为0x10000, 而PLIC的映射地址为0x400000,即client低于PLIC的地址,而进程的pagetable拷贝至内核时的pagetable时,会导致原本0x10000多次被映射)

          // CLINT
      //    mappages(pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);
      
      • 1
      • 2
    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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

    实验结果

    在这里插入图片描述


    提交实验

    $ git commit -m "lab page tabls"
    $ make handin
    
    • 1
    • 2

    查看结果

    登录网站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

  • 相关阅读:
    1 基础知识
    (1.1)bark
    node.js及npm的基本理解
    一次关于引入自定义JAR包JDK版本问题总结
    Docker 安装(方法4):使用二进制文件压缩包安装
    [数据结构]链表OJ题 (二) 反转链表
    cuda debug
    js中的类class class源码
    中高级试题」:MVCC 实现原理是什么?
    全志v853体验可能会遇到的问题
  • 原文地址:https://blog.csdn.net/u014110320/article/details/125911497