MCPLive > 杂志文章 > x86硬件辅助虚拟化之迷

x86硬件辅助虚拟化之迷

2010-06-04hjcbug《微型计算机》2010年5月上

传统的软件CPU虚拟化模式

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

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

在传统的CPU虚拟化模型中,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 Binary Translation,常简称DBT)技术和半虚拟化中的修改客户操作系统源码的技术都可以对付这些不友好的指令(下文我们仅以动态二进制翻译作为案例进行讲解)。


图4 软件完全虚拟化的Ring架构示意图

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

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


图5 二进制代码翻译工作流程

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


图6 一个优化二进制翻译操作包括一个代码缓存和一个代码缓存管理器

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

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


图7 系统调用过程中申请内存资源的流程示意图

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

◎物理机器的系统调用需要242周期;
◎在Ring1层级上,一个32位客户操作系统上运行一个二进制转译的系统调用需要2308周期。

可以看出它浪费的周期和原生物理电脑相比还是相当大的,这在一定程度上阻碍了虚拟化的进一步性能提升,不过在硬件辅助虚拟化功能越来越成熟的今天,这个局面已经得到了有效的改善。

基于软件的二进制翻译,可以分为三类:解释执行、静态翻译和动态翻译。

解释执行对源处理器代码中的每条指令实时解释执行,系统不保存也不缓存解释过的指令,不需要用户干涉,也不进行任何优化.解释器相对容易开发,比较容易与老的体系结构高度兼容,但代码执行效率很差。

静态翻译是在源处理器代码执行之前对其进行翻译,将源机器上的二进制可执行程序文件A完全翻译成目标机器上的二进制可执行程序文件B,然后在目标机上执行程序B。一次翻译的结果可以多次使用。静态翻译器离线翻译程序,有足够的时间进行更完整细致的优化,代码执行效率较高。然而,静态翻译器可能无法完整地翻译一个程序,因而需要依赖解释器的支持;而且静态翻译器需要终端用户的参与,这给用户使用造成了很大不便。

动态翻译则在程序运行时对执行到的片断进行翻译,它克服了静态翻译的一些缺点—比如,由于不能知道控制流中某点的寄存器或内存的值而不能实现代码挖掘。动态翻译还可以解决大部分实际情况中的自修改代码问题,而这在静态翻译是不可能的;动态翻译可以利用执行时的动态信息来发掘静态编译器所不能发现的优化机会;动态翻译器对用户可以做到完全透明,无需用户干预。

分享到:

用户评论

用户名:

密码: