提起虚拟化,一个不得不提的公司或者产品是 VMWare,如果说虚拟化最早的原型可以追溯到上世纪 70 年代的 IBM 的 VM 的话,那么当前的虚拟化热潮却是由 VMWare 引领的。相信有很多人跟我一样,对虚拟化的认知是通过 VMWare 得到的。当我初次接触 VMWare 时,我非常惊讶,居然有这样的产品实现了这么 nice 的功能。VMWare 的成功除了契合了时代的变迁所催生出的虚拟化需求外,也得益于自身产品的优秀。VMWare 产品的简单,便捷,易于理解当然是其中非常重要的一个优势,但更重要的原因来自于 VMWare 创造性的解决了 x86 平台内在不支持虚拟化的难题。
x86 平台难以虚拟化的本质主要来自于 CPU 的虚拟化。众所周知,x86 处理器的指令有 4 个特权等级,分别是 Ring 0 ~ 3。正常情况下,application 工作在最低特权级,即 Ring 3,而 kernel 工作在最高特权级,也就是 Ring 0,因为有许多硬件操作必须要在该级别下才能完成。在虚拟化的情况下,也就是有多个 OS 同时运行的情况下,显然这多个 OS 不能同时运行于 Ring 0,因为 OS 需要运行的某些 Ring 0 特权指令将互相干扰。因此一般的解决方案是将虚拟化软件(通常称作 Virtual Machine Monitor,或 hypervisor)放在 Ring 0,而将运行在虚拟机里的 guest OS 放到 Ring 1 或 Ring 3 中。一个正常的硬件设计是,当这些原本应该运行在 Ring 0 级别的指令在非 Ring 0 的级别里被执行时,处理器报错,这样运行在 Ring 0 的 hypervisor 就能捕获该错误,从而做相应的处理(比如模拟或替换该指令)实现虚拟化,然而 x86 处理器有一些特权指令在 Ring 0 及非 Ring 0 下都能执行, 并且有不同的含义,这就使得运行在 Ring 0 级别里的 hypervisor 无法捕获该指令,而该指令的运行也于原本的意义不同。
对此 VMWare 的解决方案是 Binary Translation,也就是 hypervisor 提前扫描 guest OS 待运行的指令,发现有这种无法捕获或无法虚拟化的特权指令时,将其替换成相应的一系列可捕获的指令,从而实现 guest OS 的虚拟化。当然除了 CPU 的虚拟化外,内存,I/O 设备的虚拟化也不那么容易,不过是 CPU 虚拟化方式的不同决定了各解决方案的不同。VMWare 的这种解决方式最大的优点是 guest OS 无需修改就可以运行在 VMWare 的虚拟机上,当然这个优点是相对于后来出现的 Xen 而言的,这种虚拟化方式也称作 Full Virtualization。
VMWare 的 Full Virtualization 解决方案一个致命的缺点是性能上的瓶颈,因为 hypervisor 要在运行时扫描指令,分析并替换,这个消耗是客观无法去掉的。当然 VMWare 采取了很多方法来弥补这些缺陷,使得虚拟机的运行不至于慢的难以忍受,造成的结果就是实现相当复杂。因此在 VMWare 掀起了虚拟化的浪潮之后,许多想投入到这个领域中去的人开始想办法从其它角度来解决 x86 难以虚拟化的难题。
Xen 的出现以及 Paravirtualization
Xen 的开发人员就想出了一个巧妙的办法。Xen 最初始于剑桥大学的一个研究项目,他们的出发点是既然 x86 系统难以虚拟化,那么我就假定 guest OS 运行在一个类似 x86 的平台上,该平台由 Xen 来提供。具体点说就是 Xen 实现了一个类似微内核这样的系统,该系统运行在物理硬件平台上(Ring 0 级别),而 guest OS 运行在 Xen 上(Ring 1 级别),于 VMWare 不同的是,运行在 Xen 上的 guest OS 需要修改,因为 Xen 提供或虚拟的硬件环境已不再是 x86 平台,而是一个类 x86 平台,当然再上面的 application 不需要修改。这种虚拟化的方式称作 Paravirtualization。
这就意味着,guest 原本需要运行某些 Ring 0 级别的特权指令才能完成的任务,现在要修改为调用 Xen 提供的相应的指令(称作 hypercall,从实现方式来说类似于 system call)。显然开源的易于修改的 Linux 是最好的目标,这也是早期的 Xen 无法支持 Windows 系统的原因。然而 Xen 的架构还远不止与此,由于 Xen 运行在物理硬件与客户机操作系统之间,因此 Xen 的重要性不言而喻,稍有差错就可能导致所有的客户机崩溃,因此要求 Xen 的代码尽量简练,实际上是越少越好,而一个完整的 guest 运行所需要虚拟的硬件除了 CPU 外还有很多,例如内存,I/O 设备等等。显然如果 Xen 即支持底下的硬件设备,又要虚拟上面需要的硬件设备,那么 Xen 的实现无异于重写一个 Linux。Xen 的想法是 Xen 只支持最基本的硬件设备,也就是 CPU,内存,中断等,其它 I/O 设备由一个专门的 guest OS 来支持,而其余的 guest OS 要想访问该设备,需要通过 Xen,然后再传递到该特殊的 guest。Xen 为此将 guest 分为两个级别,一个是有访问 I/O 设备特权的特殊的 guest,称作 Domain0,其它的称作 DomainU。
因此对 Linux 来说,要想运行在 Xen 上,就必须要修改 Kernel 代码,并且根据 Linux 是运行在 Domain0 级别还是 DomainU 级别而修改的代码也不同。早期的 Xen 采用 Linux Kernel 2.6.18 作为基础,进行修改并实现了 Domain0 以及 DomainU 的功能。
然而技术的发展往往非人所料,由于虚拟化浪潮的热火朝天,以及 x86 平台难以虚拟化的本质,使得 Intel 与 AMD 这两大芯片商开始思考如何在处理器里添加功能克服原有的缺陷。于是在 2006 年,Intel VT-x 与 AMD-V 出现了。简单的说这两个 CPU 扩展就是在保持四个 Ring 特权级别的条件下,分出两个 forms,分别给客户机与 hypervisor 使用,由于每个 form 都有这四个 Ring 级别,因此客户机的特权指令的捕获就可以轻松实现了。硬件技术的出现很快就带动了软件技术的发展,很快有利用这种硬件虚拟化技术的软件出现了,没错,就是 KVM。
比起 Xen 来,KVM 的实现更加简洁而优雅,除了利用硬件的支持,KVM 还利用了现有的 Linux Kernel 的 CPU 调度等机制,因此 KVM 不需要像 Xen 那样重新实现一个复杂的 guest OS 的调度机制,这个任务交给 Linux Kernel 来完成,此时 guest OS 对 Linux Kernel 所在的 host 来说就是一个进程。此外 KVM 的实现方式也不需要修改 guest OS,因此 KVM 的出现很快引起了 Kernel 社区开发人员的兴趣,几乎是在最短的时间内,KVM 就进入了 Kernel,此时 Xen 仍然按照自己的开发模式在按部就班的进行着。