[6.S081] Lab page tables

86 阅读3分钟

Speed up system calls (easy)

这里提到操作系统可以通过在用户空间和内核之间共享只读区域中的数据来加快某些系统调用。这消除了执行这些系统调用时内核交叉的需要。

在kern/memlayout.h中可以看到用户的内存结构为

image.png

这块只读区域在Trapframe的下一页


kernel/proc.c中的allocproc()在进程表里找到未使用的进程并初始化分配(这里有一些锁的概念没有很了解),这个函数主要初始化了进程的trapframe,pagetable,以及一些上下文,我们也需在这里初始化进程的usyscall结构

// Allocate a usyscall page
 16   if((p->usyscall = (struct usyscall *)kalloc()) == 0){
 15     freeproc(p);
 14     release(&p->lock);
 13     return 0;
 12   }
 11   p->usyscall->pid = p->pid;

在这边对pagetable的初始化调用了proc_pagetable()


kernel/proc.c中的proc_pagetable()为一个进程创建pagetable,在这个函数内给页表添加了进程的trampoline页面,trapframe页面的映射,usyscall页面的映射也在这里添加。只需稍稍复制上面的代码即可

  if(mappages(pagetable, TRAMPOLINE, PGSIZE,
  1               (uint64)trampoline, PTE_R | PTE_X) < 0){ /* TRAMPOLINE映射到的地方是相同的 */
  2     uvmfree(pagetable, 0);
  3     return 0;
  4   }
  5 
  6   // map the trapframe just below TRAMPOLINE, for trampoline.S.
  7   if(mappages(pagetable, TRAPFRAME, PGSIZE,
  8               (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
  9     uvmunmap(pagetable, TRAMPOLINE, 1, 0);
 10     uvmfree(pagetable, 0);
 11     return 0;
 12   }

     // map the usyscall just below TRAPFRAME
 30   if(mappages(pagetable, USYSCALL, PGSIZE,
 31               (uint64)(p->usyscall), PTE_R | PTE_U) < 0){ /* 注意标志位的设置 */
 32     uvmunmap(pagetable, TRAMPOLINE, 1, 0); /* 当映射失败时需要将前映射解除并释放页表 */
 33     uvmunmap(pagetable, TRAPFRAME, 1, 0);
 34     uvmfree(pagetable, 0);
 35     return 0;
 36   }
// Create PTEs for virtual addresses starting at va that refer to
  5 // physical addresses starting at pa. va and size might not
  6 // be page-aligned. Returns 0 on success, -1 if walk() couldn't
  7 // allocate a needed page-table page.
  8 int
  9 mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm);

分配了就要释放所以在kernel/proc.c中的freeproc()也要注意添加释放usyscall的过程

  if(p->usyscall)
 10     kfree((void*)p->usyscall);
 11   p->usyscall = 0;

这里就可以直接在用户态通过地址获取

  6 int
  5 ugetpid(void)
  4 {
  3   struct usyscall *u = (struct usyscall *)USYSCALL;
  2   return u->pid;
  1 }

无需进行系统调用

  1 uint64
21  sys_getpid(void)
  1 {
  2   return myproc()->pid;
  3 }

Print a page table (easy)

在操作系统init时打印出xv6三级页表中的内容

image.png


在kernel/riscv.h里定义了相关的数据结构以及macro

19 // shift a physical address to the right place for a PTE.
 18 #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
 17 
 16 #define PTE2PA(pte) (((pte) >> 10) << 12)
 15 
 14 #define PTE_FLAGS(pte) ((pte) & 0x3FF)
 
 /* pagetable保存了图中satp寄存器中的内容 */
367 typedef uint64 *pagetable_t; // 512 PTEs

kernel/vm.c中的freewalk()递归的遍历pagetable,与我们要实现的函数很相似,可以参考

  3 // Recursively free page-table pages.
  2 // All leaf mappings must already have been removed.
  1 void freewalk(pagetable_t pagetable)
  1 { 
  2   // there are 2^9 = 512 PTEs in a page table.
  3   for(int i = 0; i < 512; i++){
  4     pte_t pte = pagetable[i];
  5     if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
  6       // this PTE points to a lower-level page table.
  7       uint64 child = PTE2PA(pte);
  8       freewalk((pagetable_t)child);
  9       pagetable[i] = 0;
 10     } else if(pte & PTE_V){
 11       panic("freewalk: leaf");
 12     }
 13   }
 14   kfree((void*)pagetable);
 15 } 

我们也使用递归的方式来写

24 void vmprint(pagetable_t pagetable, uint64 count) {
 23   if (count == 0) {
 22     printf("page table %p\n", pagetable);
 21     vmprint(pagetable, count+1);
 20     return;
 19   }
 18   if (count == 4)
 17     return;
 16   // there are 2^9 = 512 PTEs in a page table.
 15   for(int i = 0; i < 512; i++){
 14     pte_t pte = pagetable[i];
 13     if(pte & PTE_V){
 12       // this PTE points to a lower-level page table.
 11       uint64 child = PTE2PA(pte);
 10       if (count == 1)
  9         printf("..");
  8       else if (count == 2)
  7         printf(".. ..");
  6       else if (count == 3)
  5         printf(".. .. ..");
  4       printf("%d: pte %p pa %p\n",i,pte,child);
  3       vmprint((pagetable_t)child, count+1);
  2     }
  1   }
42  }

Detecting which pages have been accessed (hard)

首先在kernel/sysproc.c中的sys_pgaccess()获取传递给pgaccess()的参数并调用pgaccess()

  0 int sys_pgaccess(void)
  1 {
  2 // lab pgtbl: your code here.
  3     // get argument
  4     uint64 buf;
  5     int number;
  6     uint64 ans;
  7     if (argaddr(0, &buf) < 0) return -1;
  8     if (argint(1, &number) < 0) return -1;
  9     if (argaddr(2, &ans) < 0) return -1;
 10     return pgaccess((void*)buf, number, (void*)ans);
 11 }

检查页面是否被访问,我们需要检查PTE中的Accessed位

// kernel/riscv.h
#define PTE_A (1L << 6) // accessed
#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) 
 /* 获取virtual address中对应第level级页表的偏移量 */

walk()可以获取第三级页表virtual address的PTE

// kernel/vm.c
 13 // Return the address of the PTE in page table pagetable
 12 // that corresponds to virtual address va.  If alloc!=0,
 11 // create any required page-table pages.
 10 //
  9 // The risc-v Sv39 scheme has three levels of page-table
  8 // pages. A page-table page contains 512 64-bit PTEs.
  7 // A 64-bit virtual address is split into five fields:
  6 //   39..63 -- must be zero.
  5 //   30..38 -- 9 bits of level-2 index.
  4 //   21..29 -- 9 bits of level-1 index.
  3 //   12..20 -- 9 bits of level-0 index.
  2 //    0..11 -- 12 bits of byte offset within the page.
  1 pte_t *
106 walk(pagetable_t pagetable, uint64 va, int alloc) /* 找到在第三层页表中PTE */
  1 {
  2   if(va >= MAXVA)
  3     panic("walk");
  4 
  5   for(int level = 2; level > 0; level--) {
  6     pte_t *pte = &pagetable[PX(level, va)];
  7     if(*pte & PTE_V) {
  8       pagetable = (pagetable_t)PTE2PA(*pte);
  9     } else {
 10       if(!alloc || (pagetable = (pde_t*)kalloc()) == 0)
 11         return 0;
 12       memset(pagetable, 0, PGSIZE);
 13       *pte = PA2PTE(pagetable) | PTE_V;
 14     }
 15   }
 16   return &pagetable[PX(0, va)];
 17 }

最后写出pgaccess()

步骤

  1. 遍历页面
  2. 获取页面的PTE
  3. 检查PTE,若页面被访问过则设置ans对应的bit位
  4. 清除PTE的PTE_A访问位(这里不是很理解,pgaccess()访问PTE会不会导致PTE_A被设置,要是不会的话,为何要清除PTE_A,让其保持原样不就好了?)
  5. 将ans复制到用户空间
// kernel/sysproc.c
  5 uint64 pgaccess(void *pg, int number, void *store) {
  4     struct proc *p = myproc();
  3     if (p == 0) {
  2         return 1;
  1     }
455     pagetable_t pagetable = p->pagetable;
  1     int ans = 0;
  2     for (int i = 0; i < number; i++) {
  3         pte_t *pte;
  4         pte = walk(pagetable, ((uint64)pg) + (uint64)PGSIZE * i, 0);
  5         if (pte != 0 && ((*pte) & PTE_A)) {
  6             ans |= 1 << i;
  7             *pte ^= PTE_A;  
          /*检查PTE_A是否设置后,请务必清除PTE_A。否则,将无法确定自pgaccess()最后一次调用以来是否访问了该页*/
  8         }
  9     }
 10     // copyout
 11     return copyout(pagetable, (uint64)store, (char *)&ans, sizeof(int)    );
 12 }
// kernel/vm.c
25 // Copy from kernel to user.
 24 // Copy len bytes from src to virtual address dstva in a given page table.
 23 // Return 0 on success, -1 on error.
 22 int
 21 copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
 20 {
 19   uint64 n, va0, pa0;
 18 
 17   while(len > 0){
 16     va0 = PGROUNDDOWN(dstva);
 15     pa0 = walkaddr(pagetable, va0);
 14     if(pa0 == 0)
 13       return -1;
 12     n = PGSIZE - (dstva - va0);
 11     if(n > len)
 10       n = len;
  9     memmove((void *)(pa0 + (dstva - va0)), src, n);
  8 
  7     len -= n;
  6     src += n;
  5     dstva = va0 + PGSIZE;
  4   }
  3   return 0;
  2 }

image.png