Page Tables

加速系统调用

在分配每个process的时候,增加一个映射到虚拟地址为USYSCALL(定义于kernel/memlayout.c)的物理页,保存某些可以直接访问的系统调用的相同结果,如进程的pid

这里要加速的调用程序是ugetpid(定义与user/ulib.c)

int
ugetpid(void)
{
  struct usyscall *u = (struct usyscall *)USYSCALL;
  return u->pid;
}

在kernel/proc.c中的allocproc函数中增加分配一个物理页,同时对进程记录proc.h增加一个usyscallpa的成员记录此物理地址

static struct proc*
allocproc(void)
{
...
// Allocate a page for usyscall
  if((p->usyscallpa = (struct usyscall *)kalloc()) == 0) {
      freeproc(p);
      release(&p->lock);
      return 0;
  }
  p->usyscallpa->pid = p->pid;
...
}

在proc_pagetable()增加对此物理页到USYSCALL的映射,不要忘了在proc_freepagetable中取消此映射(如果不取消,那么在freewalk中会出现leaf节点没有被先释放的问题)

pagetable_t
proc_pagetable(struct proc *p)
{
...
// map the va USYSCALL to the pa USYSCALL in user memory
  if (mappages(pagetable, USYSCALL, PGSIZE,
               (uint64)(p->usyscallpa), PTE_R) < 0) {
      uvmunmap(pagetable, USYSCALL, 1, 0);
      uvmfree(pagetable, 0);
      return 0;
  }

  return pagetable;
}
// Free a process's page table, and free the
// physical memory it refers to.
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);
  uvmunmap(pagetable, TRAPFRAME, 1, 0);
  // Remenber to unmap the usyscall mapping
  uvmunmap(pagetable, USYSCALL, 1, 0);
  uvmfree(pagetable, sz);
}

在free_proc()中释放此物理页的空间 

static void
freeproc(struct proc *p)
{
...
  if(p->usyscallpa)
      kfree((void*)p->usyscallpa);
  p->usyscallpa = 0;
...
}

出现问题:

xv6 kernel is booting

hart 2 starting
init: starting sh
$ hart 1 starting
./pgtbltest
ugetpid_test starting
usertrap(): unexpected scause 0x000000000000000d pid=4
            sepc=0x000000000000049c stval=0x0000003fffffd000
$ QEMU: Terminated

解决,在映射到虚拟地址时增加用户可访问标志PTE_U

if (mappages(pagetable, USYSCALL, PGSIZE, (uint64)(p->usyscallpa), PTE_R | PTE_U) < 0) {  // 可读 | user可访问

打印页表条目(pte),根据页表等级进行深度优先遍历,可以参考kernel/vm.c中的freewalk函数进行实现。

void vmpgtbprint(pagetable_t pagetable, int level) {
    for (int i = 0; i < 512; ++i) {
        pte_t pte = pagetable[i];
        if (pte & PTE_V) {
            uint64 pa = PTE2PA(pte);
            for (int l = level; l > 0; --l) {
                printf(" ..");
            }
            printf("%d: pte %p pa %p\n",i, pte, pa);
            if (level < 3) {
                vmpgtbprint((pagetable_t)pa, level+1);
            }
        }
    }
}

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

Detecting which pages have been accessed

通过定义PTE_A位(参考RISC-V privilege手册中的页表表项的位定义),在访问了一个页的时候,PTE_A会被置位。实现一个新的系统调用pgaccess,根据用户提供的起始虚拟地址,遍历页表,统计被访问的PTE_V。返回一个bitmask表示从起始地址开始哪些页被访问了。

注意在riscv.h中定义PTE_A的位。

int
sys_pgaccess(void)
{
  // lab pgtbl: your code here.
  struct proc *p = myproc();

  uint64 userpage;  // the starting virtual address of the first user page to check
  int pagenum;
  uint64 abitsaddr;

  if(argaddr(0, &userpage) < 0)
      return -1;
  if(argint(1, &pagenum) < 0)
      return -1;
  if(argaddr(2, &abitsaddr) < 0)
      return -1;

  if (pagenum > 64) {
      printf("Exceed the maximum of pages that can be scaned.\n");
      return -1;
  }
  uint64 bitmask = 0;
  for (int i = 0; i < pagenum; i++) {
      pte_t *pte = walk(p->pagetable, userpage + i * PGSIZE, 0);
      if ((*pte & PTE_V) && (*pte & PTE_A)) {
          bitmask |= (1 << i);
          *pte = (*pte & (~PTE_A)); // clear
//          printf("DEBUG: find accessed page. bitmask=%p, pte=%p\n", bitmask, *pte);
      }
  }

  if(copyout(p->pagetable, abitsaddr, (char *)&bitmask, sizeof(uint64)) < 0)
      return -1;
  return 0;
}