影子页表的实现

影子页表的实现

影子页表的实现 #

我们接下来看下在 KVM 里边影子页表是如何实现的。从影子页表的机制中我们可以看出,实现影子页表的过程中有两个关键点:

  1. cr3 寄存器的切换;
  2. 影子页表的构建。

在第一点中,由于进程切换的时候都需要进行页表的切换,也就是对 cr3 寄存器的修改。因此,当 Guest 在进程切换准备把 Guest 的页表写入 cr3 寄存器时,需要 VMM 介入进来,记录下此时要写入的 Guest 的页表,同时把 GVA 到 HPA 映射的影子页表写入到 cr3 中,完成一次偷梁换柱。

在第二点中,影子页表的构建,主要是通过影子页表的缺页异常处理函数来完成的,它主要的流程是:当 Guest 执行访存指令,来进行访存的时候,会将 GVA 发送给 MMU 进行查找。由于此时 cr3 存放的是影子页表,因此 MMU 会通过影子页表来查找 GVA 对应的 HPA。如果找到了,就可以直接从 HPA 中读取对应的数据,然后流程结束。

如果此时影子页表中还没有 GVA 到 HPA 的映射,就会触发 VM Exit,并从 Guest 模式退出到 Host 模式,由影子页表的缺页处理函数进行处理。影子页表的缺页处理函数会通过上文保存的 Guest 的页表,来查找 GVA 对应的 GPA。

如果 Guest 的页表中,GVA 到 GPA 的映射还不存在,就会由 VMM 向 Guest 注入缺页异常,并交由 Guest 的缺页异常处理函数,完成 GVA 到 GPA 的映射过程。完成映射后,Guest 会继续进行访存,由于此时影子页表中 GVA 到 HPA 的映射还未完成,CPU 此时会继续进入影子页表的缺页异常处理函数中。

当 GVA 与 GPA 的映射已存在时,就只需要根据 VMM 所维护的映射关系计算出 HVA。然后可以借助 Host 的内存管理机制,来分配空闲的物理页面,并且完成 GVA 到 HPA 的映射(与实模式相同,这一步也是由 VMM 主动调用 get_user_pages 完成的)。最后将映射关系填充到影子页表中。这就完成了影子页表的构建。

影子页表机制实际是一种纯软件实现的机制,我们可以看出,影子页表是通过软件方式实现了 MMU 的能力。而且在影子页表的使用过程中,会多次发生 VM Entry 和 VM Exit,你可以想象的到,影子页表机制的效率非常慢。为了解决这个问题,硬件厂商通过新增一个页表转换单元来提升性能,这就是扩展页表 (Extended Page Table, EPT)。

Viewpoint #

From #

12 | 内存虚拟化:云原生时代的奠基者