Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1508636
  • 博文数量: 487
  • 博客积分: 161
  • 博客等级: 入伍新兵
  • 技术积分: 5064
  • 用 户 组: 普通用户
  • 注册时间: 2011-07-01 07:37
个人简介

只有偏执狂才能生存

文章分类

全部博文(487)

文章存档

2016年(10)

2015年(111)

2014年(66)

2013年(272)

2012年(28)

分类: LINUX

2013-10-16 13:26:33

在 x86 上运行虚拟化的具体问题

虚拟化技术的一个重要要求之一就是通过虚拟机监控软件 (VMM) 运行的操作系统 ( 我们称之为客操作系统 ) 在运行时,其运行效果应该和直接在裸机上运行操作系统是一致的,即客操作系统不应该感觉到虚拟化技术的存在。 IBM 的主机采用 “trap-and-emulate” 的方法来实现 CPU 的虚拟化,即一般的指令直接运行,那些可能改变主机全局行为的“敏感指令”则会被截取,交由 VMM 通过仿真来完成其功能。 “trap-and-emulate” 被认为是实现 CPU 虚拟化的最好方法。 作者 Popek 和 GoldBerg 在 1974 年的一篇文章提出了 “classic virtualization” 的概念,该概念认为能够比较完好的实现 “trap-and-emulate”的硬件平台才是 “classically virtualizable” 的平台,即如果该平台上的 VMM 如果能够比较容易的捕获敏感指令,我们才认为该平台是典型的比较易于虚拟化的平台。 X86 平台由于其迅速的无处不在的统治力,在最初的设计上并没太多考虑虚拟化的要求,根据 GoldBerg 的标准,传统的 x86 属于非 “classically virtualizable”的平台。 Scott Robin 在 2000 年的文章以 Intel Pentium 为例详细介绍了 x86 平台在支持虚拟化方面存在的问题。GoldBerg 的标准并不排除用其他方法解决“trap-and-emulate”的问题, 如 X86 上的虚拟化提供商 VMware 和 XenSource 分别采用“Binary Translation” 和“Para-virtualization”来解决敏感指令问题。其中“Binary Translation”技术提前发现敏感的指令并通过插入断点来截获之,交由 VMM 来解释执行。 “Para-virtualization” 方法则直接修改客操作系统代码,修改其特权级,并将敏感指令改为 Trap Call 直接通知 VMM 来处理。 这两种在软件上处心积虑的方法会导致软件实现的复杂性,限制了 VMM 性能的提升空间,“Para-virtualization” 更是没法施用在 Windows 等私有操作系统上。

在说明 X86 平台对虚拟化的支持能力之前,我们有必要解释一下特权级的概念。 x86 硬件支持 4 个特权级 (Ring),一般内核运行在 Ring 0, 用户应用运行在 Ring 3, 更小的 Ring 有比更高的 Ring 能访问更多的系统全局资源,即更高的特权。 有些指令只能在 Ring 0 才能正确执行,如 LGDT、LMSW 指令,我们称之为特权指令;另外有些指令可以在 Ring 3 正确执行,如 SGDT、 SMSW、PUSHF/POPF,我们称之为非特权指令。

在传统的 X86 平台上支持虚拟化上存在如下问题 :

X86 指令集中存在 17 条敏感的非特权指令

“非特权指令”表明这些指令可以在 x86 的 ring 3 执行, 而 “敏感性” 说明 VMM 是不可以轻易让客操作系统执行这些指令的。 这 17 条指令在客操作系统上的执行或者会导致系统全局状态的破坏,如 POPF 指令,或者会导致客操作系统逻辑上的问题,如 SMSW 等读系统状态或控制寄存器的指令。 传统的 X86 没法捕获这些敏感的非特权指令

“Ring deprivileging” 带来的问题

除了那 17 条敏感的非特权指令,其他敏感的指令都是敏感的特权指令。 在 x86 虚拟化环境,VMM 需要对系统资源进行统一的控制,所以其必然要占据最高的特权级,即 Ring 0, 所以为了捕获特权指令,在传统 x86 上一个直接可行的方法是 “Ring deprivileging”, 如将客操作系统内核的特权级从 Ring 0 改为 Ring 1 或 Ring 3, 即 “消除” 客内核的特权,以低于 VMM 所在的 Ring 0, 从而让 VMM 捕获敏感的特权指令。 然而,采用 “Ring deprivileging” 又会带来如下问题 :

  • “Ring aliasing”。 该问题是指客操作系统可通过读取 cs,ss 段寄存器的值而知道其自身已经不处于 Ring 0, 这一结果理论上可以让客操作系统改变自己的行为,违背了虚拟化应该对客操作系统透明的原则。
  • “Ring compression”。 无论采用哪些特权级,传统 x86 都需通过分段或分页的方法来实现地址空间间的访问控制。然而传统 x86 上所有 64 位的操作系统都没使用分段,另外 x86 上的分页不区分 Ring 0、Ring 1 和 Ring 2, 也就是说 x86 上没法通过分段或分页机制来阻止 Ring 1 上的 64 位客操作系统内核来访问 VMM 的地址空间。 所以,客内核被迫采用 Ring 3, 也就是和客操作系统上的应用相同的运行级,这当然就会导致新的问题。
  • “Adverse Impact on Guest system calls”。 该问题和用 sysenter/sysexit 指令实现的系统调用机制有关。Sysenter 不同于 “int 0x80”, 其不需要经过异常表的控制而明确切换到 Ring 0, 所以 sysenter 是 x86 平台上性能更高的系统调用实现方式。 但在虚拟化情况下 sysenter/sysexit 带来的问题是, sysenter 让客操作系统上的应用进入 Ring 0 而不是客内核的 Ring 1, sysexit 在 Ring 1 上执行直接导致 fault, 解决这一问题的方法只能是让 VMM 来仿真 sysenter/sysexit, 或者让 VMM 向客操作系统屏蔽掉虚拟 CPU 的 sysenter 能力,无论哪种做法,都会导致客操作系统上应用性能下降。

“Address-space Compression”问题

传统 x86 上的操作系统如 Linux 都采用统一的线性地址空间,通过页表和特权级来控制用户进程对内核地址区域的访问,用户进程执行系统调用,只是改变自己的特权级,并不改变自己的地址空间,所有进程可以通过多级页表机制共享内核地址区域的内容。 那么在虚拟化环境下需要考虑的问题是, 是应该让 VMM 占据客操作系统地址空间的一部分,还是让其采用独立的地址空间? 采用前一方法对只有 4G 地址空间的 32 位客操作系统难一解决,另外要考虑如何防止客内核对 VMM 地址区域的访问,及如何保持对客操作系统的透明性。 采用后一种方法需要考虑如何快速实现地址空间切换,如何建构用于客操作系统和 VMM 间互相切换的控制结构 ( 类似于 IDT 和 GDT)。传统的 x86 似乎还没有很好的机制支持这个问题的解决 。

中断的虚拟化问题。

X86 操作系统的内核通过修改 EFLAGS 的 IF 位来控制外部中断的投放。 在虚拟化环境下 VMM 有诸多理由希望能统一的控制中断的投放, 然而通常情况下客操作系统对 EFLAGS.IF 的修改是很频繁的行为, 如果 VMM 通过捕获客操作系统对 EFLAGS.IF 的修改而获得对中断的控制权,显然代价过于高昂。另外一个方面是虚拟化的中断,VMM 该如何向客操作系统发起一个中断?该如何控制虚拟的中断被客操作系统投放的时间 ?

对特权资源的频繁访问问题

VMM 通过特权级控制来捕获客操作系统对特权资源的访问,在一般情况下不是一个问题,但对某些特权资源,如 APIC 的 TPR 寄存器,一方面客操作系统可能会频繁访问,另一方面 VMM 为了统一控制而又不得不截取之,导致的巨大性能开销是一个不得不严肃考虑的问题。

Intel 和 AMD 的解决方法

Intel 和 AMD 通过对其 X86 硬件架构进行扩展,解决了 X86 架构不能很好支持 “Classic Virtualization” 的问题,Intel 和 AMD 的这种 x86 虚拟化扩展分别称为 Intel VT-x 和 AMD-V( 或从代码的角度分别称为 VMX 和 SVM)。 我们认为,具有 Intel VT-x 或 AMD-V 能力的处理器都是 “classically virtualizable”的, 当然 Intel 和 AMD 不会满足于此,除了解决了前面提到的 x86 平台虚拟化存在的问题外,目前的 Intel 和 AMD 的处理器在 MMU 虚拟化和 IO 虚拟化方面都会提供相当的支持,并且在许多细节问题方面都会有所考虑。

Intel VT-x 和 AMD-V 提供的特征大多功能类似,但名称可能不一样,如 Intel VT-x 将用于存放虚拟机状态和控制信息的数据结构称为 VMCS, 而 AMD-V 称之为 VMCB; Intel VT-x 将 TLB 记录中用于标记 VM 地址空间的字段为 VPID, 而 AMD-V 称之为 ASID; Intel VT-x 将二级地址翻译称之为 EPT, AMD 则称为 NPT, 等等一些区别。 读者必须注意,尽管其相似性,Intel VT-x 和 AMD-V 在实现上对 VMM 而言是不兼容的,我们后面的介绍只限于 AMD-V。

KVM 全称 Kernel-based Virtual Machine, 即基于 Linux 内核的虚拟化技术, 精确的说,就是 KVM VMM 的核心功能是通过一个 Linux 内核模块实现的。 “基于 Linux 内核”是 KVM 在软件实现上不同于其他 VMM 实现的最重要特点, 使得 KVM 在实现上能获得如下好处 :

  • 利用 Linux 内核已有的功能和基础服务,减少不必要的重新开发。 如任务调度,物理内存管理,内存空间虚拟化,电源管理等功能,通常是一个 VMM 所必须具备的,但 KVM 可不必重新开发这些功能,直接使用 Linux 上已经相当成熟的技术。
  • 利用强大的 Linux 社区,吸引优秀的 Linux 内核程序员参与到 KVM 的开发中, 壮大 KVM 的群体, 这些程序员以及红帽等 Linux 社区背后的厂商,也乐于在 Linux 上发展一个成熟的 VMM 技术。
  • 可以长期享受 Linux 内核技术不断成熟和进步的好处,优化 KVM 的实现。 如 Linux 内核的 HugeTLBPage 技术可以用于削减 KVM 在虚拟机内存使用上的性能开销, eventfd 可以用于提升 KVM 内核执行路径和 Qemu-kvm 用户空间的交互效率。

KVM 在 VMM 的理论上属于硬件辅助的虚拟化技术, 即 KVM 需要利用 AMD-V 提供的虚拟化能力。AMD-V 让 KVM 上的虚拟机正常情况下运行在 “guest” 模式, 在执行敏感的的指令或行为时透明地切换到 “host” 模式,并在 “host”模式由 KVM VMM 的代码仿真那些敏感的指令或行为,完成后又回到“guest”模式由虚拟机运行其正常的代码。 KVM 的 VMM 代码实际上就是当虚拟机被捕获时才进入,执行仿真代码,然后又执行状态切换回到虚拟机代码直到其下一次被捕获,如此循环不断。 当然 KVM 的 VMM 在仿真复杂的行为时,可能需要用户空间的帮助, 所以 KVM 在此期间切换到用户空间。




 IA-32架构的CPU将CPU的权限分为4级:ring0到ring3,其中ring0优先级最高。CPU的指令分为特权指令和非特权指令。设置CPU状态、模式的操作全部是特权指令,修改CPU 控制器寄存器如EFLAGS、CR0、CR1、CR2、CR3的指令也为特权指令。所有的特权指令只能在ring 0 级才能调用。

       通常操作系统运行在ring 0,应用程序运行在ring 3。而在虚拟化环境下,VMM 要运行在ring0。所以在传统的IA-32 架构下,不得不修改操作系统内核,使其知晓VMM 的存在,并运行在ring1 中,应用程序仍然运行在ring3 中。这使得像windows 这种不开源的操作系统很难实现虚拟化(通过代码扫描和动态指令重写的方法解决)。

       Intel VT-X技术

       Intel VT-x 技术的诞生,简化了虚拟机的设计,并提高了VMM 对虚拟机的掌控灵活度和粒度。VT-x 是用在IA-32 体系结构上的,它对处理器进行了扩展,引入了两种新的CPU工作模式,VMX根模式和VMX非根模式,两者都可以支持所有四个等级。在VT-x 技术的支持下,VMM 和Domain0 运行在根模式下,其中VMM运行在ring0,Domain0 运行在ring1,应用程序运行在ring3。HVM Domain 运行在非根模式下,和通常的操作系统一样,内核运行在ring0,应用程序运行在ring3。两种模式可以相互转换,从根模式进入非根模式的称为VM 进入(VM entries),而反过来的称为VM 退出(VM exits)。VT-x 技术定义了虚拟机控制结构VMCS(Virtual Machine Control Structure),用来保存虚拟机的各种状态,控制VMX 非根操作的转换进出和处理器的行为。这个结构由指令VMPTRST,VMPTRLD,VMREAD,,VMWRITE 和VMCLEAR 来操作。VMM可以对每个VM 使用不同的VMCS,也可以同一个VM 内多个处理器使用不同的VMCS。




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

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

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


图11 支持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相比不会有任何变化,从而保证了原有软件和虚拟环境的正常运转。


图12 英特尔VCPU创建,运行和退出示意图

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

介绍完两种操作模式,下面我们就以英特尔平台为例来对新指令和虚拟机控制结构进行进一步介绍。

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


图13 每个VMCS对应一个虚拟CPU(假设每个虚拟机只用一个虚拟CPU)

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。


图14 VMCS的内部结构

而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退出信息区域(见表1)。


 表1

 状态区域

 客户机状态域
 宿主机状态域

 控制区域

 VM执行控制域
 VM-Exit控制域
 VM-Entry控制域

 VM退出信息区域

 VM-Exit信息域


客户机状态域是用来保存非根模式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产生的原因和具体的分类细化指标。



阅读(2083) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~