虚拟化技术概述:从基本概念到实现细节
虚拟化技术概述:从基本概念到实现细节
虚拟化技术是现代云计算和数据中心基础设施的核心技术之一,它允许在单个物理服务器上运行多个虚拟机,从而提高资源利用率和灵活性。本文将深入探讨虚拟化技术的基本概念、分类以及其实现方式,帮助读者更好地理解这一关键技术。
1. 虚拟化概述
1.0 概述
与直接调度片上资源或使用物理平台相比,使用虚拟化技术可以更灵活高效地调度资源,并实现硬隔离。为了实现虚拟化,我们需要使用Hypervisor(或VMM,Virtual Machine Monitor)。
虚拟化技术的核心目标是:截获上层操作系统应用对硬件资源的访问,然后重定向到VMM的资源池中,再由VMM来管理片上资源。
虚拟机具有以下三个典型特征:
- 同质性:虚拟机的运行环境与物理机本质上相同,但表现上可能有所不同。
- 高效性:虚拟机中运行的软件性能接近物理机。
- 资源受控:VMM需要对系统资源有完全控制和管理权限,包括资源的分配、监控和回收。
基于这些需求,我们有了各种虚拟机方案,如KVM、Xen、VMware和ACRN等。
1.1 内核态(Kernel mode)和用户态(User mode)
在x86 CPU中,操作有两个特权形态:内核态和用户态。
- 内核态:如果CPU处于内核态,执行的程序可以执行任何CPU指令,并访问内存中的所有地址,包括外围设备(如硬盘、网卡等)。
- 用户态:如果处于用户态,只能访问受限的资源,不能直接引用内存或访问外围设备。
所有用户程序运行在用户态,但有些程序需要执行内核态的任务(如读取硬盘数据)。这个过程如下:用户态执行APP x收到一个系统调用,然后设置mode bit=0切换到内核态,当内核态执行完,设置mode bit=1切换回到用户态。
1.2 特权指令与敏感指令
引入特权指令(Privileged Instruction)和敏感指令(Sensitive Instruction)的概念:
- 特权指令:对于系统中一些敏感资源的管理和读写的指令,只有处于Ring 0才能正确执行,否则会抛出异常。
- 敏感指令:由于虚拟化的引入,OS现在处于Ring 1,不能执行特权指令,因此交由Ring 0的VMM处理,这部分指令称为敏感指令。
对于有虚拟化的环境,客户机处于Ring 1而不是Ring 0。如果所有的敏感指令都是特权指令,那么执行任意的敏感指令都会产生trap,这样保证了客户机中如果进行这些“敏感”操作的指令,都会交给处于Ring 0的VMM处理。
敏感指令包括:
- 所有I/O指令
- 企图访问或修改VM模式或机器状态的指令
- 企图访问或修改敏感寄存器/存储单元的指令
- 企图访问存储保护系统或内存/地址分配系统的指令
但是x86中有些指令,必须由处于Ring 0状态的VMM处理,但是工作在Ring 1不会产生Trap,这样的话如果处于Ring 1的客户机执行这些指令,不会产生Trap,也不能被定义为特权指令,这与上一句中的目的相冲突,所以必须也要Trap这些“非特权指令”,x86中称之为临界指令(Critical Instructions)。
因此,x86中的敏感指令 = 特权指令 + 非特权指令/临界指令。如果一个系统上敏感指令 = 特权指令,那么为了让VMM完全控制硬件资源,我们让虚拟机上的OS处于Ring 1,不能直接执行敏感/特权指令,而VMM处于Ring 0,所以OS上执行敏感/特权指令的时候,就会引起陷入/cause a trap到VMM,再由VMM来模拟执行引起异常的指令。
临界指令包括敏感指令中的敏感寄存器指令和保护系统指令。
2. 虚拟化分类
根据虚拟化实现的方法,可以分为操作系统级别虚拟化(OS-level virtualization)、全虚拟化(Full virtualization)、类/半虚拟化(Para virtualization)和混合虚拟化(Hybrid-Para virtualization)。
2.1 全虚拟化(Full virtualization)
全虚拟化会模拟足够的硬件设备,不需要对操作系统内核进行修改。客户机不知道自己在一个虚拟化的环境,所以硬件的虚拟化都在VMM或宿主机中完成,客户机可以调用它以为真实硬件的控制命令。
根据“截获并重定向”的实现方式,全虚拟化分为软件虚拟化和硬件虚拟化。
2.1.1 全虚拟化中的软件辅助虚拟化
由于早期x86平台的硬件不支持虚拟化,采用纯软件方式实现“截获重定向”。通过让客户机的特权指令陷入异常,从而触发宿主机进行虚拟化处理的机制来处理,具体实现方法通过以下两种方式相结合:
- 优先级压缩:由于虚拟化的引入,应用从Ring 3 -> Ring 3,操作系统从Ring 0 -> Ring 1,VMM将取代OS处于Ring 0。
- 二进制代码翻译:优先级压缩并不能很好地处理截获所有的特权指令,需要通过二进制翻译来扫描修改客户机的二进制代码,将难以虚拟化的指令转换为支持虚拟化的指令。
2.1.2 硬件虚拟化
后来x86平台的物理设备开始支持虚拟化,提供了对特殊指令截获重定向的硬件支持,如Intel的VT-x技术。
2.1.2.1 硬件虚拟化中的Type-1 Hypervisor
Type-1 Hypervisor(或Bare-metal Hypervisor)虚拟机直接运行在硬件之上,系统上电后加载运行虚拟机监控程序,资源调度是HW->VMM->VM。这种虚拟机将上层的OS和底层的硬件脱离,所以上层的软件也不依赖或局限于特殊的硬件设备或驱动。
2.1.2.2 硬件虚拟化中的Type-2 Hypervisor
Type-2 Hypervisor(或Hosted Hypervisor)虚拟机不是直接运行在硬件资源之上,而是在操作系统之上。系统上电后,会先启动操作系统,然后加载运行虚拟机监控程序,资源调度是HW -> OS -> VMM -> VM。例如,VMware Workstation需要先启动Windows,再启动VMware来启动Ubuntu。
Type 1的虚拟机监控程序可以视为一个为虚拟机进行设计裁剪的操作系统内核,Type 2的虚拟机监控程序依赖于操作系统来进行调度和管理,因此会有限制性。
2.2 类/半虚拟化(Para virtualization)
完全虚拟化中会遇到一些需要通过二进制代码翻译来处理的不友好特权指令集合,而类虚拟化采用另一种处理方式来解决这种问题。类虚拟化(或半虚拟化)需要修改客户机内核源码(API级别),使得不再需要去模拟硬件设备,取而代之的是通过调用这个特殊API来实现虚拟化。在源代码级别修改指令集,来避免虚拟化漏洞的方式,使得VMM能够管理片上资源实现虚拟化。而且这种情况下,客户机(Guest OS)知道自己是一个客户机。
根据片上硬件资源,我们将逐步介绍CPU虚拟化/内存虚拟化/IO虚拟化/GPU虚拟化等。
3. 虚拟化的实现
3.1 CPU虚拟化
3.1.1 Socket / Core / Thread,Physical / Logical CPU
在介绍CPU虚拟化之前,需要了解Socket / Core / Thread以及物理/逻辑CPU的概念:
- Socket / 插槽:主板上提供给一个物理封装处理器的插槽。
- Core / 核心:一个完整的一套寄存器,执行单元,消息队列,代表一个独立的CPU。
- Thread / 线程:一个核心中有一个或多个线程,线程是操作系统能够进行运算调度的最小单元,是进程中的实际运作单位。
- Physical CPU:每颗芯片上的物理CPU个数,Cores数目,4C8T有4个物理CPU。
- Logical CPU:考虑多线程,比如4C8T,有8个Logical CPU。
以Intel i7-8809G为例,是4C8T,4核8线程,因为支持超线程(Hyper-threading),所以线程数是核心数的两倍,4个Physical CPU / 物理CPU,8个Logical CPU / 逻辑CPU。
在Linux中检查CPU,可以得到逻辑CPU/每个物理CPU上面的cores/每个物理CPU上面的逻辑CPU:
// Check physical CPUs
echo "physical_cpu:"
cat /proc/cpuinfo |grep "physical id"|sort |uniq |wc -l
// 1, 一个物理CPU,socket
// Check logical CPUs
echo "logical_cpu:"
cat /proc/cpuinfo |grep "processor" -c
// 8,4核8线程,8个逻辑CPU
// Check cores on each physical CPU (Hyper-threading not include)
echo "core_per_phy_cpu:"
cat /proc/cpuinfo |grep "core id" |sort |uniq |wc -l
// 4,4个核心 cores
// Check logical CPU nums on each physical CPU
echo "logical_core_per_phy_cpu:"
cat /proc/cpuinfo |grep "sib" |sort |uniq |awk -F ' ' '{print $3}'
// 8,8个逻辑CPU
3.1.2 CPU虚拟化的实例
举一个例子,有一条指令"MOV CR0, EAX",也就是将EAX寄存器的值,传给给寄存器CR0。
- 无虚拟化:如果没有VMM,处理器将这条指令丢给VM,操作系统可以访问物理处理器,处在最高特权模式,可以控制片上所有物理资源,直接对物理寄存器CR0进行赋值修改。
- 虚拟化引入:VMM加入之后,VM不再是最高特权,VMM现在是最高特权。这时候对于片上关键资源的访问,就成了敏感指令。VMM对于这种敏感指令的执行,会触发异常处理,从而陷入VMM进行模拟。因为VMM的加入,会拦截掉处理器丢给VM的这条指令,读取EAX的值然后放到内存中,虚拟的vCR0中,这样的话执行该条MOV指令并不会改变真实的CR0的值。下次如果要访问CR0值的时候,VMM进行截获,返回的也是内存中虚拟的vCR0的值,而不是物理的CR0。
3.2 内存虚拟化(Memory Virtualization)
3.2.1 无虚拟化
对于没有虚拟化的native环境,操作系统OS对于内存的管理和使用需要满足以下两点:
- 内存都是从物理地址0开始。
- 内存是连续的,或者至少在一些大的粒度(如256MB)上是连续的。
3.2.2 虚拟化引入
虚拟化的引入,也要满足以上两点,因此对于VM,引入了虚拟的客户机物理地址空间(Guest Physical Address, GPA)概念。
关于地址和地址空间的介绍:
- 地址是访问地址空间的索引,可以分为:
- 逻辑地址:存在于X86机制中,程序直接使用的地址,由16位段选择符和23位偏移量构成。
- 线性地址:又称虚拟地址,是逻辑地址转换后的结果,用于索引线性地址空间;当CPU使用paging分页机制时,线性地址必须转为物理地址才能访问平台内存/硬件资源。
- 物理地址:用于索引物理地址空间。
- 分页和分段机制都启动:逻辑地址 -> 线性地址 -> 物理地址
- 分段启动,分页不启动:逻辑地址 -> 线性地址 = 物理地址
Address Space, 地址空间:Memory, 内存可以视为一个大的数组,地址就是这个大数据的索引;而地址空间则是一个更大的数组,是所有可用资源的集合,地址就是这个数组的索引。Address Space可以分为两类:
- Physical Address Space / 物理地址空间
- Linear Address Space / 线性地址空间
在虚拟化环境下,内存的调度使用,需要进行两层转换(GVA->GPA,GPA->HPA):
- 从客户机虚拟地址(GVA, Guest Virtual Address)到客户机物理地址(GPA,Guest Physical Address)(由客户机操作系统负责)
- 从客户机物理地址(GPA,Guest Physical Address)到宿主机物理地址(HPA, Host Physical Address)(由Hypervisor负责)
因此,内存虚拟化解决了以下两个问题:
- 虚拟机维护客户机物理地址/GPA到宿主机物理地址/HPA的映射。
- 截获VM对于客户机物理地址/GPA的访问,并且根据映射关系,将其转换为宿主机物理地址/HPA。
3.3 I/O虚拟化
3.3.1 I/O访问方式
CPU需要通过I/O来访问外部资源,x86中的I/O根据访问方式不同,可以分为两类:
- Port I/O,通过I/O端口号来访问设备寄存器。
- MMIO(Memory Map I/O),通过内存访问的方式访问设备寄存器或设备RAM。
3.3.2 DMA
引入DMA(Direct Memory Access)/直接内存读取的概念。通过DMA控制器可以直接访问硬件设备资源,不需要CPU的参与(如果设备向内存复制数据都要经过CPU的话,会占用CPU时间降低系统性能)。根据DMA的特性,如果一个I/O设备是支持DMA的,那么我们可以绕过处理器来直接访问目标内存(如果设备的驱动未加修改,那么设备模拟器接收到的DMA目的地址就是客户机的物理地址)。
3.3.3 设备模型(Device Model)
VMM中要进行I/O设备的模拟,并且要能够处理和响应设备的请求,这个功能由设备模型(Device Model)来完成。设备模型需要模拟出目标设备的软件接口和功能,独立于设备的驱动,通过下图中这种调用方式:设备模型是虚拟机设备驱动(Device Driver)和实际设备驱动之间的一个模块。当客户机请求I/O,作为内核模块的VMM会将I/O请求进行拦截,然后通过宿主机的内核态-用户态接口,传递给用户态的设备模型进行处理。
3.3.5 Intel VT-d
无VT-d引入,那么I/O设备的DMA可以访问整个物理内存。如果我们引入VT-d,Intel的VT-d是从硬件上支持I/O虚拟化,在北桥上引入DMA-Remapping(DMA重映射)硬件,如下图中的右图(DMA-Remapping HW)。这样的话,虚拟机中对于I/O设备的访问,都会被DMA重映射硬件截获,然后查找对应I/O设备的页表,重映射硬件对DMA中的地址进行转换,而不是让I/O设备直接对物理内存直接访问。