Detect which pages have been accessed (hard)
要求
为 xv6 实现一个新性能:通过查看 RISC-V 页表中的拜访位,检测并报告用户空间拜访了哪些页。
实现零碎调用
pgacess
,报告拜访过哪些页面。该零碎调用须要三个参数:(1)第一个用户须要检测的页面的起始虚拟地址;(2)须要检测的页面数量;(3)一个指向缓存的用户地址,用以贮存位掩码后果。
提醒:
- 在
user/pgtlbtest.c
中理解如何应用pgaccess
。 - 在
kernel/sysproc.c
中实现sys_pgacess
,可用argaddr()
或argint()
传递参数 - 对于位掩码,可保留在内核长期缓存区,在获取正确的位掩码之后通过
copyout()
拷贝给用户 - 可设置扫描页面数的下限
kernel/vm.c
中的walk()
有助于找到正确的 PTEs(页表项)- 在
kernel/riscv.h
定义拜访位PTE_A
,参考手册确定其值 - 在查看一个页面的
PTE_A
位是否被设置后,切记要复原它。否则无奈检测自上次 pgacess 之后用户是否再次拜访它 vmprint()
可能对 debug 有帮忙
实现
后果
A kernel page table per process (hard)
要求
为每个过程提供一个内核页表。
为了让每个过程在内核运行时可能应用本人拷贝的内核页表,咱们须要批改内核。批改
struct proc
为每个过程保护一个内核页表、批改调度程序好让它在切换时切换内核页表。对于此步骤,每个过程的内核页表该当与现有的全局页表雷同。
提醒:
- 为
struct proc
增加字段 - 正当为新过程产生一个内核页表的办法是批改
kvminit
而不是批改kernel_pagetable
。你须要在allocproc
调用此函数 - 确保每个过程的内核页表都有一个到内核堆栈的映射。在未修改的 xv6 中,内核堆栈在
procinit
实现设置。你须要将这部分性能的局部或全副移到allocproc
中。 - 批改
scheduler()
以将过程的内核页表加载到外围的satp
寄存器(参考kvminithart
)。切记在调用w_satp()
后调用sfence_vma()
scheduler()
在没有过程运行时该当应用kernel_pagetable
- 在
freeproc
中开释内核页表 - 你须要一个开释页表但不开释页物理内存页的办法
- vmprint 可能对 debug 有用
- 至多须要批改的中央:
kernel/vm.c, kernel/proc.c
; 不能动的中央:kernel/vimcopyin.c, kernel/stats.c/ user/usertests.c, user/stats.c
实现
后果
Simplify copyin/copyinstr (hard)
要求
内核中的 copyin
函数通过将用户指针转变为内核能间接解援用的物理地址,进而读取用户指针所指的内存。这种转变可通过在软件中遍历过程页表实现。你须要为每个过程的内核页表 (之前实现的) 增加用户映射,以容许 copyin
间接解援用用户指针。
将
kernel/vm.c
中的copyin
的函数体替换为对copyin_new
(在kernel/vmcopyin.c
中定义)的调用;copyinstr
和copyinstr_new
同理。为使copyin_new
和copyinstr_new
失常工作,须要增加用户地址到每个过程的内核页表之间的映射。
该计划依赖于用户虚拟地址范畴与内核虚拟地址范畴不重合。xv6 将从 0 开始的虚拟地址作为用户地址空间,而内核内存从更高的地址开始。然而,该计划必须限度用户过程的大小小于内核最低虚拟地址。在内核被疏导后,在 xv6 中那个地址是 0xC000000
,即 PLIC 寄存器的地址。参考 kernel/vm.c
中的 kvminit()
、kernel/memlayout.h
和文本中的图 3-4。你须要批改 xv6 来爱护用户过程不能大于 PLIC 地址。
提醒:
- 在移用到
copyinstr
之前,首先将copyin()
替换为对copyin_new
的调用并使其失效。 - 每当内核切换过程的用户映射时,也都该当以同样的形式切换过程的内核页表。(这些状况包含
fork() exec() sbrd()
) - 切记在
userinit
中将第一个过程的用户页表包含在内核页表中 - 在内核页表中的用户地址 PTE 须要哪些权限?(内核无法访问 PTE_U 的内存页)
- 切记 PLIC 限度