Skip to content

Lab: page tables

Speed up system calls(easy)

实验思路

  1. 首先是看 kernel/memlayout.h ,能看到里面有一个 USYSCALL 的宏,还有一个 struct usyscall 的结构体,看题能知道,这题就是希望使用这个 usyscall 让用户态的程序获取 pid 的时候不切换内核态
  2. 根据提示,找到 kernel/proc.cproc_pagetable() 函数,在这里可以实现映射,根据上一条思路,和这里的这个函数一起观看会发现,这个函数还实现了了另外两个宏的映射,因此,模仿着它的映射,就可以了。但是会发现 proc 结构体里没有宏对应的变量,因此需要手动添加一个
  3. 提示里还说要注意选择权限的比特位,由于不知道权限的比特位,需要看一下书,看完书就知道比特位在 kernel/riskv.h 中也有宏的定义,直接使用就好了
  4. 根据提示,在 allocproc() 函数中,需要初始化 usyscall ,同时也能看到这个函数里初始化另外一个宏,在它下面模仿着初始化就可以了
  5. 根据提示,和上一条思路一样修改方法,修改 freeproc()
  6. 这时候运行会出现 panic: freewalk: leaf 的错误,看到 free ,猜到也许是 freeproc() 的问题,仔细看修改过的代码,没发现什么问题,由于改动和页表相关,去看一下 proc_freepagetable() ,发现这里没有 ummap USYSCALL ,添加一条 ummap USYSCALL 就可以了

踩到的坑

  1. allocproc() 函数中,我把初始化 usyscall 写到了初始化页表后面,导致初始化页表出现了缺页异常,找了了十分久才发现

实验思路

  1. 定义一个 vmprint() 函数在 kernel/vm.c 中,由于是打印页表,因此参数就是一个页表
  2. 根据示例,直接使用 %p 打印第一行地址,也就是页表的第一条地址
  3. 页表的转换需要使用位运算,这个在 kernel/riscv.h 中有宏定义好了,页表的其他信息里面也有宏定义好了,直接调用即可
  4. 参考 freewalk() 函数,是释放页表,释放页表需要遍历,因此可以直接将整个函数复制过来修改,但是由于需要递归操作,所以新建一个_print_pgtbl() 函数,将 freewalk() 复制进去,新函数的参数是页表和层级,层级用于判断递归的层数
  5. 页表中有一个位是 PTE_V ,在 kernel/riscv.h 可以得知,这个位是判断页表是否有效的。对于无效的页表,直接跳过就好,对于有效的页表,直接按格式打印层级和地址
  6. 打印完之后需要判断该页表是不是叶子的页表,判断的方法通过课程或者 xv6 book 可以知道,当 PTE_R PTE_W PTE_X 都等于 0 的时候,页表有子页表,否则是叶子页表,如果不是叶子页表,直接递归同时层级加一就行了
  7. 总结一些递归思路,首先遍历 pagetable ,取出每一个 pte ,如果 pte 是有效的继续,无效则结束,然后遍历层级,打印 .. ,接着获取该 ptepa ,按照格式打印出来,最后判断该 pte 是不是叶子 pte ,如果不是就递归

Detecting which pages have been accessed (hard)

实验思路

  1. 首先要找到 kernel/sysproc.c 里面的 sys_pgaccess() ,然后使用 argaddr()argint() 接收参数,可以参考一下其他地方的使用,前面说了会有三个参数,如果感觉不明确,可以去 user/pgtbltest.c 里看一下。
  2. 根据提示,使用 copyout() 传出参数,可以使用 printf() ,测试一些。
  3. 根据提示,做边界测试。
  4. 使用 walk() 函数,这个函数的作用就是根据虚拟地址返回物理地址,如果没有找到,就会根据 alloc 判断是否新建 pagetable ,这里不需要。
  5. kernel/vm.c 下新建一个函数,否则调用不了, kernel/sysproc.c 中没有 walk() 函数的定义,新函数使用 walk() 获取 pte ,然后定义一个 PTE_A 的宏,用于得到 access 的标志位。当获取到标志位为 1 时,设置为 0 并返回 1 。
  6. 通过位运算得到结果,通过 copyout() 传递出去。