影子页表(Shadow page table) #
运行在实模式的 Guest 只需要一个页表就可以完成 GPA 到 HPA 的映射。但在保护模式下,我们知道每个进程都有自己的页表,维护着 GVA 到 GPA 的映射。所以保护模式下的内存转换方式要更加复杂。
在保护模式下的进程,当 Guest 准备访存时,cr3 寄存器此时存放的是 Guest 的页表。如果将这个页表交给 MMU 去查询,得到的将是 GPA 的地址,而不是真正的 HPA 地址。这是因为从 GVA 到 HPA 之间存在三层映射关系,即:
- GVA 到 GPA 的映射;
- GPA 到 HVA 的映射;
- HVA 到 HPA 的映射。
但 MMU 却只有一个。因此,要解决这个问题,我们需要将 cr3 寄存器中指向的 Guest 的页表,替换成为一张从 GVA 到 HPA 映射的页表。当 Guest 再进行访存时,则可以通过这个页表完成完成从 GVA 到 HPA 的转换过程。因为在这个过程中,新建的这张页表实际上会把 Guest 本身的页表给遮挡起来,所以我们称这个页表为影子页表 (Shadow page table)。
我们说过,保护模式下每个进程都需要有自己的页表,同样的,VMM 也需要为 Guest 的每个进程维护一个影子页表。在 Guest 的进程切换过程,要更新 cr3 寄存器指向的页表地址,VMM 要把这个操作拦截下来,将 Guest 页表换成影子页表。影子页表的示意图如下所示:
可以看到,影子页表将原来的三级映射压缩成了一级映射,CR3 寄存器里只要存储影子页表的地址就可以了。这样 MMU 就可以自动完成从 GVA 到 HPA 的转换。