本关的任务为“Detect which pages have been accessed”,需要实现一个新的系统调用pgaccess
,它指出访问了哪些页面被访问了(读、写等)。系统调用需要三个参数:
- 第一个用户页面的起始虚拟地址
- 需要检查页数
- 一个存储每一页是否被访问的掩码
该lab的所有代码:Github
测试该系统调用的函数位于user/pgtbltest.c:pgaccess_test()
中:
void
pgaccess_test()
{
char *buf;
unsigned int abits;
printf("pgaccess_test starting\n");
testname = "pgaccess_test";
buf = malloc(32 * PGSIZE);
if (pgaccess(buf, 32, &abits) < 0)
err("pgaccess failed");
buf[PGSIZE * 1] += 1;
buf[PGSIZE * 2] += 1;
buf[PGSIZE * 30] += 1;
if (pgaccess(buf, 32, &abits) < 0)
err("pgaccess failed");
if (abits != ((1 << 1) | (1 << 2) | (1 << 30)))
err("incorrect access bits set");
free(buf);
printf("pgaccess_test: OK\n");
}
分析下源码可以知道,测试程序分配了32个页,并且使用(or 访问)了这分配的32个页的第1、2、30页,之后程序调用pgaccess
来检测abits的第1、2、30位是否为1,即判断该系统调用是否实现了“检测已经访问的页”这个功能。
向内核添加系统调用的方法在lab2中已经了解过了,不过这里xv6已经帮我们添加好了,我们只需要实现系统调用kernel/sysproc.c:sys_pgaccess()
即可。
在xv6 book中可以知道一个PTE的每位构成如上图,其中0 - 9
位是一些标志位,第6位为Accessed
,也就是访问位,需要在内核中添加这个标志:
// kernel/riscv.h
#define PTE_A (1L << 6)
而risc-v处理器会利用硬件将已访问的页的PTE_A
正确设置。
实现sys_pgaccess
的步骤大致可分为:
- 接受用户态传递的三个参数:
- 第一个用户页面的起始虚拟地址(指针)
- 需要检查页数(int)
- 一个存储每一页是否被访问的掩码(指针)
- 遍历“需要检查页数”,并每次检查遍历的页是否已访问。获取每个页对应的PTE将使用
walk
函数来获取,而检查将使用PTE_A
来判断;如果当前页的PTE_A
为1,则说明该页被访问过,利用位运算(是的,位运算在该lab里立大功)来存储信息,即第几页被访问了,并且不要忘记了实验指导中的提示,“Be sure to clear PTE_A after checking if it is set. Otherwise, it won’t be possible to determine if the page was accessed since the last time pgaccess() was called”,检测完后应该将PTE_A
标记位清除。 - 将存储的信息利用
copyout
函数从内核态传递给用户态,届时用户态可通过第三个参数获取。
具体实现如下:
int
sys_pgaccess(void)
{
// lab pgtbl: your code here.
uint64 uvm_pgaddr;
int page_counts;
uint64 abits;
argaddr(0, &uvm_pgaddr);
argint(1, &page_counts);
argaddr(2, &abits);
int result = 0;
pagetable_t page_table = myproc()->pagetable;
for (int i = 0; i < page_counts; i++) {
pte_t *pte = walk(page_table, uvm_pgaddr, 0);
if (((*pte) & (PTE_A)) != 0) {
result |= (1 << i);
*pte &= (~PTE_A);
}
uvm_pgaddr += PGSIZE;
}
copyout(page_table, abits, (char*)&result, sizeof(result));
return 0;
}