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限制
实现
结果
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net