虚拟化技术在概念上与仿真类似。仿真技术可以在一个平台上构建出另外一个平台(比如在X86平台的Linux系统中通过QEMU仿真ARM64平台),虚拟化技术可以在一个平台上构建一个或者多个相同结构的平台。现代操作系统通常都包含一个简化的虚拟化系统,用于虚拟化CPU和内存。CPU虚拟化技术使得每个正在运行的进程表现得好像它是唯一正在运行的进程。如果一个进程试图消耗所有的CPU资源,操作系统将抢占这个进程的CPU资源并允许其他进程公平分享。同样,内存虚拟化技术使得正在运行的进程通常有自己的虚拟地址空间,操作系统将虚拟地址空间映射到具体的物理内存,让该进程产生一种错觉,认为它是整个物理内存的唯一用户。
硬件设备通常也会被操作系统虚拟化。进程通常使用伯克利套接字来访问网络设备,而无须担心其他应用程序的干扰。操作系统的图形用户界面,比如GNOME(GNU Network Object Model Environment,GNU网络对象模型环境)、KDE(K Desktop Environment,K桌面环境)或终端模拟器(比如Linux Terminal)复用屏幕和输入设备,使得用户感觉好像他们在独占整个计算机系统。
坦白地说,我们每天都有可能触及虚拟化技术,享受虚拟化带来的好处。虚拟化所提供的隔离性通常可以防止一个系统的Bug或恶意行为破坏其他系统的正常运行。
虽然系统中的操作系统组件为应用程序提供了一定程度的隔离性,但如果操作系统组件包含Bug,这种隔离性就会被打破。而Hypervisor可以为应用程序提供完全的隔离,基于Hypervisor构建的系统也会比原生操作系统具备更强的隔离性。Hypervisor实现强隔离性的关键是虚拟化技术,包含CPU虚拟化和外围设备I/O虚拟化。
1.虚拟化技术概述
对直接运行在硬件上的操作系统(见图1-1a)而言,CPU的虚拟化就是对CPU资源的分时复用。vCPU本质上是一小段在pCPU(Physical CPU,物理CPU)运行的时间片。当一个进程独占CPU运行一段时间被中断后,操作系统会保存当前进程的上下文,然后恢复另一个进程的上下文,一段时间后重复类似操作。上述过程在主流操作系统(比如Linux)中一般每10ms发生一次。
提示: 此时的vCPU和pCPU虽然概念上是类似的,但实现上并不相同。vCPU包含虚拟用户模式和虚拟特权模式,pCPU包含原生用户模式和原生特权模式。vCPU的虚拟特权模式或由Hypervisor模拟实现,或由vCPU和Hypervisor协作实现。
当部署Hypervisor时,如图1-1b所示,Hypervisor运行在pCPU的特权模式下,原本直接运行在原生硬件上的操作系统改变为运行在Hypervisor创建的虚拟机中,并且降级到用户模式运行。此时Hypervisor需要解决的问题不仅是pCPU的分时复用,还涉及映射到虚拟机中的vCPU特权指令的模拟问题。因为虚拟机中的操作系统仍然会执行它所认为的特权指令,这才是CPU虚拟化所要解决的关键问题。
图1-1 传统操作系统模式和Hypervisor运行模式
为了能更清楚地描述CPU的虚拟化问题,计算机科学领域的著名学者杰拉尔德·J.波普克(Gerald J. Popek)和罗伯特·P.戈德堡(Robert P. Goldberg)在1974年发表的论文《第三代可虚拟化架构的正式要求》中引入了特权指令和敏感指令的概念,并将CPU的指令集分为3类:特权指令、控制敏感指令和行为敏感指令。其中,控制敏感指令是指试图修改系统中资源配置状态的指令,包括更新虚拟地址到物理地址的映射、修改与设备通信或使用系统全局配置相关的寄存器指令等;行为敏感指令的行为或结果取决于系统的资源配置状态,例如对虚拟内存进行加载和存储操作的指令就属于行为敏感指令。
这些概念的引入对虚拟化和操作系统的设计非常重要,这3类指令的拦截和模拟是虚拟化技术实现的核心。这篇论文为虚拟化技术的发展奠定了基础,并对后续的研究和实践产生了深远影响。
CPU可虚拟化的充分条件是所有敏感指令(包括控制敏感指令和行为敏感指令)必须是特权指令集合的子集。Hypervisor利用特权指令实现对敏感指令的拦截和处理,以控制虚拟机对底层硬件的访问,实现对虚拟机的隔离和资源分配。Hypervisor负责解释敏感指令或修改其行为,或将敏感指令传递给底层物理硬件进行处理。这样的机制允许Hypervisor实现虚拟机的隔离、资源调度、内存虚拟化和设备模拟等功能。
2.虚拟化技术的类型
从客户操作系统的角度来说,CPU虚拟化行为可以分成两类。
(1)完全模拟原生pCPU的行为
完全模拟原生pCPU的行为,客户操作系统将完全不需要修改就可以直接在虚拟机中运行,就像运行在原生物理设备上一样。因此客户操作系统感知不到Hypervisor的存在。
(2)非完全模拟原生pCPU的行为
非完全模拟原生pCPU的行为需要客户操作系统和Hypervisor相互配合来实现客户操作系统的服务,因此客户操作系统可以感知到Hypervisor的存在。
3.虚拟化技术的原理
具体采用哪一种虚拟化技术实现Hypervisor,取决于CPU ISA(Instruction Set Architecture,指令集架构)和具体的应用需求。
(1)可虚拟化的ISA
如果所有的敏感指令都是特权指令,根据波普克和戈德堡的理论,该CPU ISA可通过陷入-仿真 一模型进行完全虚拟化,如图1-2所示。支持陷入-仿真模型的ISA有ARMv8、RISC-V、MIPS、PowerPC、SPARC指令集。
图1-2 基于陷入-仿真模型的完全虚拟化技术
在虚拟化实现中,Hypervisor运行在特权模式,具有对硬件资源的完全控制权,因而可以在所有虚拟机间共享硬件平台。所有虚拟机运行在用户模式下,而虚拟机中的vCPU期望能够访问所有硬件资源,因此Hypervisor必须提供一种间接的机制来实现vCPU期望的所有功能。在用户模式下执行特权指令会产生一条陷阱异常信息,Hypervisor会利用这个特征来捕获虚拟机试图执行的特权指令,并精确地模拟该特权指令的原有功能。
如图1-2所示,如果异常触发的原因是vCPU在虚拟特权模式下执行特权指令,则该条特权指令会被Hypervisor模拟;如果异常是由于vCPU处于虚拟用户模式下非法执行特权指令而触发(比如除零异常),则该异常会被Hypervisor转发给当前虚拟机,并在vCPU中触发一个可编程异常。也就是说,如果pCPU处于虚拟用户模式,但是vCPU处于虚拟特权模式,那么需要给vCPU创造一种幻象,即vCPU运行在自己期望的特权模式中,并且每一条特权指令在Hypervisor中都会有一个对应的模拟程序。当Hypervisor需要模拟某条特权指令时,对应的程序将会被调用。总之,无论是Hypervisor模拟特权指令还是将异常转发给虚拟机,Hypervisor最终都会恢复vCPU的上下文,以使当前虚拟机继续执行。
(2)不可虚拟化的ISA
如果某敏感指令不是特权指令,那么该敏感指令在虚拟用户模式下执行时不会触发陷阱,因而无法被运行在虚拟特权模式下的系统软件所捕获,所以不能通过陷入-仿真模型进行完全虚拟化。例如,在没有引入硬件虚拟化扩展的X86 32位指令集中,某些敏感指令在虚拟用户模式下执行时会被忽略,而不会触发陷阱异常。以X86的POPF指令为例,该指令用于替换标志寄存器的值,会改变允许/禁止中断的标志位。但是在虚拟用户模式下执行这条指令时,这个标志位不会被改变,也不会触发陷阱。因此,X86 32位ISA是不能通过陷入-仿真模型进行虚拟化的ISA。但是我们可以通过特殊的工程技术来实现pCPU虚拟化(详情见1.1.4小节)。
讨论了CPU虚拟化后,下面来研究一下I/O虚拟化。
客户操作系统在启动的时候会检测硬件,以找出当前系统中连接的所有I/O设备。这些检测操作都会陷入Hypervisor中。那么Hypervisor如何处理这些设备的I/O请求呢?
通常有两种解决方案。
第一种方案 :将设备的管理功能集成到Hypervisor中,作为Hypervisor的一个子系统,由Hypervisor根据这些I/O请求操作对应的I/O设备,并将结果反馈给虚拟机中的客户操作系统。这一方案的弊端在于使得Hypervisor的代码量和复杂度急剧增加,从而很难保证Hypervisor的安全性和高效率,故通常只将很少的驱动集成到Hypervisor中,比如控制台、串口驱动。
第二种方案 :将所有I/O设备分配给一个虚拟机,其他虚拟机的I/O请求全部发送给这个虚拟机处理。这种方案具有极大的灵活性,可以利用现有客户操作系统中的驱动程序。如果设备本身就是一个独占设备,那么它至多被分配给一个虚拟机。但是如果设备需要在不同的虚拟机间共享,则需要额外的管理方法。在操作系统的设备驱动中,只要对该设备用互斥锁则可保证I/O操作的原子性。但是在Hypervisor的设计中使用互斥锁会破坏不同虚拟机间的隔离性。
因此I/O设备虚拟化最合适的方法就是创建相互隔离的安全虚拟机(见图1-3),让虚拟机独占该设备,并为每一个申请访问它的应用虚拟机提供服务。每个应用虚拟机通过这种模型向I/O虚拟机和人机交互虚拟机发送请求来访问真实的设备。而在虚拟设备的底层,Hypervisor需要提供必要的机制(共享内存通道)来传递这种请求,以保证虚拟机间的隔离性。
图1-3 I/O设备虚拟化模型
虚拟化技术主要有以下几点优势。
1)Hypervisor可以在同一物理设备上并行运行多种操作系统。例如,可在基于Hypervisor构建的虚拟平台上同时运行提供实时任务的RTOS(如VxWorks、μC/OS-Ⅱ等)和提供非实时任务的GPOS(如Linux或Windows),既解决了GPOS实时性方面的不足,又解决了RTOS应用不够丰富的劣势,如图1-4所示。
2)通过虚拟化技术可以把不同子系统封装到不同的虚拟机中。比如,驱动程序、网络协议栈或者文件系统等内核组件可以直接运行在某个虚拟机上,其他子系统可以共享这些组件,这大大提高了系统的安全性和代码的复用率。即使其中一个子系统崩溃或者被攻击,也不会影响其他子系统。
图1-4 典型Hypervisor示意图
对于现代嵌入式操作系统来说,代码量越来越庞大,存在的安全隐患也越来越多。比如,缓冲区溢出攻击就是一种常见的网络攻击手段,原理是利用用户程序对缓冲区的超界访问,访问系统关键数据和程序,进而窃取系统的控制权。在没有使用Hypervisor架构的系统(见图1-5a)中,缓冲区溢出攻击一旦成功,整个操作系统将暴露在入侵者面前,入侵者将能完全控制整个系统资源,访问所有的关键模块。而在使用了Hypervisor架构的系统(见图1-5b)中,即使用户端的交互应用被入侵,导致其所在的操作系统被劫持,也不会致使运行在Hypervisor上的其他客户操作系统被控制,即把系统被入侵的损害降到了最低。
图1-5 缓冲区溢出攻击示意图
3)Linux作为主流GPOS的优点是使用免费,且有一个庞大的开源社区支持。Linux遵照GPL(GNU General Public License,GNU通用公共许可协议),要求任何由Linux衍生出来的代码都要遵照同样的许可发布,也就意味着开源。这对商业开发者来讲是一个两难的选择,既希望使用免费的系统,又需要保护商业机密。通过Hypervisor可以实现许可的隔离,如图1-6所示。
图1-6 许可隔离
4)虚拟化技术实现了软件和硬件的松散耦合,使得客户操作系统只需要做极少的改动就可以移植到一个新的平台。虚拟化技术还可以为客户操作系统提供稳定的运行环境,特别是维护周期长的系统,可以不受硬件平台限制而得到较长时间的维护。采用Hypervisor方案,看起来像是把所有的鸡蛋放在同一个篮子里。如果运行所有虚拟机的Hypervisor崩溃了,其结果可能比单独一台专用服务器的崩溃要严重得多。然而大多数服务器停机的根源不在于硬件故障,而主要在于臃肿、不可靠、有漏洞的软件,特别是操作系统。使用Hypervisor方案,可以让运行在特权模式下的软件仅有Hypervisor,其代码量比一个完整操作系统的代码量低两个数量级,也就意味着漏洞的数量也低两个数量级,风险系数大大降低。
除了松散耦合带来的强大隔离性,采用Hypervisor方案还有其他的好处。其一是减少物理机器的数量,占用空间更少,节省了硬件与电源的开支。其二是可以设置备份点,虚拟机的迁移仅需移动内存映像和主要保存在操作系统中的进程关键状态信息(包括与打开文件、警报、信号处理函数等相关的信息),这使得虚拟机的迁移比在普通操作系统中进行进程的迁移要容易得多。
最早实现CPU完全虚拟化的技术是陷入-仿真模型。而对于不支持陷入-仿真模型的CPU ISA(比如X86 32位指令集)来说,一方面可以通过二进制翻译技术和半虚拟化技术实现虚拟化;另一方面也可以对不能虚拟化的CPU ISA进行扩展,引入新的指令和运行模式,来支持陷入-仿真模型,这种方式一般称为硬件虚拟化技术。接下来分别介绍这3种技术。
1.二进制翻译技术
二进制翻译(Binary Translation,BT)技术(见图1-7)由VMware公司于1998年首次应用在面向PC(Persoual Computer,个人计算机)的Hypervisor产品VMware Workstation当中。VMware Workstation首先扫描操作系统内核二进制文件的代码段,以确定基本块。所谓基本块,是指程序顺序执行的语句序列。根据定义,除了最后一条指令,基本块内不会含有其他改变程序计数器(Program Counter,PC)的指令。然后VMware Workstation会检查基本块中是否含有敏感指令。如果有敏感指令,则每条敏感指令都会被替换成VMware Workstation的过程调用。与此同时,基本块的最后一条指令也会被VMware Workstation的过程调用替代。基本块执行结束后,CPU控制权会再次返回VMware Workstation,并扫描下一个基本块的位置。一般一个基本块需要依次进行翻译、缓存、执行等操作。如果基本块已经被翻译完毕并缓存,就可以被立刻执行。待所有基本块都被翻译完成后,系统内大多数程序都将被缓存并且被接近全速执行。如果没有敏感指令,那么基本块和用户应用程序可以直接在硬件上运行。
图1-7 采用二进制翻译技术虚拟化X86平台
如果使用X86 32位处理器(该处理器不支持虚拟化),并且客户操作系统无法获取源码(如Windows系统),那么二进制翻译技术将是唯一的全虚拟化解决方案。为弥补X86处理器的虚拟化缺陷,在市场需求的驱动下,Intel和AMD分别推出了基于X86架构的硬件辅助虚拟化技术Intel VT-x和AMD-V(AMD Virtualization,AMD虚拟化)。自此之后,二进制翻译成为一项过渡技术。随着基于X86架构的硬件虚拟化技术的广泛应用,二进制翻译逐渐退出了历史舞台。VMware公司在2017年发布的VMware vSphere 6.5是基于二进制翻译技术的最后一个Hypervisor版本。
2.硬件虚拟化技术
Intel VT-x和AMD-V的核心思想都是通过引入新的指令和运行模式,使Hypervisor和客户操作系统分别运行在不同模式(root模式和非root模式)下,且客户操作系统运行在Ring 0下。通常情况下,客户操作系统的核心指令可以直接下达给计算机系统硬件执行,而不需要经过Hypervisor处理。当客户操作系统执行到敏感指令时会触发陷阱,自动陷入root模式,让Hypervisor截获并处理这条特权指令。这种对现有指令集进行扩展的虚拟化技术称为HVM(硬件虚拟化技术),如图1-8所示。
图1-8 X86上的硬件虚拟化技术
采用硬件虚拟化技术的Hypervisor,客户操作系统不需要经过修改。但是这类Hyper-visor在性能上会有所降低,因为使用虚拟化技术的硬件采用陷入-仿真的方式,引入了太多的陷入操作。而在现代CPU硬件平台上,陷入的代价是昂贵的,会清空处理器的缓存、TBL和分支预测表。
3.半虚拟化技术
对客户操作系统来说,半虚拟化技术(Para-Virtualization,PV)类似应用程序通过操作系统的系统调用获取操作系统的内核服务。当采用这种方法时,Hypervisor必须定义由过程调用集合组成的API供客户操作系统使用,这类API集合一般称为Hypercall API。
Hypervisor精确仿真复杂指令的语义是一件耗时的工作,而客户操作系统直接调用Hypercall API去完成I/O任务,比精确仿真每条敏感指令更有优势。半虚拟化技术用于X86平台的虚拟化方案如图1-9所示。
二进制翻译技术和硬件虚拟化技术本质上都是模拟完整的计算机敏感指令集,主要原因是操作系统的源码不可获取(比如Windows)或者源码种类多样(比如Linux)。当然理想的情况是Hypercall API可以标准化,而后续的操作系统都调用该Hypercall API接口,而不是执行敏感指令,这样的做法将会使得虚拟化技术更容易被支持和使用。另外,我们可以在支持虚拟化技术的硬件平台上同时采用完全虚拟化技术和半虚拟技术来实现Hypervisor,这样可以充分发挥两种虚拟化技术的优势,如图1-10所示。
图1-9 半虚拟化技术用于X86平台的虚拟化方案
图1-10 支持完全虚拟化和半虚拟化的Hypervisor
在图1-10中,在支持虚拟化技术的硬件平台上,左侧是一个没有经过修改的Windows系统,执行敏感指令时硬件陷入Hypervisor,Hypervisor通过仿真来执行这条敏感指令的精确语义并返回。右边是一个经过半虚拟化修改的Linux内核版本,其中不含有敏感指令。当需要进行I/O操作或者修改重要内部寄存器(比如指向页表的寄存器)时,Linux内核会调用Hypervisor中的程序来完成操作。就像在标准Linux系统中,应用程序通过int指令实现系统调用获取Linux内核服务一样。
Hypervisor可以同时实现HVM和PV。当Windows在支持HVM的Hypervisor上运行时,Hypervisor将解释陷入的敏感指令;当客户Linux系统在支持HVM的Hypervisor上运行时,半虚拟化后的Linux会直接执行Hypercall API,而不需要对敏感指令进行仿真。这时的Hypervisor在某种程度上相当于一个微内核架构的操作系统(只执行最基本的服务,比如内存管理、中断管理等),代码短小精练,以特权模式运行在硬件上,大幅提升了系统的可靠性。
提示: 这里介绍的Hypervisor通常是Ⅰ型Hypervisor,Ⅱ型Hypervisor会在1.2节详细讨论。
对客户操作系统进行半虚拟化也存在一些问题。第一,如果所有的敏感指令都被Hypervisor中的程序所替代,硬件不可能理解Hypervisor的内部程序,那么虚拟化的操作系统将无法再次在原生物理机器上运行;第二,市场上的Hypervisor(比如VMware Workstation、Xen、KVM(Kernel-based Virtual Machine,基于内核的虚拟机)等)Hypercall API的标准不同,导致修改后的内核无法在所有的Hypervisor上运行。对此,目前一个可取的解决方案是使用VMI(Virtual Machine Interface,虚拟机接口)来处理内核对敏感操作的执行和对Hypercall API的调用。VMI主要用于底层与硬件、底层与Hypervisor的交互,如图1-11所示。
图1-11 VMI Linux运行在裸机、VMware Hypervisor、Xen Hypervisor上
图1-11所示是一个半虚拟化的Linux版本,称为VMI Linux。VMI Linux直接运行在原生硬件上时,会链接到通过发射敏感指令来完成工作的函数库,如图1-11a所示;当运行在Hypervisor(VMware Workstation或者Xen)上,则会链接到另外一个函数库,该函数库提供对下层Hypervisor的超级调用程序的封装,如图1-11b和图1-11c所示。通过在不同场景下链接不同函数库这一方式,操作系统内核保持了可移植性和高效性,可以适配不同的Hypervisor。