1.1 虚拟化
什么是虚拟化?按一般的感觉,虚拟化就是在一台物理机器上,可同时运行多个操作系统,而这些操作系统彼此之间不能感知。本质上说,虚拟化是通过空间上的分割,时间上的分时和模拟,将物理机上面的一份资源抽象成多份。虚拟机(Virtual Machine)是由虚拟化层提供的独立的虚拟计算机系统,每个虚拟机都拥有自己的虚拟硬件(CPU,内存,IO设备)。通过虚拟化层的模拟,虚拟机在上层软件看来,就是一个真实的物理设备。这个虚拟化层一般称为虚拟机监控器(virtual machine monitor)。
而学术界对虚拟化做了严格的定义。Popek在1974的论文提出了VM的三个特征:
q 等价:即应用程序在VMM之上的VM运行,除了时间因素外,其余都和在物理硬件上的执行行为相同。
q 资源控制:VMM对物理资源具有完全控制,而VM中的程序(包括操作系统)不得直接访问设备。
q 高性能:在虚拟环境中执行的应用程序,绝大多数指令要能够直接在物理机器上执行,只有少量指令需要VMM的模拟。这一条就过滤了模拟器(boches),是不能称为VM的。
1.1.1 CPU虚拟化
VMM对机器硬件资源的虚拟化过程中,CPU虚拟化是最为重要的一个环节。只有保证虚拟机的指令能够被正确地虚拟执行,各个虚拟机之间不互相影响,即指令的执行结果不改变其它虚拟化的状态,才能够保证整个虚拟化环境的正确和有效。CPU虚拟化是为每个虚拟化提供一个或者多个虚拟CPU(virtual CPU)。多个VCPU分时复用物理CPU。VMM必须为多个VCPU合理分配时间片并维护所有VCPU的状态,当一个VCPU的时间片用完需要切换时,要保存当前VCPU的状态,将被调度的VCPU的状态载入物理CPU。所以CPU虚拟化需要解决两个问题,一是VCPU的正确运行,二是VCPU的调度。
那么如何实现VCPU的正确运行?
简而言之,处理器呈现给软件的接口就是一堆指令(指令集)和一堆寄存器(包括用于通用运算的寄存器和用于控制处理器行为的状态和控制寄存器)。而IO设备呈现软件的接口也是一堆的状态和控制寄存器。这些都是系统的资源,其中影响处理器和设备状态和行为的寄存器称为关键资源或者特权资源。如x86处理器的CR0~CR4寄存器。可以读写系统关键资源的指令叫做敏感指令,如x86的lgdt/sgdt/lidt/sidt/in/out等等。
现代的大多数CPU都划分为两种或者多种状态。某些指令只能在最高级别的状态下才能正确执行,在其它级别执行是禁止的。操作系统内核的代码通常运行在最高级别的状态下。根据运行级别的不同,CPU指令可以分为两类:
q 特权指令:只能在最高级别状态下执行。在低级别状态执行后产生Trap。
q 非特权指令:可以在各个级别的状态下执行。
CPU虚拟化通常采用的是“特权解除”和陷入-模拟(trap-and-emulation)”技术。特权解除是指为了实现VMM对虚拟机的控制,降低Guest OS运行的特权级别,而将VMM运行在最高特权级的技术。Guest OS的大部分指令仍可以在硬件上直接运行,只有当Guest OS执行到特权指令时,才会陷入到最高特权级的VMM模拟执行。这样就实现了陷入-模拟执行。对照前面论文提出的VM三个条件,“特权解除”和“陷入-模拟”实现了VMM对资源的完全控制和高性能要求。
“陷入-模拟”要求敏感指令都是特权指令,都不能在低级别状态执行,可惜X86体系不能满足这个要求,在X86指令集中有十几条敏感指令不是特权指令,这就造成X86虚拟化的复杂性。比如,x86指令sgdt/sidt/sldt可以在用户态读取特权寄存器GDTR/IDTR/LDTR的值,smsw可以读取当前机器的状态,还有call ,jmp,int n,pop,push等指令,都会影响计算机系统的虚拟化。
1)x86的全虚拟化
VMware代表的全虚拟化要在运行时检测敏感指令,捕捉到敏感指令后模拟敏感指令的执行。
2)X86的半虚拟化
以 xen为代表。基本思想是修改Guest OS的代码,把敏感指令的操作,替换为对VMM的超级调用(hypercall)。
3)硬件辅助的虚拟化
为解决X86虚拟化的困难,intel推出了硬件辅助虚拟化技术。基本思想是引入新的处理器运行模式和新的指令。Guest OS运行在受控模式,原来的一些敏感指令可以直接在虚拟机环境执行。
同时VT技术的新指令可以用硬件完成“陷入-模拟”模式切换时上下文的保存和恢复,这样大大提高了“陷入-模拟”上下文切换的效率(此时已经不是原来意义的陷入-模拟,这里借用了陷入-模拟的概念)。
1.1.2 内存虚拟化
VMM掌控了所有的系统资源。内存自然也在VMM的管理和控制之下。但是Guest OS本身也有内存的管理机制,所以在虚拟化的场景下,就比通常系统的多了一层映射。通常的计算机系统,经过MMU转换后的地址就是真实的物理地址,而在虚拟化的情况下,这个地址还需要经过VMM的一次映射,才能得到真正的物理地址。列出虚拟化情况需要转换的地址:
GVA:虚拟地址,指VM里面进程所使用的内存地址。
GPA:物理地址。这个地址是伪物理地址,是经过MMU映射的,虚拟机上面看到的物理地址。
HVA:宿主机的虚拟地址。
HPA:宿主机物理地址。是真实的物理地址。
但在实际应用中,如果VM的每个地址访问都需要VMM的参与,都需要经过一次软件转换,代价是不可接受的。为了实现虚拟地址到机器地址的高效转换,常用的有两种转换方法:一种是半虚拟化采用的MMU虚拟化,另外一种是全虚拟化采用的影子页表。
1) MMU虚拟化
MMU半虚拟化,是指利用物理的MMU,在虚拟机里面一次完成从虚拟地址到机器地址的映射。为达到这个目的,VMM是需要保存一张转换表(从虚拟机物理地址到机器地址),经过转换表的处理,VM里面的页表中的地址不再是虚拟机的物理地址,而是真实的机器地址。
为实现MMU虚拟化,必须对Guest OS的内核做修改,在每个涉及到内存分配和页表操作的地方,都要改为VMM提供的超级调用。VMM通过超级调用,查找转换表,对页表项做修改,载入真实的机器地址。
对于VM来说,它可以知道真正的机器地址,因此为了保护各个VM之间的隔离,VMM在对页表进行地址转换前,会对页表中的每一个页表项检查,确保页表项只映射了属于该VM的机器页面,而且不能包含对页面的可写映射。
2) 影子页表
全虚拟化的情况下,Guest OS必须是透明的,也就是说不能修改Guest OS的系统内核。这样需要改Guest OS系统内核的MMU虚拟化就不能应用了,必须使用影子页表的技术。影子页表是为Guest OS的每个页表都维护一个影子页表,并将合成后的映射关系(从虚拟地址到机器地址)写入到影子页表,而Guest OS的页表内容则保持不变。然后,VMM将影子页表交给MMU进行地址转换。
在利用影子页表的情况下,Geust OS所见到的页表内容没任何变化,影子页表的分配和维护完全由VMM控制。但是,使用影子页表的开销是很大的,一方面是时间开销,一方面是空间开销。
首先是时间开销,Guest OS构造页表的时候不会通知VMM(对比MMU虚拟化是通过超级调用通知VMM),VMM必须要等到Guest OS发生缺页,通过分析缺页原因,再为其补全影子页表。此过程中VMM需要通过模拟MMU遍历Guest OS的页表,才能获得Guest OS维护的地址映射关系(虚拟地址到VM的物理地址),这种间接手段的效率比半虚拟化的效率低很多。
其次是空间开销。VMM需要支持多个VM同时运行。以linux而言,Guest OS里面每个用户进程
都有自己的页表,因此影子页表的空间开销会随着进程数量的增多而迅速增大。而且Guest OS的进程数量对于VMM来说是不可控的。
1.1.3 IO虚拟化
设备对软件来说,就是一堆的寄存器(io端口)和IO内存,以及中断和DMA。而设备虚拟化的过程,就是模拟设备的这些寄存器和内存,然后截获Guest OS里面对IO端口和寄存器的访问,然后通过软件的方式来模拟真实的硬件。
1)全虚拟化的IO虚拟化
在全虚拟化,因为不修改Guest OS的内核,Guest OS保存了IO设备的原生驱动。但是VMM处理设备的方式会根据VMM位置的不同而有所不同。例如,全虚拟化最有代表性的VMware ESX和VMWare Workstattion,由于VMM实现模式不同,采用的设备虚拟化方式也不同。
在VMware ESX中,VMM直接运行在物理硬件之上,直接操作硬件设备,而Guest OS看到的则是一组统一的虚拟IO设备。Guest OS对这些虚拟设备的每一个IO操作都会陷入VMM 中,由VMM对IO指令进行解析并映射到实际的物理设备,然后直接控制硬件完成。
而VMWare WorkStation采用了不同的方式。VMM实际上运行在一个传统的操作系统之上,这类VMM无法获得对硬件资源的完全控制,因此采用软件模拟的方式来模拟IO设备。Guest OS的IO操作会被VMM捕获,并转发给宿主机(host OS)的一个用户态进程,该进程通过对宿主机操作系统的系统调用来模拟设备的行为。
模拟IO虚拟化方式的最大开销在于处理器模式的切换:包括从Guest OS到VMM的切换,以及从内核态的VMM到用户态的IO模拟进程之间的切换。
2)半虚拟化的IO虚拟化
在半虚拟化的情况下,修改Guest OS的内核,将原生的设备驱动从Guest OS移出,放到一个特殊的设备虚拟机中(对xen来说,就是Dom0了),其余虚拟机中的IO请求都由设备虚拟机处理。而在Guest OS内部,为每个虚拟设备安装一个特殊的驱动程序,由该驱动程序负责IO请求的传递,设备虚拟机经过VMM授权,解析收到的请求并映射到实际物理设备,最后交给设备的原生驱动来完成IO。实际上在这种情况下,Guest OS的驱动是消息代理的作用,把io事件转换为消息,发送给设备虚拟机处理。