我是cloud3,前段时间有虚拟机出现内存问题,今天借着这个话题给大家介绍一下内存虚拟化,也就是MMU虚拟化。
我们知道从intel的80386引入了保护模式后,内存空间分为虚拟地址空间和物理地址空间。后引入页表机制,把虚拟地址送往MMU,MMU查TLB不中的情况下,依次查页表就可以找到对应的物理地址。
(关于MMU的原理可以先看我的文章-图解MMU)
在引入虚拟化技术后,内存地址空间就变得复杂了,客户机(Guest)和宿主机(Host)都有自己的地址空间。GuestOS本身有虚拟地址和物理地址。HostOS也有虚拟机地址和物理地址。那虚拟机如何访问到物理机上的物理地址呢?这就是今天我们要讨论的内存虚拟化技术。
首先标记几个概念:
- HPA:Host Physical Address
- HVA:Host Virtual Address
- GPA:Guest Physical Address
- GVA:Guest Virtual Address
- PDBR:页目录表物理基地址寄存器,X86上叫CR3
- EPT:扩展页表
ptr:这里用来描述指向某个页表的寄存器
一.内存虚拟化要解决的问题
内存虚拟化实际实现就是MMU虚拟化,要实现GVA -> GPA -> HVA -> HPA,而传统MMU只能实现VA->PA的转换。所以在虚拟化场景下要解决虚拟机里面的进程如何访问物理机上的内存这一问题,也就是GVA->HPA的映射问题。
在硬件辅助内存虚拟化出现之前,这个过程是通过软件实现的,即通过VMM来实现的。最典型的实现方式就是影子页表技术。
二.影子页表
(Shadow page table)
影子页表我用一句话来描述就是:VMM把Guest和Host中的页表合并成一个页表,称为影子页表,来实现GVA->HPA映射。
变为:
影子页表需要实现 GVA -> HPA的转换。如何实现呢?有下面几步:
1,GVA->GPA,VMM层的软件会将guest Page Table本身使用的物理页面设为write protected的,Guest在进行GVA->GPA 时,由于是只读的,导致 VM exit, traps to VMM。(关于VM exit的过程我们在CPU虚拟化时再详解)。
2, GPA -> HVA,这一过程由VMM软件实现的,这个很容易理解,就是通用的malloc。
3, HVA->HPA,这一过程就是我们已知的使用物理MMU完成VMM进程的虚拟内存到物理内存的转换。
4, 把GVA -> HPA,这一路的映射关系记录到页表中,这个页表就是影子页表。
虚拟机页表和影子页表通过一个哈希表建立关联(当然也有其他的关联方式),客户机操作系统把当前进程的页表基址载入PDBR时而VMM将会截获这一特权指令,将进程的影子页表基址载入客户机PDBR,使客户机在恢复运行时PDBR实际指向的是进程对应的影子页表。这样通过影子页表就可以实现真正的内存访问。
影子页表实现非常复杂,需要为每个Guest中的每个进程的Guest PT都维护一个对应的Shadow PT。page fault和vm-exit的数量,也加重了CPU的负担。为了提高效率,各个CPU厂家推出了硬件辅助MMU虚拟化的技术。
三.扩展页表技术/EPT
嵌套页表技术/NPT
从Intel的Nehalem架构开始,EPT(Extended Page Tables)就作为CPU的一个特性加入到CPU硬件中去了。AMD也提供的类似技术叫做NPT,即Nested Page Tables。
硬件层面引入EPTP寄存器,来指向EPT页表基地址。Guest运行时,Guest页表被载入PDBR,而 EPT 页表被载入专门的EPT 页表指针寄存器 EPTP。
GVA->GPA的转换依然是通过查找原来页表完成,而GPA->HPA的转换则通过查找EPT来实现,每个guest VM有一个由VMM维护的EPT。
具体过程
当Guest中进程访问GVA时,CPU首先就要通过PDBR寄存器去找页目录,但是PDBR中存储的地址是GPA,所以要到EPT中进行GPA->HPA的转换,这个转换过程和物理MMU的工作流程相同。
找到了页目录的HPA基地址,再通过GVA中的Directory offset段,就找到页表的VGA了,这个页表VGA再去EPT中进行GPA->HPA的转换,就找到页表VGA的HPA了。
重复上述过程,依次查找各级页表,最终获得该GVA对应的HPA。如果是三级或者四级页表,也是同样的原理。
page fault处理
上面的查表过程是最理想的处处命中情况,那如果有page fault的情况如何处理呢?
如果Guest的页表中没有命中可直接由guest OS处理,不会产生vm-exit。如果在EPT中没有命中,则产生EPT violation异常,这是Host中VMM层的page fault,不需要vm exit,只需要按照Host中的page fault处理就可以了。所以说EPT/NPT MMU解耦了GVA->GPA转换和GPA->HPA转换之间的依赖关系。并且一个VM只需要一套EPT页表,减少了内存开销,维护也比较简单。
四.看图总结
最后我们直观的看看引入虚拟化之后MMU的变化情况:
没有虚拟化:
影子页表:
EPT/NPT:
通过上面的对比图,我们应该能清楚的看到MMU虚拟化的整个设计思路。