Background

CSP是上海交通大学软件学院的研究生课程,因为之前大四上的时候没好好上,特意重修了一遍,这里是重修时候课上的一些记录。

正文

虚拟化是一个非常有趣的技术,对于不同的人,听到这个词可能都会有不同的想法。因为虚拟化的概念实在是太常见了。在这里我们只讨论对操作系统的虚拟化,通俗来讲就是如何在一台机器上跑多个系统的那种虚拟化。这样的虚拟化在 wiki 上被称作 Hardware Virtulization,或者是叫做 Platform Virtualization。

这是一篇半科普的文章,并不是很严谨,有用词不慎还请指正。

虚拟化的分类

就算是把重点放在 Hardware Virtulization 上,也存在好多种实现的方式,这些方式都是从不同角度来进行虚拟化的。如果想要了解这些方法,首先要介绍下一个操作系统的体系结构。

Computer System Architecture
Computer System Architecture

上面的图比较形象地介绍了从底层硬件到操作系统的整体架构。操作系统和硬件之间的那一层叫做 Instruction Set Architecture (ISA)。这里就是指底层的那些指令,比如 load, store, call, return 等。其中有一些,是特权指令,只有运行在 ring 0 模式下的应用可以使用这些指令,也就是内核。这样的指令包括 hlt, load cr3 等。这样的指令,就是图中 3 所代表的 System ISA。而用户可以使用的指令,比如 call, return 这些,就是 4 所代表的 User ISA。两者共同组成了 ISA。

再上一层,是 ABI,也就是操作系统和库之间的那一层。它包括系统调用,和之前提到的 User ISA,也包括一些调用协定,比如调用方和被调用方各自保存哪些寄存器的值这样的。这一层对编译器的实现有很大的影响。

再往上一层就是广为人知的 API,作为一个只会使用 API 的程序员,了解下面的东西还真花了一些功夫,但 API 的概念想必早就已经深入人心了,这里就不再去介绍了。

Process and System VM
Process and System VM

那如果要实现虚拟化,可以从 ABI 和 API 两个层次来实现,这两种实现都要跟 ISA 打交道。上图说明了两种实现的结构,Process VM 可以实现在 Host OS 上运行其他 ISA 或 OS specific 的程序,这可以被当做是在 API 层次上实现了虚拟化。通常来说,Process VM 会有一个图中所示的 Runtime,Runtime 跟 Guest 代码是在一个进程里的,所有 Guest 代码的执行都会在其中。

Process VM
Process VM

这张图可以看出,其实 Process VM 的实现还是挺复杂的,其中最关键的是 Evaluation Engine,它会负责做代码的解释,或者是二进制的翻译。因此其实 Process VM 可以算是在 ABI 或者是 API 上的虚拟化,并不是纯粹只是在 API 层次上的虚拟化。除了 Evaluation Engine 之外,还有俩 emulator,分别来捕获和模拟 Host OS 的异常和系统调用。可以看到,Process VM 跟 Container 还是有比较大的差距的,最开始以为这两个是一个东西,但是现在看来不是这样的。起码这个 Process VM 比 Container 技术上要复杂一些的样子。 Process VM 的例子有 IA-32 EL, FX!32, DynamoRIO 等等,都是没有摸过的东西。

再回去看看 System VM,被大家熟知的 Xen 和 kvm 就是 System VM 范畴的。它主要的部分是 VMM,VMM 负责管理和保护硬件,同时让运行在它上面的操作系统以为自己在跟硬件打交道。这里还可以继续分为两类,一类是 Classic VM,一类是 Hosted VM。

Classic VM and Hosted VM
Classic VM and Hosted VM

两者的区别在于,Hosted VM 中,VMM 是运行在 OS 之上的,而 Classic VM 中的 VMM 则是直接运行在硬件上的。Xen 是典型的 Classic VM,而 kvm 因为是在 Linux Kernel 中的,因此是 Hosted VM。

介绍完虚拟化的分类,那已经知道了它们大概的逻辑架构是什么样的,那接下来会分析下,如果要实现一个虚拟化,都需要对哪些资源进行虚拟化,以及可以采取的方式都有哪些。

在虚拟化中,最主要的虚拟化是三个:CPU, Memory 和 IO,这也是最经典的三个概念。这里会先介绍 Xen,后面 kvm 相比 Xen 其实差不多,只是利用了硬件的支持使得实现更加简单了。

CPU 虚拟化

提到 CPU,首先就是特权级的问题,在没有虚拟化的时候,Kernel 运行在 ring 0 里,应用程序运行在 ring 3 里,但其实不止有这样两个特权级,因此 Xen 的做法是把 VMM 放在 ring 0,而 Guest OS 运行在 ring 1,应用还是在 ring 3。这样就可以使得只有 VMM 才能运行很多特权指令,而 Guest OS 很多指令在运行的时候会失败。

软件方式

不过失败会有很多种,有的会触发一个 fault,然后可以被 trap 到 VMM 中,而有的指令在不同特权级别下有不同的逻辑,所以会按照低特权级的逻辑执行,也有的指令会 silent fail,也就是毫无声息地就挂了。对于可以 trap 的指令,完全可以被 VMM 处理,而对于后面两者,没有很好的办法,这样的指令一共有17条。Xen 的方法是修改操作系统,对于不能被 trap 的指令用 hypercall 的方式 trap 到 VMM,而还有一种二进制翻译的技术,就是用可以被合理 trap 的指令来模拟这17条指令,这样的二进制翻译可以做代码级别的拼接,因此在处理循环等等的时候甚至比上面那种方式要快。VMWare 就是用这样的黑科技获得了很大的市场,也是它的核心技术之一。

Xen 提出了一种优化,就是能够在 Guest OS 里处理一部分系统调用,这样就不需要再在每次系统调用的时候 trap 到 VMM 处理。

硬件辅助

既然软件实现这么麻烦,还要修改 Guest OS,那为什么不让硬件来辅助进行虚拟化呢,Intel VT-x 就是干这个的。

VT-x 提出了两个 mode,就是 root mode 和 non-root mode。其中每个 mode 都有跟之前一样的特权级。这样 VM 都是放在 non-root mode 下的,而 VMM 是放在 root mode 下的。每当 Guest OS 触发了一个异常等等,都会由硬件来保证会被 trap 到 在 root mode 下的 VMM 中。这样就大大降低了虚拟化的实现难度,kvm 就是采用了 VT-x 的辅助,来进行的,因此如果 CPU 没有这个 feature,就不能用 kvm 来完成虚拟化。

Memory 虚拟化

内存虚拟化很关键。因为引入了虚拟机,所以原本由 Virtual Address(VA) 到 Physical Address(PA) 的转换,变成了 Guest Virtual Address(GVA) 到 Guest Physical Address(GPA),再到 Host Physical Address (HPA) 的转换。

那问题来了,cr3 寄存器只有一个,那就是说页表只能有一个啊,那怎么能有两个转换呢,于是就有了 Shadow Page Table(SPT) 来解决这样的问题。

Shadow Page Table

既然 cr3 寄存器只有一个,而转换有两个,那做一个折衷的选择,让 VMM 维护一个从 GVA 直接到 HPA 的页表,这样,在虚拟机中的进程执行的时候,只需要这一张页表就 OK 了。所以这样的设计意味着对于每一个 VM 中的每一个进程,都需要有一个 SPT 维护在 VMM 中。

Shadow Page Table(SPT)
Shadow Page Table(SPT)

这样的设想是可以解决问题的,但是会在实现上遇到一些坑。比方说,Guest OS 因为是不知道有 SPT 这样一个东西的,因此在它只会更新自己的页表,而管不到 VMM 中维护的 SPT,那怎么样才能使 VMM 能够知道 VM 在修改自己的页表呢,那方法就是 Guest Page Table(GPT) 会被置为可读不可写的,那这样每次写都会 trap 到 VMM 中,这个时候 VMM 再去更新 SPT。

地址翻译过程
地址翻译过程

而整个的地址翻译过程如图所示。首先,GVA 如果在 TLB 里 MISS 了,就会去 SPT 中找地址,如果还是没有,就会触发 Page Fault,然后会去 GPT 中,根据 GVA 先找到 GPA,然后再通过维护在 VMM 中的 PMAP,也就是从 GPA 到 HPA 的一张表,找到真正的 HPA,再放到 SPT 中。这只是简单的介绍,真正的比这个要复杂一些。

SPT 是可以解决地址转换的问题,但是有不少的缺点。首先就是在 SPT 找不到的时候,会遍历一次 GPT,很伤,还有就是切换进程,在加载 cr3 寄存器的时候会 Flush TLB,这样才能正确地 trap 到 SPT 或者被 VMM 中的逻辑处理。简单来说就是性能不好。

EPT/NPT

既然软件实现 GVA 到 GPA 再到 HPA 的转换这么麻烦,能不能让硬件来做这件事呢,那 EPT/NPT 就是做这件事的。EPT/NPT 暂时可以被理解为多了一个 cr3,因此 CPU 可以维护两个页表,一个用来做从 GVA 到 GPA 的转换,一个用来做 GPA 到 HPA 的转换,这样会少很多跟 VMM 的交互。不过在实际情况中,GPT 和 EPT 都有可能是多层的,每遍历 GPT 的一层都可能要遍历一次 EPT,如果 GPT 是 啊层,EPT 是 b 层,遍历是 O(mn) 的。

IO 虚拟化

软件方式

IO 是最后一个比较重要的概念。最简单的 IO 虚拟化方案,是在每次 IO 操作的时候,都 trap 到 VMM 中处理。但是这样的效率太低了。Xen 采取的方法是引入一个特权 VM: Domain 0。这里也存在两种实现,一种是使用 qemu,VM 中的 IO 请求都会通过一个驱动转发给 Domain 0 的 qemu,qemu 会负责具体的逻辑。

还有一种是把驱动分成前端后端两种,前端在 Guest VM 中,后端在 Domain 0 里,IO 请求在 Guest OS 中的前端驱动里,通过 IO Ring 的方式共享给后端,后端会发给真正的硬件驱动来完成 IO 请求。

SR-IOV & IOMMU 硬件支持

SR-IOV 是硬件支持的设备虚拟化,对它的了解并不深,最早支持 SR-IOV 的设备是网卡,它会把硬件分为多个 Virtual Function(VF),VF 可以被分给不同 VM 来使用。

IOMMU 还不懂,之后再来补充- -

PS: SOSP’17 竟然在上海举办,给 IPADS 的神牛们跪了。

Reference

  1. Smith, James E., and Ravi Nair. “The architecture of virtual machines.” Computer 38.5 (2005): 32-38.
  2. Process VM - NYU Computer Science

评论