# 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; | |
} |