概述
对于客户机操作系统来说,存在两种常用的虚拟化方法,即full-virtualization(完全虚拟化)、para-virtualization(并行虚拟化)。由于本文讨论影子页表,因此只针对内存进行考虑,影子页表是完全虚拟化的做法,所谓完全虚拟化,是指客户机操作系统不感知自身处于虚拟环境,因此不需要修改客户机操作系统源码。对于虚拟化环境来说,存在四种地址,GVA(Guest virtual address,客户机虚拟地址)、GPA(Guest physical address,客户机物理地址)、HVA(Host virtual address,宿主机虚拟地址)、HPA(Host physical address,宿主机物理地址)。对于客户机应用程序来说,它想访问一个具体的物理地址需要两级页表转换,即GVA到GPA和GPA到HPA,本文暂不考虑宿主机应用程序,因此忽略HVA。
当硬件不提供支持内存虚拟化(例如EPT)的扩展时,硬件只有一个页表基址寄存器(例如x86下的CR3或者ARM的TTBR寄存器组),硬件无法感知此时是做GVA到GPA的转换还是GPA到HPA的转换,事实上,硬件只能完成一级页表转换,因此影子页表的本意是在VMM中创建一个客户机页表的影子页表,能够一步完成从GVA到HPA的转换,如下图所示,展示了这一过程。
影子页表的做法
首先,明确几个相关概念,以Linux 32位为例,寻址时需要32的地址号,我们知道这32位可以分为3个部分,最后12位是页偏移量,中间10位称为PT(Page Table,页表),最前面的10位称为PD(Page Descriptor,页目录)。在寻址时,首先根据前20位找到对应的物理页,在根据最后12位的偏移量找到具体内容,因此一般把前20位称为物理页帧号,引入影子页表后,就会引入三种物理页帧号,分别是GFN(Guest Frane Number,客户机页帧号),MFN(Machine Frane Number,机器物理页帧号)和SMFN(Shadow Machine Frane Number,影子宿主机物理页帧号),事实上,当我们得到一个GFN的时候,我们更希望得到这个GFN对应的SMFN,这就是影子页表性能很差的原因之一,需要做一个GFN到SMFN的转换,为了提升这部分性能,目前只要是通过hash的方式来做这个转换,在转换时还要根据影子页表的类型(主要是当前页表位于第几级,记为type),上述过程可以记为SMFN=hash(GFN,type),其实这里GFN和MFN是一样的,因此也可以记为SMFN=hash(MFN,type)。
客户机操作系统的页表不是静态的,在运行时需要修改客户机操作系统的页表,而客户机页表的修改需要同时修改VMM中对应的影子页表,因此客户机页表的修改需要被VMM截获,为了达到每次客户机页表的修改都可以通知VMM,将客户机页表设置为只读权限,因此每次修改时都会产生page fault,进入到VMM中,由VMM代替客户机操作系统修改其页表,然后更新自身的影子页表中GVA到HPA的映射关系。这就是影子页表性能很差的原因之二,因为每次修改的开销相比如非虚拟化环境有很多额外操作。