# Speed up system calls

将常用的系统数据保存在用户空间的页表中,这样就不用每次使用系统调用来获取了。lab 中已经定义好了 USYSCALL 的宏和结构体,内容就是一个 pid,可以直接拿来用。

仿照 trapframe 的分配方法,将 usyscall 分配一下:

static struct proc*
allocproc(void)
{
  // ...
  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  if((p->usyscall = (struct usyscall *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  p->usyscall->pid=p->pid;
  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
 // ...
}

再仿照 trapframe 的映射方法,将 usyscall 映射一下:

// Create a user page table for a given process, with no user memory,
// but with trampoline and trapframe pages.
pagetable_t
proc_pagetable(struct proc *p)
{
  // ...
  // map the trapframe page just below the trampoline page, for
  // trampoline.S.
  if(mappages(pagetable, TRAPFRAME, PGSIZE,
              (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }
  // map the usyscall page just below the trapframe page
  if(mappages(pagetable, USYSCALL, PGSIZE,
              (uint64)(p->usyscall), PTE_R | PTE_U) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmunmap(pagetable, TRAPFRAME, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }
  return pagetable;
}

释放页面的操作:

static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
  if(p->usyscall)// 释放 usyscall
      kfree((void *)p->usyscall);
  p->usyscall=0;
  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;
}
// 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);
  uvmunmap(pagetable, USYSCALL, 1, 0);// 取消 usyscall 的映射
  uvmfree(pagetable, sz);
}

usyscall 中除了保存 pid,还可以保存通过系统调用获取但没有进行修改的其它变量,例如 uptime () 函数。

# Print a page table

需要理清页表的三级结构,不过 lab 中有现成的函数可以参考,不怎么用思考。

void vmprint_1(pagetable_t pagetable, uint64 depth){
    if(depth>2) return;
    if(depth==0)
        printf("page table %p\n", pagetable);
    char*buf=prefix[depth];
    // there are 2^9 = 512 PTEs in a page table.
    for(int i = 0; i < 512; i++){
        pte_t pte = pagetable[i];
        if(pte & PTE_V){// 有效的页表项
            // this PTE points to a lower-level page table.
            printf("%s%d: pte %p pa %p\n", buf, i, pte, PTE2PA(pte));
            uint64 child = PTE2PA(pte);
            vmprint_1((pagetable_t)child,depth+1);// 下一层
        }
    }
}
void vmprint(pagetable_t pagetable){
    vmprint_1(pagetable,0);// 分两个函数了,因为最好加个递归深度方便实现
}

# Detect which pages have been accessed

检查 PTE 的 PTE_A 标志位是否为 1 即可。

RISCV 的 PTE 结构如下:

在 kernel/risc.v 中声明 PTE_A,位于第 6 位。

#define PTE_A (1L << 6)

然后实现 sys_pgaccess,必须要初始化 bitmask 为 0,因为传入的页面长度不一定是 32!使用已有的 walk 函数,可以获取物理地址,免去对三级页表的层层转换了。

int
sys_pgaccess(void)
{
  uint64 addr,bitmask=0,buf;//init bitmask
  int len;
  argaddr(0,&addr);
  argint(1,&len);
  argaddr(2,&buf);
  if(len>32||len<0) return -1;// 处理的 page 最大为 32 个
  pte_t* pte;
  for(int i=0;i<len;addr+=PGSIZE,i++){
      pte=walk(myproc()->pagetable,addr,0);// 利用 walk 函数
      if(pte==0)
          panic("walk error");
      if(*pte & PTE_A){// 标志位为 1
          bitmask|=1<<i;// 更新 bitmask
          *pte &= ~PTE_A;// 清除 PTE_A
      }
  }
  copyout(myproc()->pagetable,buf,(char*)&bitmask,sizeof(bitmask));// 拷贝到指定位置返回
  return 0;
}

# Test

更新于