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可访问
Print a page table
打印页表条目(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;
}