x86虚拟化之迷


微型计算机 10年5月上
x86硬件辅助虚拟化之迷
  上一期我们对虚拟化技术作了简要的回顾,并对x86指令集固有的虚拟化漏洞进行了解析,相信大家已经对英特尔和AMD(英特尔VT和AMD-V)是如何解决x86虚拟化漏洞提起了兴趣,本期我们就一点点地揭开处理器巨头身上的虚拟化标签。

  上期我们已经讲到,虚拟化其实就是在“学习”计算机体系中硬件抽象层和API抽象层的工作内容。

  在计算机中,CPU无疑是最重要的部件,要想虚拟化出一台电脑,CPU的虚拟化无疑是重中之重,事实上英特尔和AMD近年来也在全力推广CPU的虚拟化技术。那么究竟哪些处理器是支持硬件虚拟化,除了处理器还需要什么配合才能实现硬件虚拟化呢?下面我们先来看看支持CPU虚拟化的硬件环境要求。

  虚拟化的硬件环境需要哪些硬件支持

  从处理器推出硬件虚拟化技术到现在,已经有四年的时间。虽然不少主流市售的处理器已经支持硬件虚拟化技术,一些新型的主板芯片组也提供了对I/O虚拟化的支持,但总的来看并非所有平台已经普及硬件虚拟化支持。对于企业用户来说,要想知道自己是否可以从本文中学以致用,首先要看公司的设备是否有配套的“兵器”(CPU或者主板),有必要对公司的硬件装备进行一次例行检查。

  英特尔VT(如不特殊说明,本文均只指VT-x技术)和AMD-V的硬件支持不仅仅局限于CPU,BIOS中是否开启相关选项,芯片组是否内置相关技术都是虚拟化支持的条件。

  英特尔CPU对虚拟化的支持情况精确到了CPU的SPEC code,以英特尔 Core2 Duo系列处理器为例,即使是同一型号,也有可能分为支持虚拟化和不支持虚拟化的多个批次,例如Core 2 Duo E7500中,编号为SLB9Z的不支持,SLGTE的支持。如果大家想了解公司的英特尔CPU是否支持虚拟化,最好的办法是去官方网站查询,查询网址是:http://ark.intel.com/VTList.aspx。

  AMD最早支持AMD-V的产品是20 0 6年发布的Orlean内核的Athlon 64。在服务器CPU方面,REV.F版本的皓龙(Opteron)及后续版本(包括巴塞罗那、上海、伊斯坦布尔等核心)的处理器都支持;在桌面处理器方面,除了Sempron系列、Rev.F版本以前的Athlon/Athlon64 X2系列,均支持AMD-V;除了Sempron系列和TurionK8 Rev E系列以外,AMD之后的移动处理器均支持。

  BIOS是否支持虚拟化也很重要,一般会有如图1的设置界面,若要实现虚拟化功能,需要用户在BIOS中启用它。如果BIOS中找不到相关选项,那么就只能到主板厂家的官方站点去查询下载新版的BIOS,看是否提供了开启虚拟化的功能。英特尔和AMD其实都提供了对应的MSR寄存器来对硬件虚拟化技术进行激活和屏蔽处理。主板厂家在写BIOS Code的时候只需要进行相应设定即可。

  关于芯片组支持虚拟化特性,主要是指对虚拟化I/O的支持。在英特尔平台是指支持VT-d,ICH9系列和ICH10系列南桥都符合要求,而如果采用X58芯片组配合Core i7系列处理器,则可以组成虚拟化功能最全面的配置。AMD方面则主要在它们的服务器芯片组领域提供了I/O、MMU的虚拟化支持特性,例如SR5690和SR5670芯片组。

  如何用工具检测

  上面三点是检测虚拟化功能的三个要点,但对于一般用户来说,总是希望能通过软件直接了解公司的配置是否支持虚拟化。能够检测虚拟化技术的工具软件非常多,笔者推荐SecurAble,因为其它一些CPU信息类检测软件检测到的VT开启实际上多半只是对CPU寄存器的探测,并没有考虑到BIOS相关设定。

  另外提醒大家注意的是,这款工具并不能检测VT-d。

  当有如图2所示显示的时候,就表明你的英特尔CPU支持虚拟化并且在BIOS中VT的设置已经打开,如果显示Locked Off(图3)则表示在BIOS中进行了屏蔽或相关设定,使得虚拟化选项没有正常开启,如果显示NO则表示CPU不支持虚拟化。

  传统的软件CPU虚拟化模式

  软件辅助的CPU虚拟化主要有两大流派,一种叫做软件完全虚拟化(Soft Full Virtualization),以VMware为代表,另一种则是以Xen联盟为代表的半虚拟化(ParaVirtualization,也有译作操作系统协助虚拟化)。

  软件完全虚拟化的整个过程中并不需要硬件和操作系统协助参与,它不需要对客户操作系统进行代码修改。而半虚拟化则不同,它不但在VMM上面运行一个或者多个客户操作系统,还在这些客户操作系统“旁边”运行一个特别的“管理操作系统”(用Citrix Xen的术语叫做Dom0,用Microsoft Hyper-V的术语叫做theParentPartition)。这个“管理操作系统”是给系统管理员用来管理VMM的。而客户操作系统在CitrixXen体系中叫做DomU,在Microsoft Hyper-V架构中叫做ChildPartitions。在解决x86指令集漏洞这个问题上,半虚拟化技术是通过修改客户操作系统的源代码从而使得客户操作系统和VMM工作在不同特权级别以方便进行陷入和模拟。这项技术之前有一个明显的不足,那就是对像Windows这样不开发源代码的操作系统就无法进行虚拟化,但在硬件辅助虚拟化出现后,这个缺陷已经得以补正,而近期英特尔和Xen的合作相当紧密,使得半虚拟化技术的发展也很迅速。

  在传统的C P U 虚拟化模型中,CPU最常采用的是特权解除(Privilege Deprivileging)和“先陷入后模拟”的技术。其中特权解除是指为了实现VMM对虚拟机的全盘控制从而降低客户操作系统的运行特权级别的技术,它可以使VMM和客户操作系统进行类似原生操作系统中用户态和核心态之间的请求和调度返还。客户操作系统被解除特权后一般会降至Ring1或者Ring3层次(形成上期文章中提到的0/1/3和0/3/3模型,这个现象也被称为特权压缩—Ring Compression),无论是哪一种模型,客户操作系统都无法运行于最高特权级别Ring0上,这样类似GDT,IDT,LDT和TSS这些特权指令就没法直接运行,只能通过陷入模拟的方式进行,大大增加了系统开销。而现在的x86-64平台上因为必须使用分页模式(它不能区分Ring0和Ring1/2),使得客户操作系统和客户机应用程序均运行在Ring3上,从而出现了有名的IA-32隔离安全性问题,这也是软件完全虚拟化中无法在IA32架构上虚拟出64bit客户操作系统的真正原因。

  特权解除这项技术的初衷是使得大部分指令可以由客户操作系统直接执行,而执行特权指令时则可以顺利地产生陷阱,将指令交由最高特权级的VMM去处理。听上去的确很美好,但这种技术在x86指令集上却存在问题。正如前文所讲,x86指令集漏洞使得有部分指令不能够正常的陷入,而完全虚拟化中的动态二进制翻译(Dynamic BinaryTranslation,常简称DBT)技术和半虚拟化中的修改客户操作系统源码的技术都可以对付这些不友好的指令(下文我们仅以动态二进制翻译作为案例进行讲解)。

  从图4中,我们不难看出,动态二进制翻译解决的正是发生在Guest OS、VMM、系统硬件三者之间的部分敏感指令不能正常陷入的问题(x86指令集虚拟化漏洞)。在这里我们有必要对动态二进制翻译技术(这里只考虑相同ISA结构下的情况)进行简单的讲解。

  所谓的动态二进制翻译,其实就是将客户操作系统中正在执行的核心态代码动态地转换为已经虚拟的安全代码的过程。这里所谓的“安全”,指的是对其它被客户操作系统以及VMM来讲是安全的代码。

  二进制代码翻译工作只发生在客户操作系统的内核态代码被调用的情况下。

  动态二进制翻译发展到今天已经越来越成熟,一些减少动态二进制翻译系统开销的优化技术也渐渐成熟,二进制翻译后的代码是保存在转译缓存中,当遇上需要转译的指令流中存在循环指令的情况下,就可以直接从缓存中读取这部分指令流而不需要再次转译,同时它还可以起到Trace Cache(追踪缓存)的作用,可以对跳转指令进行优化处理,这在一定程度上减小了动态二进制翻译的性能开销。

  尽管优化后的动态二进制翻译已经在性能损耗上有明显下降,但动态二进制翻译技术依然有几个大的难题不易解决,它们分别是:1.系统调用、2.访问的芯片组和I/O,3.中断和DMA,内存管理、4.实时代码和精确异常等问题。这里面我们简单讲解一下系统调用机理,因为半虚拟化的超级调用和硬件辅助虚拟化中的陷入模拟都和其机理相似;而I/O,中断和内存方面的问题在硬件辅助虚拟化越来越成熟后已经得到了明显改变,但第4点中提到的缺陷则还没有非常好的解决办法。

  我们可以说动态二进制翻译的系统性能开销有相当大的“功劳”是来自于系统调用。什么是系统调用呢?熟悉操作系统原理的人一定对此并不陌生,我们就以大家最熟悉的Windows平台为例,一个系统调用就是一个用户应用程序请求内核服务的结果。系统调用提供了进程和操作系统的接口(注:上一期我们讲解的ABI概念中提到了系统调用),这些调用通常以汇编语言的指令形式出现。系统调用大约可以分成进程控制、文件管理、设备管理、通信和信息维护五类,下面我们就以图7进程控制中的申请内存资源分配为例进行讲解。

  我们可以看到,用户程序通过产生陷阱从用户态切入了内核态,去申请扩大自己的内存使用资源,申请成功后又从内核态返回了用户态,在x86体系中系统调用的命令为:SYSENTER和SYSEXIT(或者快速SYSCALL指令等)。根据相关测试数据表明,一个在虚拟机上运行的系统调用比运行虚拟机的物理本机运行系统调用的开销大10倍。VMware的工程师曾经测试过一个采用3.8GHz Pentium 4的系统,结果如下:

  ◎ 物理机器的系统调用需要242周期;

  ◎ 在Ring1层级上,一个32位客户操作系统上运行一个二进制转译的系统调用需要2308周期。可以看出它浪费的周期和原生物理电脑相比还是相当大的,这在一定程度上阻碍了虚拟化的进一步性能提升,不过在硬件辅助虚拟化功能越来越成熟的今天,这个局面已经得到了有效的改善。

  分为两家的x86 CPU硬件虚拟化英特尔和AMD现在都是新增了针对虚拟化的操作模式和相关指令,而在机理相似的虚拟机控制结构方面,英特尔称呼它为VMCS,而AMD则叫VMCB。

  尽管在一些指令和模式细节上两者依然存在不同,但在整体实现原理上基本上没有太大差别,主要不同可参见图8和图9。

  在此笔者并非给大家展示两家技术细节,而是要告诉大家什么是x86平台的硬件辅助虚拟化。因此本期将以英特尔处理器的虚拟化技术为例进行讲解。

  x86硬件辅助CPU虚拟化的过程

  注:首先需要明确一点,英特尔的CPU虚拟化采用的技术别称为VT-x,但VT-x中并不仅仅只包含CPU虚拟化,还包括中断虚拟化和内存虚拟化等内容,而AMD在AMD-V在官方资料中也是把CPU、内存和中断等虚拟化技术全部放在了SVM技术规范中进行统一讲解的。

  传统的IA32处理器架构并不是十分可靠的虚拟化架构,为了解决这个问题,英特尔通过VT-x技术对原有架构进行了扩展补充。

  VT-x技术引入了两种专为虚拟化打造的操作模式,称为根操作模式(VMX Root Operation)和非根操作模式(VMX Non-Root Operation ),其中VMM运行在根操作模式下,而客户操作系统则运行在非根操作模式下,每个模式都存在Ring0-3四个特权级别,所以在VT-x中,对特权级别进行描述时必须说明是在根模式还是在非根模式下。对照前面我们讲解的软件完全虚拟化时的示意图我们不难发现,客户操作系统(Guest OS)所运行的特权级别发生了变化,由Ring1变成了Ring0,而原本工作在Ring0的VMM则被注明是工作在根操作模式下的Ring0上(有一些文档中称之为Ring-1)。相信仔细阅读上期虚拟化文章的读者朋友,不难理解英特尔为什么要制造出两个新的操作模式来,因为客户操作系统重新回到了Ring0上(当然这里是非根模式下的),而且经过英特尔对相关指令的重新设计,使得原本不能通过先陷入后模拟的方式执行的指令都可以顺利执行,而在根模式下,所有指令的执行和传统IA-32相比不会有任何变化,从而保证了原有软件和虚拟环境的正常运转。

  在硬件辅助CPU虚拟化中,陷入的概念已经被VM-Exit操作取代,它意味着从非根操作模式切换到根操作模式,对应的从根操作模式切换回非根操作模式被称为VMEntry。我们在上一期的文章中提到了CPU虚拟化的基本原理,这里我们有必要温习一下。里面提到了“CPU虚拟化是为物理机器上的每一个虚拟机提供一个或者多个虚拟CPU(简称VCPU),每个VCPU分时复用物理CPU,在任意时刻一个物理CPU只能被一个VCPU使用,VMM要在整个过程中合理分配时间片以及维护所有VCPU的状态”,这里谈到的VCPU状态维护其实就是VCPU的上下文切换,而VCPU的环境结构主要有硬件使用部分和软件使用部分组成,软件部分主要由VMM控制,主要包括VCPU的状态信息,浮点寄存器等,而硬件使用部分指的是英特尔和AMD用来描述和保存VCPU状态信息的内存空间,它们分别存放在被称为VMCS(Virtual-MachineControl Structure,虚拟机控制结构)和VMCB(Virtual-Machine ControlBlock虚拟机控制块)的数据域中,VMCS和VMCB都是最大不超过4KB的内存块。在进行VCPU上下文切换的时候,要涉及硬件部分和软件部分两方面,而本篇主要是介绍和硬件部分关系密切的VMCS和VMCB。介绍完两种操作模式,下面我们就以英特尔平台为例来对新指令和虚拟机控制结构进行进一步介绍。

  如果暂不考虑EPT(内存虚拟化)相关指令,英特尔为VMX和VMCS共引入了十条指令,并且分别有明确的分工和定义,下面先简单介绍一下引入指令。

  VMX ON和VMX OFF是用来打开和关闭VMX操作模式的指令,在默认情况下,VMX是关闭的,当需要使用这个功能时,可以通过VMX ON随时进行VMX模式。在进入VMX模式后,VMM又会通过VMLAUNCH或者VM RESUME指令产生VMEntry,使CPU从根操作模式切换至非根操作模式,从而开始运行客户机相关软件。在运行软件的过程中如果发生中断或者异常,就会激活VM-Exit操作,此时CPU又进行了一次模式切换,只不过这次是切换到根操作模式,在处理完成后一般又会返回非根操作模式去运行客户机软件。如果不想运行虚拟机软件的时候,则会利用VMX OFF关闭VMX操作模式。

  除此之外,还有一条VMCALL指令,因为这个指令涉及到大家不常用的SMM(系统管理模式)VM Exit,所以这里就不多作介绍了。

  而在VMCS方面,每个VMCS对应一个虚拟CPU(VCPU),在虚拟化软件的设置中,我们一般设置一个虚拟机对应一个虚拟CPU。

  而VMCS在使用时要与逻辑CPU绑定,一个逻辑CPU在任意的一个时间点都只能绑定一个VMCS,而VMCS在不同的时刻是可以和不同的逻辑CPU绑定的。

  VMCS用来绑定和解除绑定的命令分别是VMPTRLD和VMCLEAR,而用来对VMCS数据域进行读写的指令分别为VMREAD和VMWRITE。除此之外还有一条VMPTRST的指令,是指将当前的VMCS状态值存储到一个指定的内存空间。

  最后我们来谈一谈VMCS结构,看一看这个4KB大小的内存空间里都有些什么。

  在偏移0处是VMCS版本标识,偏移4处是VMX失败指示,这里将存放因VM-Exit执行不成功而产生的VMX失败原因,而我们下面要详细介绍的是在偏移8处的VMCS数据域。我们可以把这个数据域分成三部分:状态区域,控制区域和VM退出信息区域。

  客户机状态域是用来保存非根模式VCPU运行状态的,当发生VM-Exit时,VCPU的当前运行状态将写入客户机状态域(并非全部,另有一部分为VMM控制的软件部分,下同),而当VM-Entry发生时,CPU会将客户机状态域中保存的状态加载到自己身上从而保证顺利地切换到非根操作模式。而宿主机状态域则用来保存在根操作模式下CPU的运行状态,它仅仅在发生VM-Exit时将状态值写入CPU中,而在VMEntry发生时不进行保存操作。

  控制区域中VM-Entry控制域和VM-Exit控制域是对VM-Entry和VM-Exit操作的具体行为进行控制规定的地方,如VM-Entry控制域中的MSR加载、事件注入控制和VM-Exit控制域中的主机地址空间等,而VM执行控制域的作用是控制VM-Exit操作发生时的行为,比如某些敏感指令、异常和中断是否产生VM-Exit操作,也就是说只要是在这个控制域里列明的指令,都是可根据实际情况进行VM-Exit操作的开启和关闭操作的。当然没有写入控制域的一些指令也会产生VM-Exit操作,那些指令可以称之为无条件VM-Exit指令,凡是产生VM-Exit操作的指令都会由VMM来模拟完成。

  VM-Exit信息域比较简单,存放的是VM-Exit产生的原因和具体的分类细化指标。

  综述

  本期我们所讲的就是x86硬件CPU虚拟化中的基本概念和流程控制。简而言之,虚拟化环境无非就是VCPU的建立、运行、退出到再运行的这样一个循环的过程,而类似VMX ON这些指令就像是在各个环节的节点进行盖章确认操作。VCPU的建立和退出主要和VMEntry和VM-Exit操作相对应,而VCPU的日常运行我们并不需要关心,相关的程序会进行合理调度,VCPU的再运行则会在操作方式和指令上有一些变化。

  最后我们再综合回顾一下英特尔平台的CPU硬件虚拟化的基础过程。首先执行VMX ON指令进行VMX根操作模式,然后初始化VCPU相关信息,VMCS也进行了相应域的初始化设定,通过VMLAUNCH或者VM RESUME指令产生VM-Entry,进入这个状态后,处理器会作以下操作,首先执行基本检查确保VM-Entry可以开始;然后对宿主机状态域和客户机状态域进行有效性检查,并加载客户机状态域中的状态值;再根据VM-Entry控制域中的相关要求进行相关操作。如果一切正常就把执行环境切换到客户机上,如果失败则会在信息域上注明。进入客户机环境后,大部分的操作都可以自行完成而不用切换到根模式中来,这就是VCPU的运行过程。

  如果在此过程中,客户机程序因为特权级不够,访问敏感资源或者产生中断就都需要VCPU进行退出处理,也就是要进行VM-Exit处理(这时又分成两种情况,有一部分产生VM-Exit的指令是可通过VM执行控制域进行开关控制的,而还有一部分指令是无论如何都会产生VM-Exit的,只要产生VM-Exit的,都需要进行先陷入后模拟的过程),这时CPU先记录退出原因到VMCS的信息域中,然后将当前VCPU的运行状态保存到客户机状态域中,其它相关信息如MSR类信息也可能按照设置写入相关区域。接下来根据VM-Exit控制域和宿主机状态域中的设置,将MSR相关设定和宿主机状态域中的内容加载到CPU相应寄存器中。至此,CPU又切换回了VMX根操作模式,下一步将转由VMM来处理VM-Exit了,处理完成后又是下一个VM-Entry,也即是VCPU的再运行,如果VCPU还在同一个CPU上运行的话(也即不需要VMCS针对不同的CPU进行解除再绑定的操作),就可以用执行效率更高的VMRESUME(相比VMLAUNCH)指令来实现VM-Entry,如此循环即是一个完整的虚拟化流程。当然,真正的x86 CPU硬件虚拟化设计远比今天描述的要复杂得多,感兴趣的读者可以进一步深入研究。

你可能感兴趣的:(linux,操作基础,虚拟化)