内存虚拟化
三种地址
- 客户虚拟地址
Guest Virtual Address
- 客户物理地址
Guest Physical Address
- 主机物理地址
Host Physical Address
将GPA
和HPA
进行管理,是VMM
需要负责的工作。
内存虚拟化的方式
影子页表
set_cr3 (guest_page_table):
for GVA in 0 to pow(2, 20)
if guest_page_table[GVA] & PTE_P:
GPA = guest_page_table[GVA] >> 12
HPA = host_page_table[GPA] >> 12
shadow_page_table[GVA] = (HPA << 12) | PTE_P
else
shadow_page_table[GVA] = 0
CR3 = PHYSICAL_ADDR(shadow_page_table)
这里遍历了页表,将影子页表设置为了将GVA
翻译为HPA
的结构,这个过程需要遍历所有GVA
项,最后将影子页表基地址设置到CR3
中。
这样VMM
就可以拥有一个独立的影子页表,相应的,一旦guest OS
修改页表,影子页表也要做相应的更新。
为了达到这样的目的,这里使用一些手法,将这些页中的所有页都设置为只读,如果guest OS
修改了这些页,那么硬件就会触发缺页异常。这样VMM
就可以处理缺页异常,更新影子页表。如何将Guest OS
和Guest app
进行隔离?在影子页表的实现中,需要同时维护两个页表,当guest os
切换到U
,VMM
也要做切换影子页表的操作。
如果切换到U
,我们需要调用set_ptp(current, 0)
, 这样只有标注为User
,会被添加到影子页表中。
直接映射
这是一种半虚拟化的方式,我们需要直接修改guest OS
代码,只用GVA
和HPA
,Guest OS
接着操纵它的HPA
空间,然后使用hypercall
将自己要修改的信息告诉VMM
, VMM
来更新页表,CR3
指向这个GUEST OS
的页表。
硬件虚拟化对于内存翻译的支持
创建一个新的页表将GPA
翻译为HPA
,这个表由VMM
直接控制,每个VM
一个表,Intel
称之为EPT
,ARM
上叫Stage-2 Page Table
。这样在翻译完成GVA->GPA
后会自动进一步的翻译。
由于本身一次非虚拟化页表的页表访问需要4次内存访问,但是在虚拟化场景下,这里的地址都变为了GPA
了,需要进行进一步的翻译。不同的是,每一级的都要将GPA
翻译为HPA
,总共需要24次访存。
这里TLB
可以缓存GVA->HPA
,大大加速了翻译过程。同时一阶段页表缺失不会引起VM Exit
.