MIT 6.828 - Lab - Page tables
课程主页:https://pdos.csail.mit.edu/6.828/2023/index.html
Speed up system calls
执行系统调用涉及到上下文的切换,这个过程虽然很快,但不至于可以被完全忽略,对某些功能很简单的系统调用而言,执行上下文切换代价还是不小的。本实现要求实现一个 getpid 函数,它返回当前进程的进程号。实现起来很容易,就是返回 proc->pid,但只有在内核态才能拿到 proc 结构体,这就需要执行系统调用。这里可以考虑将一个物理页同时映射到用户地址空间和内核地址空间。在进程创建的时候,就将 pid 写到这块内存中,这样在用户空间只需要从中读取 pid 即可。
在 proc 结构体中增加一个 usyscall 的指针,这个指针执行共享的内存。
struct proc {
// ...
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
+ struct usyscall *usyscall;
};
在 allocproc 中分配内存,并将 pid 写入:
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);
// ...
}
在 proc_pagetable 中,将 usyscall 映射到 USYSCALL 位置,并设置访问权限:
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;
}
+ 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;
}
这里将 p->usyscall 映射到虚拟地址 USYSCALL 处,并设置用户只读权限。在 ulib.c 中,ugetpid 可以直接从这个地址读取 pid:
int
ugetpid(void)
{
struct usyscall *u = (struct usyscall *)USYSCALL;
return u->pid;
}
Print a page table
这个实验的目的是打印页表,做法就是按照页表的结构遍历整个页表树,实现如下:
static void dovmprint(pagetable_t pagetable, int level) {
// 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)) {
for (int j = 0; j <= level; j++) {
printf(" ..");
}
printf("%d: pte %p pa %p\n", i, pte, PTE2PA(pte));
if (level < 2) {
uint64 child = PTE2PA(pte);
dovmprint((pagetable_t)child, level+1);
}
}
}
}
void vmprint(pagetable_t pagetable) {
dovmprint(pagetable, 0);
}
打印格式如下:
..0: pte 0x0000000021fd9c01 pa 0x0000000087f67000
.. ..0: pte 0x0000000021fd9801 pa 0x0000000087f66000
.. .. ..0: pte 0x0000000021fda01b pa 0x0000000087f68000
.. .. ..1: pte 0x0000000021fd9417 pa 0x0000000087f65000
.. .. ..2: pte 0x0000000021fd9007 pa 0x0000000087f64000
.. .. ..3: pte 0x0000000021fd8c17 pa 0x0000000087f63000
..255: pte 0x0000000021fda801 pa 0x0000000087f6a000
.. ..511: pte 0x0000000021fda401 pa 0x0000000087f69000
.. .. ..509: pte 0x0000000021fdcc13 pa 0x0000000087f73000
.. .. ..510: pte 0x0000000021fdd007 pa 0x0000000087f74000
.. .. ..511: pte 0x0000000020001c0b pa 0x0000000080007000
Detect which pages have been accessed
这个实验要求实现一个系统调用 sys_pgaccess,该系统调用可以返回页表是否被访问过的信息。
当一个页表被访问时,其 accessed 位会被设置,所有判断 accessed 位就可以确定一个 page 是否被访问过。
这个系统调用的使用方法如下:
int pgaccess(void *base, int len, void *mask);
base 为起始虚拟地址,len 为 page 的数量,从 base 开始的 len 个配置的访问情况会被存储在 mask 中。系统调用实现如下:
int
sys_pgaccess(void)
{
uint64 base;
int len;
uint64 maskaddr;
argaddr(0, &base);
argint(1, &len);
argaddr(2, &maskaddr);
// 这里为了方便,限制每次最多获取 64 个页的访问情况
if (len > 64) {
return -1;
}
uint64 mask = 0;
struct proc *p = myproc();
for (int i = 0; i < len; i++) {
pte_t *pte = walk(p->pagetable, base + i * PGSIZE, 0);
if (pte != 0 && (*pte & PTE_A)) {
mask = mask | (1 << i);
*pte = *pte & ~PTE_A;
}
}
// 将结果拷贝到用户地址空间
if (copyout(p->pagetable, maskaddr, (char*)&mask, sizeof(mask)) < 0) {
return -1;
}
return 0;
}