Lab: page tables
实验准备
切换到pgtbl分支
ruby
$ git fetch
$ git checkout pgtbl
$ make clean
Speed up system calls
需求
一些操作系统(例如Linux)通过在用户空间和内核之间共享只读区域中的数据来加速某些系统调用。这消除了在执行这些系统调用时跨越内核的需要。为了帮助您了解如何将映射插入页表,您的第一个任务是为xv6中的getpid()系统调用实现这种优化。
在创建每个进程时,在USYSCALL(在kernel/memlayout.h中定义的虚拟地址)上映射一个只读页面。在该页的开头,存储一个结构体usycall(也在memlayout.h中定义),并将其初始化以存储当前进程的PID。对于这个实验,ugetpid()已经在用户空间端提供,并将自动使用usycall映射。如果在运行user/pgtbltest时ugetpid测试用例通过,您将获得这部分实验的全部学分。
The solution
首先在进程结构体中添加一个struct usyscall*
变量,修改kernel/proc.h 目的是方便后续物理内存的分配与回收
c
struct proc {
...
struct usyscall* t;
};
首先我们要分配一页物理内存,在kernel/proc.c的allocproc函数中,模仿trapframe page的分配方式。 同时将进程pid写入该内存中。
c
static struct proc*
allocproc(void)
{
...
if((p->t = (struct usyscall *)kalloc()) == 0){
freeproc(p);
release(&p->lock);
return 0;
}
p->t->pid = p->pid;
...
}
然后将虚拟地址USYSCALL通过页表映射到该物理页,在kernel/proc.c的proc_pagetable函数中,模仿trapframe page的映射方式。 需要注意的是这里不能只设置PTE_R位,因为用户需要访问该物理页,所以还需要设置PTE_U位。
c
pagetable_t
proc_pagetable(struct proc *p)
{
...
if(mappages(pagetable, USYSCALL, PGSIZE,
(uint64)(p->t), PTE_R | PTE_U) < 0){
uvmunmap(pagetable, USYSCALL, 1, 0);
uvmfree(pagetable, 0);
return 0;
}
...
}
然后是回收该物理页,在kernel/proc.c的freeproc函数中,模仿trapframe page的回收方式。
c
static void
freeproc(struct proc *p)
{
if(p->t)
kfree((void*)p->t);
p -> t = 0;
...
}
最后是取消掉USYSCALL的映射,在在kernel/proc.c的proc_freepagetable函数中。
c
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
...
uvmunmap(pagetable, USYSCALL, 1, 0);
...
}
Print a page table
需求
为了帮助您可视化RISC-V页表,也可能是为了帮助将来的调试,您的第二个任务是编写一个打印页表内容的函数。
定义一个名为vmprint()的函数。它应该接受一个pagetable_t参数,并以下面描述的格式打印该页表。在exec.c返回参数argc之前插入if(p->pid==1) vmprint(p->pagetable),打印第一个进程的页表。如果你通过了make grade 的pte printout 测试,你将获得这部分实验的全部学分。
css
page table 0x0000000087f6b000
..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
第一行显示vmprint的参数。之后,每个PTE都有一行,包括引用树中更深的页表页面的PTE。每个PTE行都缩进了一个数字"..",表示它在树中的深度。每个PTE行显示其页表页中的PTE索引、PTE位和从PTE中提取的物理地址。不要打印无效的PTE。
The solution
递归遍历三层树页表,将函数定义在kernel/vm.c中,可借鉴freewalk函数。
c
void vmsprint(pagetable_t pagetable, int tag){
if(tag>2)return;//三级页表
for(int i = 0; i < 512; i++){//遍历所有PTE
pte_t pte = pagetable[i];
if(pte & PTE_V){//如果该PTE有效
for(int j = tag; j; j--)//以指定格式输出有关信息。
printf(".. ");
printf("..%d: pte %p pa %p\n",i,pte,(pagetable_t)PTE2PA(pte));
vmsprint((pagetable_t)PTE2PA(pte),tag+1);//获取下一级页表的物理地址,递归处理下一级页表
}
}
}
void vmprint(pagetable_t pagetable){
printf("page table %p\n",pagetable);//输出第一行
vmsprint(pagetable,0);
}
记得在defs.h中添加其函数原型void vmprint(pagetable_t);
Detect which pages have been accessed
需求
一些垃圾收集器(自动内存管理的一种形式)可以从访问了哪些页面(读或写)的信息中获益。在本部分的实验中,您将向xv6添加一个新特性,该特性通过检查RISC-V页表中的access bits来检测并向用户空间报告这些信息。RISC-V硬件页读取器在解析TLB缺失时在PTE中标记这些位。
您的任务是实现pgaccess(),这是一个报告访问了哪些page的系统调用。这个系统调用有三个参数。
- 要检查的第一个user page的虚拟地址。
- 需要检查的页数。
- 一个缓冲区的首地址,将结果存储到位掩码(一种每页使用一位的数据结构,其中第一页对应于最低有效位)中。
如果pgaccess测试用例在运行pgtbltest时通过,您将获得这部分实验的全部学分。
The solution
预备知识
- TLB(Translation Lookaside Buffer)是一个高速缓存,用于加速虚拟地址到物理地址的转换过程。
-
- CPU需要进行虚拟地址到物理地址的转换时,它会先查询TLB。如果TLB命中,即在TLB中找到了虚拟地址到物理地址的映射信息,那么CPU可以直接获取映射到的物理地址,快速地进行访存操作。
- 在TLB访问过程中,如果发生了TLB缺失(TLB miss),意味着在TLB中没有找到所需的虚拟地址到物理地址的映射信息。此时,CPU需要从页表获取映射信息。
- 在这个过程中,访问位(access bits)发挥着重要的作用。当CPU访问页表以获取映射信息时,它可以同时更新相应页表项中的访问位。访问位被设置为已访问(或未访问),用来跟踪页面的访问情况。
- 通过访问位的更新,CPU可以了解到哪些页面是最近被访问过的,以便进行页面置换策略的优化。例如:可以根据访问位来决定哪些页面应该保留在TLB中,以提高后续访问的命中率。同时,访问位的更新也可以用于支持其他性能优化,如页面预取和缓存控制等。
- 在xv6的三级页表结构中,访问位被设置在最下级页表表项中。
- user page的大小与物理页是一样的,都是4096个字节,故相差4096字节的数据肯定在不同user page中。
- 根据页表映射机制可知,在同一user page中的虚拟地址,获取到的PTE是一样的。
- 检查PTE_A设置后,需清除PTE_A。否则,将无法确定自上次调用pgaccess()以来是否访问了该页(即,该位将永远设置)。
code
思路在注释中
c
int sys_pgaccess(void)
{
uint64 st;
argaddr(0,&st);//获取user page虚拟地址
char *buf = (char*)st;
int len = 0;
argint(1,&len);//获取要check的页数
if(len > 32)len = 32;//限制check最大页数
unsigned int abits = 0;//存储位掩码
for(int i = 0; i < len; i++){
pte_t *pte = walk(myproc()->pagetable, (uint64)buf+PGSIZE * i, 0);//获取最下级页表表项
// (+PGSIZE*i)起到换页的效果
if(*pte & PTE_A){
*pte &= ~PTE_A;//清除PTE_A
abits |= (1 << i);//获取位掩码
}
}
argaddr(2,&st);//获取缓冲区的地址
if(copyout(myproc()->pagetable, st, (char *)&abits, sizeof(abits)) < 0)//将位掩码拷贝到用户缓冲区
return -1;
return 0;
}