原文链接:https://www.usenix.org/system/files/login/articles/105498-Revelle.pdf
作者:Don Revelle / BDomne 译
译者注:本文为虚拟化技术的简单理论介绍,原文于 11 年发表,这期间技术也一直在不断发展,因此内容方面读者可适当斟酌。另外,翻译有误的地方还望各位加以斧正。
Hypervisor 和虚拟化技术正引领着云计算、集群以及高可用方案的发展。作为主流的虚拟平台,x86 架构处理器虽然在实现上存在一定挑战,但还是出现了许多成熟的解决方案,本文将简要探讨这些解决方案背后的原理和具体实现。
这其中,VMware、Hyper-V、XEN、KVM 和其它基于 Linux 的 hypervisor 是不断增多的实现方案中比较有代表性的,就目前而言,大多数的 hypervisor 无外乎采用二进制转译技术
、半虚拟化技术
或 基于 CPU 的全虚拟化技术
来实现。
x86 架构下虚拟化的主要挑战是如何进行有效的操作系统隔离以及降低虚拟化的开销。一般来说操作系统都要求在最高的权限下执行,以支持自身的内部服务并向用户进程提供相应服务。为了能使多个操作系统共享 CPU,需要引入一套中间协议和特权管理机制。 如果没有这部分中间层的支持,那么不论是 CPU 的时序分割还是系统资源的分配,任何试图运行多个操作系统的行为都将降低计算机的整体性能。
虚拟化的其它关键问题还包括执行速度、安全性、内存管理、复用性以及诸如网卡之类的设备隔离。
而 “Popek and Goldberg” 法则[1,2]定义了一套高效的处理器虚拟化规范,该法则提出了特权指令的理想模型和预期效果。在理想情况下,指令都应该以预设的方式来执行,而不必关心当前所处的权限级别,并且运行中出现的任何错误都应该被系统捕获处理。但并非所有 x86 架构的处理器指令集都符合 “Popek and Goldberg” 法则,因此必须借助中间层来处理那部分不合乎规范的指令。
Hypervisor 借助高效且复杂的算法来管理多个操作系统,它是一个独立的软件抽象层,需要以低开销运行,并能始终掌控整台机器的管理权限,它将用于确保多操作系统共同工作时实现有效的分离和边界限制。
我们知道 x86 处理器使用 ring0~ring3 的权限级别划分,通过获得 ring0 权限,即最高权限,hypervisor 可以管理所有的系统资源。当然,如果 CPU 不支持虚拟化,那么其上运行的系统会相应降到低权限级别。x86 处理器的权限保护机制使得完全隔离 hypervisor 抽象层成为可能,通常 hypervisor 也被称为虚拟机监控器(VMM,virtual-machine monitor),在本文中这两个术语可以交换使用。
另一方面,x86 处理器依赖查表来进行系统运行时的管理,诸如中断描述向量和内存段描述之类的全局表结构就是例子,对这些表的访问控制显然是 VMM 必须实现的。在多操作系统虚拟化环境中,这些处理器控制表需要由 hypervisor 维护,因此必须模拟每个系统的上下文。简单来说,hypervisor 扮演的角色就是管理那些无法在多系统环境下共享的处理器控制表及相关资源。
a) Bochs 模拟器
首先介绍 Bochs [9],它是一款模拟 CPU 和各种芯片组的软件,实现了全部的处理器指令并能模拟物理 CPU 循环进行一系列的取指、分析和执行操作。Bochs 通过调用其内部函数来模拟执行所有的原指令,不会直接涉及 CPU 层面,它的模拟引擎运行在用户态,并通过分配的内存空间来模拟物理地址空间。
此类转译在执行速度方面存在欠缺,在内存访问、操作码解析以及模拟指令执行过程中会产生额外的开销,致使模拟单条指令需要执行多条实际的处理器指令。Bochs 只是一个简单的虚拟化案例。
b) 转译算法与直接 CPU 执行
而诸如 VMware ESX Server [10]和 QEMU [11,12]等都用到了更优的转译方式,较之简单的解释型模拟如 Bochs 具有极大的性能优势,其基本思想就是将原指令转译为可直接在目标处理器上执行的指令。
二进制转译是不支持虚拟化的处理器所采用的中间方案,如前所述,x86 指令集并不满足 “Popek and Goldberg” 规范,有小部分处理器指令不适合进行虚拟化。在转译过程中会使用相应指令来替换这些有问题的指令,进而模拟原指令的操作。
这里给出一个简单的例子来帮助理解:在取指和分析阶段,转译器遇到了 CLI 指令。CLI 是一个特权指令,用于关闭 x86 CPU 上的中断。转译器会将其替换为针对虚拟的抽象 CPU 上的关闭中断指令,而不是对真实的 CPU 执行此操作。
那些已转译的指令会被缓存到用于直接在 CPU 上执行的基本块中,基本块的最后均存在一条返回 hypervisor 的指令。指令块在执行时产生的任何 CPU 异常或错误都会强制执行流程返回到 hypervisor 中,对于安全性和隔离性来说这是至关重要的,虚拟操作系统不应该有机会获得 CPU 的控制权,除非 hypervisor 自身因存在 bug 而能被绕过。
需要注意的是,VMware 实现的是非常复杂的自适应二进制转译算法[3]。此外,其 VMM 还具有便于大规模部署和管理机器的功能,如虚拟机迁移以及内存管理。较之作为用户态进程的 QEMU,ESX 则是在内核态下实现的,这使得它的 hypervisor 能完整操控整个处理器。
c) XEN 和半虚拟化
接着来看 XEN,它的半虚拟化技术[4,8]为替换 CPU 特权操作提供了特殊的软件服务接口,不过要想使用这些接口必须对操作系统进行专门的修改。
为了做到这一点,开发人员必须修改高度复杂的内核程序,并将其中的特权指令操作替换为对 XEN 接口的调用,以便 XEN 能在隔离的地址空间中模拟这些操作。在针对 XEN 接口完成重新编译后,会创建一个可直接运行的操作系统,再经过一些设置后,XEN 就可以对该操作系统进行加载和调度了,进而可以直接控制处理器。
在半虚拟化系统中,将基于中断来请求 XEN 操作,此过程称为 hypercall 调用。当发生 hypercall 调用时,CPU 会将控制权转交给 hypervisor,以完成请求的特权操作。这类似于系统调用,只不过请求是由内核发给 hypervisor 的。这些特权操作存在于只能由 XEN 访问的地址空间中,并模拟标准 x86 处理器中的内核/进程地址空间方式进行划分。
Hypercall 包括创建内存空间请求、页表操作请求以及其它虚拟化操作请求。另外,诸如缓存对象分配和线程等没有被虚拟化,但设备和页表这些是被虚拟化的。完整的 hypercall 列表可在 xen.h 头文件中查看。
XEN 是一个独立的基础内核,由于它是系统管理者且位于 ring0 层,因此拥有最终的处理器控制权,其上相应的 guest 操作系统则以较低级别的权限运行。当 guest 系统在处理器上执行时,任何处理器异常或错误都会被 hypervisor 捕获并处理,从而确保了虚拟系统的隔离性及安全性。为了提升效率,XEN 允许 Linux 内核绕过 XEN 层直接处理 CPU 上的系统调用(0x80h),这被称之为快速陷入处理例程。
此外,XEN 上的 guest 系统具有与独立操作系统不同的启动项和引导过程,而且也没有可用于查询系统内存大小等内容的 BIOS。相对的,XEN 提供了一个映射到 guest 系统地址空间的特殊表结构来作为替代,这是一个称为 “start_info” 的 C 结构体,在 xen.h 头文件中有该结构体的定义。
半虚拟化通常被认为具有非常好的性能,其主要缺点是必须修改操作系统的源码,这给闭源系统带来了问题,例如微软的产品线。流行的开源操作系统,如 Linux 和某些版本的 BSD 内核已经成功实现了半虚拟化,可以在无需 CPU 虚拟化支持的情况下运行 XEN。
d) CPU 支持的全虚拟化
而 AMD、Intel 和其它公司目前直接在处理器中内嵌了虚拟化属性,这样做的好处是,任何基于 x86 架构的系统都可以直接在虚拟环境中执行,而无需进行二进制转译或源码修改。AMD 称其虚拟化方案为 AMD-V,Intel 的则称为 VT(Virtualization Technology)。
AMD 和 Intel 的虚拟化芯片组支持 guest 系统的概念并专门为 hypervisor 新增了一层额外的特权模式。hypervisor 拥有控制机器的完整执行权限,而 guest 系统无需任何修改即可在其虚拟的环境中运行。从 guest 系统的角度来看,原先的权限级别并没有发生改变,即内核态和用户态的 ring0/3 模式维持不变,它也并不知道自身是作为 guest 端来运行的。
另一方面,AMD 和 Intel 都使用表来作为虚拟机定义、状态改变以及运行时态跟踪的数据结构,即 VMCB(AMD [5])和 VMCS(Intel [6]),此类数据结构存储了 guest 虚拟机的配置细节,如机器控制位和处理器寄存器的设置。这些数据结构有点大,但作为学习还是非常值得借鉴的,其中揭示了许多复杂的关于 CPU 如何看待虚拟机的细节和 VMM 管理 guest 虚拟机的其它信息。
Hypervisor 还会借助 VMCB/VMCS 来定义 guest 系统执行时的监控事件,事件对应于可被拦截的特定处理器状态,例如尝试访问处理器控制寄存器。VMCB/VMCS 数据结构仅会被映射到 hypervisor 可访问的内存页中,guest 系统不允许访问这些表,否则将违反隔离性限制。
监控事件的触发被称为 VM-EXIT(virtual-machine exit),当发生这种情况时,CPU 会将当前执行状态保存在 VMCB 或 VMCS 中,而后返回 hypervisor 上下文并处理导致 guest 系统退出的事件。这里,VMCB/VMCS 表的地址存储于特定的寄存器中。
此外,当 guest 系统在执行过程中出现错误、异常或其它需要进行模拟操作的情况时,都将强制触发 VM-EXIT 并切换到 hypervisor 的上下文中,这和正常运行于 x86 处理器上的操作系统所使用的异常/陷入机制有点类似。
e) 基于 Linux 内核的 KVM
我们再看下 KVM,它是一款非常流行的全虚拟化 hypervisor,作为 Linux 内核的集成模块,它将虚拟化功能直接带到了 Linux 宿主系统。KVM 不是完全独立的,它需要借助 Linux 宿主的内核子系统。KVM 的实现需要 CPU 的虚拟化支持,同时还需要 QEMU 来作为后端。 对虚拟设备的 I/O 请求会被 KVM 捕获,而后 QEMU 将在 KVM 的控制下为 guest 系统提供设备模拟操作。
需要说明一点,XEN、VMware、KVM 和 Hyper-V 均适用于支持虚拟化扩展的 CPU。
最后我们来了解 IOMMU,支持 IOMMU(I/O Memory Management Unit)技术的芯片组正成为 x86_64 架构的主流,它使得 hypervisor 可以管理设备的 DMA(direct memory access)操作。在独立系统环境中,设备可以访问任意的系统地址空间,此时系统和设备看到的都是单一的系统地址空间。但在多系统虚拟环境中,hypervisor 将会定义多个系统地址空间来供不同的 guest 使用,然而设备看到的仍是单一的系统地址空间。IOMMU 的作用是为设备创建虚拟的系统地址空间,并与 hypervisor 定义的空间相关联以限制设备可寻址的地址空间范围。
IOMMU 和系统总线结构位于同一层级,因此可以确认设备源并拦截内存操作请求,通过查询其内部的 IOMMU 页表来判断是否允许请求,并对允许请求的地址进行转换。IOMMU 强大的功能允许 hypervisor 将设备分配给特定的 guest 系统,并将设备可访问的内存地址范围限制在相应的 guest 地址空间中。IOMMU 的这种隔离性和映射功能可被用于 PCI-Passthrough。PCI-Passthrough 允许 guest 系统直接访问设备,而 guest 系统不会意识到它正在被 IOMMU 重定向且不具有修改 IOMMU 芯片的能力。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2018-3-16 11:10
被BDomne编辑
,原因: