Page 68 - 《软件学报》2025年第9期
P. 68
李传东 等: RISC-V 架构下的懒惰影子页表模型 3979
量处理, 都会推迟到页表项使用时刻使用最新的进程页表项进行影子页表同步.
3.3 页表同步快速路径
在使用页表同步行为推迟后, 一次页表同步仍然会发生两次虚拟机退出, 第 1 次用于无效化影子页表项, 第 2
次则是发生页面错误后的页表同步. 虽然该方法可以批量化一些页表同步操作的发生, 但是却增加了一次虚拟机
退出的开销. 虽然在 RISC-V 架构下的虚拟机退出相较传统架构更加轻量级, 但是仍然需要在退出后保存虚拟机
上下文. 而懒惰影子页表中页表同步的首次虚拟机退出只需要进行轻量级的页表无效化操作, 上下文切换的开销
就成为了瓶颈.
因此, 懒惰影子页表模型针对页表项修改发生的第 1 次虚拟机退出设计了页表同步快速路径. 具体而言, 懒惰
影子页表模型在拦截 TLB 刷新操作后直接在最高特权级中处理影子页表项的无效化操作. 首先, 在 M mode 可以
直接使用物理地址访存而无需页表遍历和查询 TLB, 避免了 TLB 污染. 然后, 由于页表同步中首次虚拟机退出只
需要进行简单的页表无效化操作, 退出到 M mode 后仍然保留虚拟机上下文, 只保存少数需要直接使用的通用寄
存器即可完成处理. 由此, 页表同步操作主要分为: (1) 轻量级的快速路径对影子页表项进行无效化; (2) 虚拟机退
出对页表进行批量同步. 这极大地提升了传统影子页表中对页表同步的效率.
3.4 懒惰影子页表模型实现
在 RISC-V 基础架构下, 懒惰影子页表模型的实现只需要将传统影子页表模型的页表同步机制进行修改即可.
具体而言, 由于在该架构下虚拟机操作系统也运行在 U mode, TLB 刷新操作本身就会产生一次指令错误的 trap,
在其对应的处理函数中即可进行影子页表无效化的实现. 而在发生页面错误时, 虚拟机同样会直接退出到 hypervisor
中, 在页面错误处理程序中即可检查发生错误的 GVA 对应的进程页表项和影子页表项与第 2 级映射页表, 检查
是否发生了权限错误或者映射修改, 进行对应的同步即可. 由于在该配置下, 直接将指令错误的 trap 陷入到 M mode
会大范围影响其他类型指令错误的处理, 当前也无法精确地将 TLB 刷新操作陷入到 M mode, 我们并没有在该配
置下实现快速路径.
在具有 H-extension 的 RISC-V 架构下, 懒惰影子页表模型选择将 hypervisor 中的 hgatp 寄存器置零来禁用两
级页表遍历中对于嵌套页表的遍历过程, 同时在 vsatp 寄存器中直接装载从 GVA 映射到 HPA 的影子页表基地址.
那么, 当 TLB 缺失后, 硬件只会使用 vsatp 寄存器中的基地址进行一维的影子页表遍历. 同时, 为了保证 vsatp 寄存
器中的页表基地址对虚拟机不可见, 我们通过设置 hstatus 寄存器中的 VTVM bit 直接拦截虚拟机中任何对该寄存
器的读写而返回其请求的进程页表的基地址, 同时该位的设置也会拦截 TLB 刷新的操作, 这与我们的设计不谋而
合. 而对于懒惰影子页表快速路径的实现, 我们通过 medeleg 寄存器直接将虚拟指令错误导致的异常保留在 M
mode 进行处理, 由于只在 M mode 中进行简单的页表无效化操作, 所以快速路径直接由高性能汇编代码实现, 同
时保留虚拟机的上下文, 仅类似于进行一次函数调用.
在 RISC-V 架构下, 可以选择刷新指定的 TLB 条目或者全局刷新, 理想情况下, 对于页表项修改后, 客户机操
作系统应当仅刷新对应的表项而非全局刷新, 因此懒惰影子页表也能够获得该次刷新对应的页表项的 GVA. 而一
旦直接进行全局 TLB, 懒惰影子页表则认为是进行了一次页表的大范围调整, 因此不再进行同步行为延迟, 而是直
接陷入 hypervisor 在此时进行页表同步操作.
4 实验分析
4.1 测试环境
我们使用 QEMU 7.1.0 [30] 模拟器构建 RISC-V 硬件环境, 该版本模拟器已经提供 H-extension 支持. 实现了懒惰
影子页表模型的 hypervisor 直接运行在该模拟硬件环境下以验证有效性. Hypervisor 总共管理 16 GB 物理内存.
运行在 hypervisor 上的虚拟机配置为使用 8 GB 物理内存. 我们通过微基准测试在 RISC-V 基础架构上测试和对
比传统影子页表模型和懒惰影子页表模型; 选取 SPEC2006 测试集 [31] 中的典型应用: 403.gcc, 429.mcf, 433.milc,

