前言

本文将会在P2的基础上,对云计算中的重要组成部分——计算虚拟化,进行介绍。

计算虚拟化概述

定义:计算虚拟化通过虚拟化软件层(即Hypervisor或VMM),将物理服务器的硬件资源与上层应用进行解耦,形成统一的资源池,将资源弹性分配给逻辑隔离的虚拟机共享使用。

CPU虚拟化

CPU特权级别

在我们的计算机系统中,CPU被划分为几个特权级别,每个级别我们称之为Ring。通过划分Ring级别,可以对CPU进行访问控制。
2020-08-11-16-35-25

  • Ring0:OS内核,处于特权指令级别,安全等级最高,一般只有操作系统才能执行,是内核态;
  • Ring1/2:一般驱动程序,处于非特权指令级别;
  • Ring3:应用程序,处于非特权指令级别,是用户态;

CPU指令

说到特权级别,不得不提的一点是CPU指令。CPU指令即指挥机器工作的指示和命令,程序就是一系列按一定顺序排列的指令,执行程序的过程就是计算机的工作过程,而指令集就是CPU中用来计算和控制计算机系统的一套指令的集合。目前,CPU指令集分为两大阵营:RISC指令集CISC指令集

根据权限划分的不同,CPU指令又分为以下几种:
2020-08-11-16-36-18

  • 特权指令:可以对操作系统产生直接影响的指令,一般不直接提供给给用户使用,例如对系统资源进行管理与分配等;
  • 非特权指令:一般的用户指令,不会对操作系统产生直接影响;
  • 敏感指令:指的是操作特权资源的指令,如读写时钟、控制中断、修改内存页表、访问地址重定位系统,以及所有IO指令。个人觉得比较有意思的一点是,在RISC架构的主机中,所有敏感指令都属于特权指令,而在CISC架构中,敏感指令一部分属于特权指令,另一部分属于非特权指令。这将直接影响到虚拟化架构的划分。

CPU虚拟化实现方式

CPU虚拟化历史、架构

20世纪60年代中期,IBM在自家的大型机上实现了虚拟化技术,主要针对的是自家采用RISC架构的Power系列处理器。虚拟化后,GuestOS会运行在Ring1级别,我们称之为特权解除。此时,GuestOS并不知道自己运行于Hypervisor上,他认为自己是运行在真实硬件上的。所以GuestOS和HostOS一样,是可以在不经过Hypervisor的情况下,将非特权指令直接交由CPU执行的。那么特权指令呢?对于特权指令,Hypervisor会对它进行Trap→模拟后,再交由CPU执行。举个栗子,GuestOS执行了一条特权指令——关机,这条特权指令是敏感指令。Hypervisor捕获到了这条特权敏感指令以后,采取中断→Trap→模拟动作,实际上GuestOS关闭的是它虚拟机本身,而不会是物理主机,这样就保证了物理主机的安全性。

而对于CISC架构的主机来说,RISC的虚拟化方式可能会带来一场灾难,为什么呢?我们知道,在CISC架构里面,敏感指令有一部分是特权指令,另一部分是非特权指令。也就是说,CISC的非特权指令里面,包含有敏感指令。再举个栗子,某天GuestOS执行了一条非特权指令——kill掉一个进程,这条指令是敏感指令,将会直接交给CPU执行。如果这条指令kill掉的是Hypervisor的关键进程,Hypervisor因此宕掉,就会影响到上层的虚拟机。想象一下,生产环境中的Hypervisor出现问题,导致Hypervisor上的所有的业务虚拟机受到牵连,导致业务中断,岂不是会造成重大的损失?

针对以上问题,1999年,VMware推出了针对x86架构主机的虚拟化技术,即全虚拟化方案。

全虚拟化

全虚拟化场景下,Hypervisor会主动扫描、捕获、翻译来自GuestOS的所有二进制代码,这个过程我们称之为二进制翻译(BT,Binary Translation)。当发现敏感指令和特权指令时,对指令进行二进制翻译后再交由CPU安全执行。然而,这种频繁的动作会加重Hypervisor的工作负担,消耗大量资源,导致性能下降,这是全虚拟化的一大短板。全虚拟化的代表性产品是VMware ESXi、KVM。

半虚拟化(准虚拟化)

半虚拟化场景下,通过修改GuestOS,令GuestOS得知自己正处于Hypervisor之上。修改后的GuestOS会主动将非特权指令中的敏感指令进行替换,使敏感指令只能够作用于其虚拟机本身,从而防止对HostOS造成影响。此外,GuestOS还会通过Hypercall接口,主动将特权指令发送给Hypervisor,让Hypervisor对来自GuestOS的特权指令进行Trap→模拟后,交由CPU执行。半虚拟化大大减轻了Hypervisor的压力,相比于全虚拟化,半虚拟化性能更高。修改GuestOS的方法通常是在GuestOS中安装半虚拟化驱动。例如,VMware ESXi在GuestOS中部署VMtools、Citrix Xen在GuestOS中部署PV Drivers、QEMU-KVM通过安装virtio驱动,实现半虚拟化。QEMU-KVM半虚拟化会在后续的文章重点介绍。
2020-08-11-18-50-17

硬件辅助虚拟化

目前,硬件辅助虚拟化分为两大阵营:Intel VT-x和AMD-v(SVM),均受到主流的虚拟化产品的支持。
以Intel VT-x为例,在硬件辅助虚拟化中,CPU被划分为root(根)模式和none root(非根)模式。虚拟化后,Ring0~3处于none root模式,而Hypervisor处于root模式。也就是说,GuestOS会运行在Ring0级别,这意味着GuestOS执行特权指令时,无需再经过Hypervisor的Hypercall接口进行Trap→模拟。而此时,由于采用了硬件辅助虚拟化技术,CPU能够明确区分来自GuestOS的特权指令和非特权指令,当CPU捕获到来自GuestOS的特权指令和敏感指令时,通过VMCALL调用Hypervisor,Hypervisor令GuestOS自动挂起并切换到Root模式,通过CPU执行特权指令和敏感指令,该过程叫VM Exit。为了让GuestOS执行非特权指令(不包含敏感指令),Hypervisor可以调用VMLaunch或VMResume指令切换到Non-root模式,将GuestOS的指令交由CPU执行,该过程叫VM Entry。AMD-v(SVM)提供的硬件辅助虚拟化功能与Intel VT-x大多相似,但是名称上可能有所出入,此处不作过多介绍。
硬件辅助虚拟化既解决了全虚拟化的系统开销与性能损耗,也避免了半虚拟化下修改GuestOS的兼容性问题,进一步解放了GuestOS。
2020-08-11-18-49-47

内存虚拟化

术语和概念

  • **HPA(Host Physical Address):**主机物理地址
  • **HVA(Host Virtual Address):**主机虚拟地址
  • **GPA(Guest Physical Address):**虚拟机物理地址
  • **GVA(Guest Virtual Address):**虚拟机虚拟地址

内存使用原则

内存使用时,必须遵循以下原则:

  • 使用内存的地址必定从0开始
  • 使用内存的地址必定是连续的

OS内核使用内存是从0开始的。一个应用进程使用内存时,也必须是从0开始的。但是多个应用进程需要使用内存时,而内存却只有一个地址0,怎么办呢?

其实,在一个OS里面,每个应用进程都有自己的一小段内存空间,即虚拟地址空间,由OS内核维持。而应用进程所使用的内存地址,我们称之为HVA。其与HPA保持着映射关系,保证每个应用进程都能正常使用内存,如图:
2020-08-12-13-43-39
通过以上内容我们可以得出结论:一个OS需要使用内存,必须经过物理地址和虚拟地址。

内存虚拟化实现方式

和物理主机的OS一样,虚拟机的GuestOS需要使用内存,也必须经过物理地址和虚拟地址。而虚拟机的本质是HostOS上的一个进程,在虚拟化环境下,如何解决虚拟机的内存使用问题呢?

这个时候就需要Hypervisor的介入了。Hypervisor在虚拟机和主机之间引入了一层新的地址——GPA。Hypervisor将主机非连续的地址映射成了连续的虚拟机物理地址,供虚拟机使用,从而满足了虚拟机使用内存的条件,如图:
2020-08-12-14-14-11

I/O虚拟化

I/O指的是输入/输出设备,相信大家都知道吧!一般来说,我们的硬盘、网卡等设备都属于I/O设备。I/O虚拟化将会在下一篇文章重点介绍。

IO模拟(IO全虚拟化)

完全使用Hypervisor来模拟虚拟机的I/O请求,由Hypervisor主动捕获虚拟机的IO操作,然后将捕获的IO操作转发给硬件。这种虚拟化方式不需要对OS和驱动程序进行修改,因此这种方式对于多种虚拟化技术的可移植性和兼容性比较好,但是需要在GuestOS和Hypervisor之间频繁进行交互,性能很差(例如模拟键盘鼠标等常用硬件,通过焦点捕获,焦点被哪个主机捕获就被哪个主机使用);

IO半虚拟化

在GuestOS中部署一个前端驱动(也就是上文所讲的半虚拟化驱动),通过前端驱动,将GuestOS的IO请求主动发送给Hypervisor的后端驱动,适用于硬盘和网卡,性能高;

IO-through

IO设备直通,也就是裸设备映射,直接将物理设备(如硬盘、网卡等)分配给虚拟机,但是需要硬件支持(在Xen下由Dom0分配,但是访问使用直接使用,不经过Dom0,需要硬件支持)。