2.3 虚拟化类型
虚拟化分为全虚拟化、类虚拟化和硬件辅助虚拟化三类,本节我们将对不同的虚拟化类型进行详细描述。
2.3.1 全虚拟化
全虚拟化是虚拟化的一种理想状态,虚拟机监视器模拟出来的平台是一个完备的运行环境,从GuestOS看来和在真实的物理机上运行完全一致,GuestOS察觉不到是运行在一个虚拟化平台上。在这样的虚拟化平台上,GuestOS 无须做任何修改即可运行,所抽象的虚拟机具有完全的物理计算机特性,我们称这种虚拟化平台为“全虚拟化平台”。全虚拟化平台需要正确处理所有的敏感指令,进而正确完成对CPU、内存和I/O的各种操作。
在x86架构的CPU中,不是所有的敏感指令都是特权指令,因此并不能完全解决那些不是特权指令的敏感指令的虚拟化漏洞,例如全局描述符表(GDT)、局部描述符表(LDT)和中断描述符表(IDT)的一些操作指令,如SGDT、SLDT、SIDT。早期没有硬件支持,全虚拟化只能通过软件方式实现,即主要通过二进制代码翻译机制实现。
如图2-6所示,在全虚拟化中,对于特权指令,还是采用先前的“陷阱+模拟”的方式。当虚拟机的内核态需要运行虚拟化漏洞指令时,Ring 0下的虚拟机监视器通过二进制代码扫描并替换Ring 1下的GuestOS的二进制代码,将所有虚拟化漏洞指令替换为其他指令。二进制翻译是一种直接翻译可执行二进制程序的技术,能够把一种处理器上的二进制程序翻译到另一种处理器上执行。在虚拟机监视器中,会动态地把虚拟机中的虚拟化漏洞指令翻译为其他指令,从而实现虚拟化。
图 2-6
二进制代码翻译的优势在于,对GuestOS无须做任何修改;其劣势在于,由于实时的内存扫描,造成虚拟机的性能受到很大的损耗。
2.3.2 类虚拟化
2003年出现的Xen,使用了另外一种类虚拟化的方案来解决x86架构下CPU虚拟化漏洞的问题。不同于全虚拟化,类虚化的GuestOS知道自己运行在一个虚拟化环境中。类虚拟化可以通过修改GuestOS的内核代码来规避虚拟化漏洞的问题,GuestOS会将与敏感指令相关的操作都转换为对虚拟机监视器的超级调用(Hypercall),交由虚拟机监视器进行处理,使GuestOS内核完全避免处理那些难以虚拟化的虚拟化漏洞指令。而超级调用支持批处理和异步两种优化方式,这使得通过超级调用能得到近似于物理机的速度。类虚拟化的方法是一种全新的设计理念,它将问题的中心由虚拟机监视器移向GuestOS自身,通过主动的方式由GuestOS去处理这些指令,而不是移交给虚拟机监视器进行处理,在这种设计理念下就必须得修改GuestOS内核。
但与全虚拟化相同的是,类虚拟化修改过的GuestOS也会运行在Ring 1下。运行在Ring 1下的GuestOS没有权限执行的指令,会交给运行在Ring 0下的虚拟机监视器来处理,这在很大程度上与应用的系统调用类似:系统调用的作用是把应用无权限执行的指令交给操作系统完成。因此,虚拟机监视器向GuestOS提供了一套“系统调用”,以方便GuestOS调用,这套“系统调用”就是超级调用。只有Ring 1下的GuestOS才能向虚拟机监视器发送超级调用请求,以防止Ring 3下的应用调用错误导致对系统可能的破坏。因此,只有运行在特权级别Ring 1下的GuestOS 内核才能申请超级调用。例如,当虚拟机内核需要操作物理资源来分配内存时,虚拟机内核无法直接操作物理资源,而是通过调用与虚拟机监视器内存分配相关的超级调用,由超级调用来实现真正的物理资源操作。
如图2-7所示,类虚拟化让GuestOS知道自己是在虚拟机上运行的,工作在非Ring0状态。那么它原先在物理机上执行的一些特权指令,就会被修改成超级调用。这种方式是可以和虚拟机监视器约定好的,相当于通过修改代码把GuestOS移植到一种新的架构上,就像定制的一样。所以,类虚拟化技术的GuestOS都有一个专门的定制内核版本。这样就不需要有陷阱机制、二进制代码翻译、模拟的过程了,性能损耗非常低,这也是半虚拟化的优势。通过修改GuestOS和超级调用的机制,虚拟机的性能将会得到大幅度的提升,但是只有专属的虚拟机镜像可以被使用。
图 2-7
2.3.3 硬件辅助虚拟化
硬件辅助虚拟化,顾名思义,就是借助硬件实现虚拟化。为了更好地解决x86架构下虚拟化漏洞的问题,芯片厂商扩展了其指令集来支持虚拟化。其核心思想是通过引入新的指令和运行模式,使虚拟机监视器和GuestOS分别运行在root模式和no-root模式下,这样一来,GuestOS还是可以运行在Ring 0下的,GuestOS的内核也不需要修改。在通常情况下,GuestOS的特权指令可以直接下达到计算机系统硬件执行,而不需要经过虚拟机监视器。当GuestOS执行到敏感指令时,系统会切换到虚拟机监视器,让虚拟机监视器来处理特殊指令。
为了弥补x86处理器的虚拟化缺陷,Intel推出了基于x86架构的硬件辅助虚拟化技术VT-x,支持硬件辅助虚拟化。Intel的VT-x对虚拟机设计了一种新的模式,引入root模式和no-root模式的概念,两者都可以运行在Ring 0~Ring 3下。其中虚拟机监视器运行在root模式下的Ring 0下,而GuestOS运行在no-root模式下的Ring 0下。因为root模式下的Ring 0比原先的Ring 0还要优先,所以业界也把root模式下的Ring 0称为Ring-1。Intel的VT-x技术解决了早期x86架构在虚拟化方面存在的缺陷,可使未经修改的GuestOS运行在Ring 0下(在no-root模式下),同时减少虚拟机监视器对GuestOS的干预。
VT-x将虚拟机监视器与GuestOS的执行环境完全隔离开,通过指令集的变化实现虚拟机监视器和GuestOS运行环境之间的相互转换,即上文所述的root模式和no-root模式。启动或退出root模式和no-root模式通过新添加的VMXON和VMXOFF指令实现。从root模式到no-root模式的转换通过VMEntry指令实现,从no-root模式到root模式的转换通过VMExit指令实现。若在no-root模式下执行了敏感指令或发生了中断等,则会执行VMExit操作,切换回root模式运行虚拟机监视器。切换过程如图2-8所示。
图 2-8
对于相同的敏感指令,在root模式和no-root模式下被处理的方式不同。当虚拟机中的应用产生一个中断时,会通过VMExit退出no-root模式,并把详细的中断信息保存在VMCS寄存器中。虚拟机监视器随后会确认该虚拟机退出的原因,执行相关操作,模拟虚拟机中的中断操作,并把结果发送给该虚拟机。该虚拟机重新进入no-root模式,其GuestOS根据收到的中断结果继续执行中断的后续操作。在VT-x模式下,Ring 0下的指令会根据是否在root模式下而采取不同的处理方式,从而实现虚拟化。也就是说,在硬件层面已经做了区分,因此在全虚拟化情况下,性能越来越好。如前文所介绍的,VT-x通过新添加的指令实现Ring-1,从而解决了指令二进制翻译及类虚拟化无法解决的问题。硬件辅助虚拟化机制如图2-9所示。
图 2-9
在VT-x模式下,当GuestOS的内核运行在no-root模式下的Ring 0下时,对系统资源操作的一部分指令不通过虚拟机监视器,而是穿透虚拟机监视器直接在CPU上运行,我们将这部分指令称为“非敏感指令”或“无害指令”。这种模式可以大大提升虚拟化的效率,实现虚拟化三大标准中的“高效性(Efficiency)”。
现在我们回顾一下CPU虚拟化技术的实现。纯软件的CPU虚拟化使用了“陷阱+模拟”的方式来模拟特权指令,而在x86架构下,由于只能模拟特权指令,无法模拟虚拟化漏洞指令,因此无法实现全虚拟化。硬件辅助虚拟化引入了root模式和no-root模式,它们都有Ring 0~Ring 3这四种特权级别。所以,在硬件辅助虚拟化中,传统的“陷阱”概念实际上被 VMExit 操作取代了,它代表从no-root模式切换到root模式,而从root模式切换到no-root模式使用VMEntry指令实现。基于硬件辅助的虚拟机监视器做了大量的优化,GuestOS 内包括 I/O 驱动等都无须做任何修改,GuestOS可以直接在CPU上运行一部分特权指令,在确保虚拟机稳定运行的情况下大大提升了性能。