[MIT6.S081]Lab3: Page Tables
# [MIT6.S081]Lab3: Page Tables
Lab: page tables (mit.edu) (opens new window)
# 1、Speed up system calls
这个 assignment 中我们要为 getpid 这个系操作加快速度,因为系统调用需要我们保存上下文,还要从用户态切换到内核态,因此会损耗很多时间。于是我们为每个进程新分配了一个页,其虚拟地址位于 USYSCALL
,就在 TRAPFRAME
的下面,并且在这个页的开头用 struct usyscall
保存了进程的 pid。
我们先要在 proc.h
中添加一个 struct usyscall
类型的变量
struct usyscall *usyscall;
接着我们在 proc.c
中进行我们的操作,我们先要在进程初始化的时候,去分配一个新页,并将进程的 pid
赋值给 usyscall
,在 allocproc
中加入
if((p->usyscall = (struct usyscall *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
p->usyscall->pid = p->pid;
之后我们在 proc_pagetable
中添加映射
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;
}
可以直接模仿着 trapframe
的写法,需要注意的是如果映射失败的话在解除映射时要把 TRAMPLINE
和 TRAPFRAME
也解除了。
接着在释放 page 的时候我们也要接触和 USYSCALL
的映射
void proc_freepagetable(pagetable_t pagetable, uint64 sz) {
uvmunmap(pagetable, TRAMPOLINE, 1, 0);
uvmunmap(pagetable, TRAPFRAME, 1, 0);
uvmunmap(pagetable, USYSCALL, 1, 0);
uvmfree(pagetable, sz);
}
还有释放进程的时候也要把分配的空间回收了,在 freeproc
中加入
if (p->usyscall) {
kfree((void *)p->usyscall);
}
p->usyscall = 0;
最后还有要注意的一点,也是我找了半天的问题,就是在我们要在调用 proc_pagetable
来为进程创建用户页表之前分配 page 给 usyscall,不然没法正确的添加映射,毕竟 page 都没分配,怎么映射。
# 2、Print a page table
这个就比较简单了,要在 exec.c
return argc 之前打印 pid = 1 的进程页表。
我们先在 exec.c
return 前添加函数
if (p->pid == 1) {
vmprint(p->pagetable);
}
return argc; // this ends up in a0, the first argument to main(argc, argv)
将 vmprint 函数进行声明,在 defs.h
中
void vmprint(pagetable_t);
之后我们在 vm.c
中实现该函数,
根据 hints,我们可以仿照函数 freewalk
来写,显然那我们需要用到递归。
我们先打印不需要加入递归的第一行,之后调用另外实现的递归打印函数即可。
void vmprint(pagetable_t pagetable) {
printf("page table %p\n", pagetable);
recprint(pagetable, 1);
}
recprint
函数传递了两个参数,一个是 pagetable_t,还有一个便是递归的层数,因为我们要通过打印 ..
来表示我们在树中的深度。
void recprint(pagetable_t pagetable, int cnt) {
for (int i = 0; i < 512; i++) { // 遍历页表
pte_t pte = pagetable[i]; // 取出 PTE
if (pte & PTE_V) { // 如果是合法的,则需要打印,先打印 层数 - 1 个 ".. ",带有空格!而最后一个没有空格。
for (int j = 0; j < cnt - 1; j++) {
printf(".. ");
}
printf("..%d: pte %p pa %p\n", i, pte, PTE2PA (pte)); // 打印最后的 .. 和相关的信息,调用了 riscv.h 中的 PTE2A 函数
if ((pte & (PTE_R|PTE_W|PTE_X)) == 0) { // 如果不是叶子,则需要继续递归打印下去,只有叶子才有这些标志位
recprint((pagetable_t)PTE2PA(pte), cnt + 1);
}
}
}
}
具体看代码注释就好了。
# 3、Detecting which pages have been accessed
这个 assignment 也比较简单,首先我们根据 riscv
的手册,可以知道 PTE_A
位于 1L << 6
处
因此我们在 riscv.h
中添加定义
#define PTE_A (1L << 6) // whether have been accessed
之后我们直接在 sysproc.c
中实现即可
#ifdef LAB_PGTBL
int
sys_pgaccess(void)
{
// lab pgtbl: your code here.
uint64 va;
int len;
uint64 des_addr;
if (argaddr(0, &va) < 0)
return -1;
if (argint(1, &len) < 0)
return -1;
if (argaddr(2, &des_addr) < 0)
return -1;
int res = 0;
for (int i = 0; i < len; i++) {
if (va >= MAXVA) {
return -1;
}
pte_t *pte = walk(myproc()->pagetable, va, 0);
if (*pte == 0) return -1;
if (*pte & (PTE_A)) {
res |= (1 << i);
*pte &= (~PTE_A);
}
va += PGSIZE;
}
if (copyout(myproc()->pagetable, des_addr, (char *)&res, sizeof(res)) < 0) {
return -1;
};
return 0;
}
#endif
首先要保证虚拟地址是合法的,也就是 va < MAXVA
接着我们每次用 walk
取出 va
对应的 PTE,记得要用指针,因为后面要修改 PTE。
res |= (1 << i);
用来表示保存答案,*pte &= (~PTE_A);
表示将 PTE_A
标志位置为 0。
最后再用 copyout
将答案传到用户态即可。