ref:CSE,SE,SJTU
虚拟化
我们需要在一个物理机上运行多个不同的OS,从而实现软件上的
- 隔离 - 如果OS崩了,那不会影响其他的OS
- 可维护性 - 易部署克隆移植备份(容器化)
- 安全 - 虚拟机自省
因此,我们在传统的OS的位置放置一个支持虚拟化的系统,称为VMM(VM Monitor) or Host. 每一个其上运行的虚拟机成为一个Guest。(当然也可以在传统OS和VM之间加入一层VMM,但是这样性能会比较差)
VMM具有高优先级,管理多个硬件。但是和传统OS具有不同的抽象,仅负责调度VM。
为了实现虚拟化,需要在三个层面进行隔离
- CPU - 每个虚拟机各自拥有自己的user/kernel mode
- Memory - 每个虚拟机各自拥有自己的虚拟MMU
- IO - 每个虚拟机各自拥有自己的虚拟设备
CPU 虚拟化
尝试I - VM as 进程
对于每个进程而言,CPU似乎都只被自己占有。如果我们直接把VM作为传统的进程,结果会如何?
宿主机作为一般的应用
问题在于,如果按照这样的方式,VM处于user mode,某些特殊的指令(如关中断,change CR3)就无法执行。VM是无法执行高优先级指令的。
尝试II - Trap & Emulation
解决方式显而易见,如果VM无法执行,那么只需要转交给VMM执行即可。
- Trap - 调用高优先级指令时,由于处于user mode,因此trap到宿主机
- Emulate - 实现对应的函数,将VM的系统状态存储到VMM的内存,VM并没有操作硬件的CR3,只是修改了宿主机的内存而已。
问题在于,Intel的指令集一开始并不天然支持虚拟化。
如popf,将pop出的内容存到flag寄存器里。对于IF(中断)而言,如果处于kernel mode,那么popf会更新IF,如果处于user mode,popf只会被忽略掉(而不是发生trap)。然而,我们无法区分同样是在user mode运行情况下,VM究竟是在
这类指令共有17条。
SGDT, SIDT, SLDT, SMSW, PUSHF, POPF, LAR, LSL, VERR, VERW, POP, PUSH, CALL, JMP, INT n, RET, STR, MOV
尝试III - 处理特殊指令
1.指令解释器:软件模拟
软件写个CPU模拟器,容易实现,但是性能非常低。(因为所有指令都是在对内存操作)
2.二进制翻译: 翻译为其他指令
每个basic block进行翻译,将这些指令翻译成VMM提供的函数调用,并存在code cache中。
Inline Call
然而问题依然存在,
首先是某些语言如Java会修改源代码(SMC),因此需要一个机制通知cache已经过期,否则执行的就是错误的代码;
其次,这就导致了每次Guest转交整个basic block,如果发生PC同步的中断,只会在边界处发生,而真实机器则可能在每个指令发生。
总结一下,虚拟机的kernel执行这些代码时,发生二进制翻译,而应用程序执行这些代码时则进行Trap&Emulate
3.半虚拟化: 直接在源代码中替换
这要求虚拟机中的程序清楚地意识到自己处于虚拟化运行
修改OS的代码,使得其中的指令直接变成对VMM的函数调用(hypercall)(eg. Xen)
4.硬件支持虚拟化: 改变 CPU(主流)
CPU分为两种模式,Root和非Root,每个模式都从ring-0至ring-3有四个优先级。由CPU本身存储虚拟化所需求的bit。VMM负责处理Exit
内存虚拟化
在虚拟化设计中,存在着三种地址,GVA, GPA,HPA。(G-guest,H-host)
然而对于虚拟机而言,他并不知道哪些HPA是属于自己的。如果设置VM的CR3直接指向GPT的话,很有可能使得VM访问越界。
尝试I-Shadow Page
我们并不让CR3指向属于VM管理的GPT,而是属于VMM管理的SPT(注意,软件模拟虚拟化的时候VM的虚拟CR3也在VMM上)。相当于两表合一,通过SPT直接寻址到宿主机的物理地址
代码语言:javascript复制set_cr3 (guest_page_table):
for GVA in 0 to 220
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 = 0
CR3 = PHYSICAL_ADDR(shadow_page_table)
那么,如何同步GPT和SPT呢?答案很简单,就是将GPT设置为只读,如果要写的话,就会触发Page Fault,然后由VMM处理并且同步SPT。
如果Guest上的应用通过SPT访问的话,同时也能访问到Guest的kernel space。所以需要对两种模式分别创建SPT,其中user态缺失了kernel space。
对于每个VM,需要一张HPT,对于每个VM上的进程,又需要一张GPT,并且两张SPT。
尝试II-直接映射
修改guest OS的源代码,让GVA直接映射到HPA。对于页表的修改只能通过hypercall进行,而对于guest OS只读。
但是这样做对guest OS不那么透明,并且guest OS会了解很多HPA相关的信息,可能会导致rowhammer攻击(见下节)
尝试III-硬件支持虚拟化
Intel EPT(extended)和AMD NPT(nested)为每个VM单独创立了一个表,用于将GPA映射到HPA上,由VMM管理。
IO 虚拟化
for OS lecture
尝试I -Device emulation
尝试II-Para-virtualization driver, e.g., virtio
尝试III- Hardware support, e.g., SR-IOV