html 动漫网站,wordpress表excel插件,龙岩网站建设方案书,主机屋如何做网站《重识云原生系列》专题索引#xff1a;
第一章——不谋全局不足以谋一域第二章计算第1节——计算虚拟化技术总述第二章计算第2节——主流虚拟化技术之VMare ESXi第二章计算第3节——主流虚拟化技术之Xen第二章计算第4节——主流虚拟化技术之KVM第二章计算第5节——商用云… 《重识云原生系列》专题索引
第一章——不谋全局不足以谋一域第二章计算第1节——计算虚拟化技术总述第二章计算第2节——主流虚拟化技术之VMare ESXi第二章计算第3节——主流虚拟化技术之Xen第二章计算第4节——主流虚拟化技术之KVM第二章计算第5节——商用云主机方案第二章计算第6节——裸金属方案第三章云存储第1节——分布式云存储总述第三章云存储第2节——SPDK方案综述第三章云存储第3节——Ceph统一存储方案第三章云存储第4节——OpenStack Swift 对象存储方案第三章云存储第5节——商用分布式云存储方案
主流开源虚拟化技术KVM知识地图 KVM知识地图
1 KVM运行原理
1.1 KVM架构简介 KVMKernel-based Virtual Machine是基于Linux内核的开源虚拟化解决方案从2.6.20版本开始被合入kernel主分支维护。最初只支持X86平台的上支持VMX或者SVM的CPU不久后被确认为标准Linux内核的虚拟化方案并逐步支持S390、IA64和PowerPC等体系架构KVM本身只提供部分的虚拟化功能虚拟CPU和内存而由经过特殊改造后的Qemu(Qemu-kvm)来帮助下提供完整的平台虚拟化功能。 KVM依赖于x86硬件的虚拟化特性提供全虚拟化的虚拟机其基本思想是在Linux内核的基础上添加虚拟机管理模块重用Linux内核中已完善和成熟的机制和模块比如进程调度、内存管理、IO管理等使之成为一个可以支持运行虚拟机的Hypervisor。整体架构如下图所示 KVM是基于硬件辅助虚拟化技术(如Intel VT-x)的全虚拟化解决方案如上图所示说明如下
VMM(即KVM内核)运行于根模式下的Ring0
宿主机上的用户态进程运行于根模式下的Ring3
虚拟机中的Kernel运行于非根模式下的Ring0
虚拟机中的用户态进程运行于非根模式下的Ring3 Qemu-kvm是KVM官方提供并维护的改进后的Qemu针对KVM解决方案将标准Qemu做了针对性的改造使其具有更好的性能并与KVM进行了完美的融合 一个VM(虚拟机)就是一个传统的Linux进程VM运行于Qemu-KVM进程的地址空间中 VMM向上层提供/dev/kvm接口/dev/kvm是一个标准的字符设备通过ioctl接口控制Qemu-kvm通过调用/dev/kvm设备的ioctl接口对虚拟机进行相关控制比如创建虚拟机、创建VCPU、运行虚拟机等 为提升KVM虚拟机中的IO性能KVM还提供了Virtio驱动相当于Xen环境中的半虚拟化驱动。
1.2 KVM虚拟化架构 如下图KVM虚拟化有两个核心模块 1KVM内核模块负责CPU与内存虚拟化包括VM创建内存分配与管理、vCPU执行模式切换等主要包括KVM虚拟化核心模块kvm.ko以及硬件相关的kvm-intel.ko或kvm-amd.ko模块。 Kvm.ko是KVM的核心公共模块kvm-intel.ko和kvm-amd.ko分别是针对Intel和AMD平台架构的独立模块。在KVM核心公共模块中包含了IOMMU、中断控制、KVM arch、设备管理等部分代码这些代码构成了虚拟机管理的核心功能从这些模块的大致信息也可以看出KVM自身并没有实现一个完整的PC系统的虚拟化而只是实现了最核心的CPU虚拟化、内存虚拟化和IO虚拟化等部分功能并向上层提供了相应的API其余虚拟化和管理工作主要交给了Qemu-kvm负责。 2QEMU-KVM设备模拟模块实现IO虚拟化与各设备模拟磁盘、网卡、显卡、声卡等通过IOCTL系统调用与KVM内核交互。KVM仅支持基于硬件辅助的虚拟化如Intel-VT与AMD-V在内核加载时KVM先初始化内部数据结构打开CPU控制寄存器CR4里面的虚拟化模式开关执行VMXON指令将Host OS设置为root模式并创建的特殊设备文件/dev/kvm等待来自用户空间的命令然后由KVM内核与QEMU相互配合实现VM的管理。KVM会复用部分Linux内核的能力如进程管理调度、设备驱动内存管理等。 KVM虚拟化架构图
1.3 KVM运行视图 KVM运行过程中存在三种模式
客户模式Guest Mode运行GuestOS执行Guest非IO操作指令。用户模式User Mode运行QEMU实现IO模拟与管理。内核模式Kernel Mode运行KVM内核实现模式的切换VM Exit/VM Entry执行特权与敏感指令。 KVM运行的基本如下图所示 KVM运行流程图 流程描述
运行在用户态的Qemu-kvm通过ioctl系统调用操作/dev/kvm字符设备创建VM和VCPU内核KVM模块负责相关数据结构的创建即初始化然后返回用户态Qemu-kvm通过ioctl调用运行VCPU即调度相应的VM运行内核进行相关处理后执行VMLAUNCH指令通过VM-Entry进入Guest OS运行Guest OS运行于非根模式下Guest OS执行相应的虚拟机代码非敏感指令可直接在物理CPU上运行当Guest OS中执行到敏感指令、发生外部中断、或Guest OS发生内部异常时将产生VM-Exit并将相关信息记录到VMCS结构中VM-Exit使CPU退回到根模式下由VMM读取VMCS结构判断VM-Exit的原因如是IO操作或是其他外设指令则返回到用户态Qemu-kvm(即根模式下的Ring3)由Qemu-kvm完成对相关指令的模拟如果不是则由VMM自行处理处理完成后重新VM-entry进入到Guest OS运行
2 KVM对CPU/内存/IO虚拟化的技术实现
2.1 CPU虚拟化
2.1.1 pCPU与vCPU关系 如下图物理服务器上通常配置2个物理pCPUSocket每个CPU有多个核core。开启超线程Hyper-Threading技术后每个core有2个线程Thread。在虚拟化环境中一个Thread对应一个vCPU。在KVM中每一个VM就是一个用户空间的QEMU进程分配给Guest的vCPU就是该进程派生的一个线程Thread由Linux内核动态调度到基于时分复用的物理pCPU上运行。KVM支持设置CPU亲和性将vCPU绑定到特定物理pCPU如通过libvirt驱动指定从NUMA节点为Guest分配vCPU与内存。KVM支持vCPU超分over-commit使得分配给Guest的vCPU数量可超过物理CPU线程总量。 pCPU与vCPU关系图
2.1.2.1 VCPU描述符数据结构 硬件虚拟化使用VCPU(Virtual CPU)描述符来描述虚拟CPUVCPU描述符与OS中进程描述符类似本质是一个结构体其中包含如下信息
VCPU标识信息如VCPU的ID号VCPU属于哪个Guest等。虚拟寄存器信息在VT-x的环境中这些信息包含在VMCS中。VCPU状态信息标识白VCPU当前所处的状态(睡眠、运行等)主要供调度器使用。额外的寄存器/部件信息主要指未包含在VMCS中的寄存器或CPU部件比如浮点寄存器和虚拟的LAPIC等。其他信息用户VMM进行优化或存储额外信息的字段如存放该VCPU私有数据的指针。 总体来说VCPU可以划分为两部分
以VMCS为主由硬件来使用和更新的部分主要是虚拟寄存器。除VMCS之外由VMM来使用和更新的部分。如VCPU标识、状态信息等。 当VMM创建虚拟机时首先要为虚拟机创建VCPU整个虚拟机的运行实际上可以看做VMM调度不同的VCPU运行。
2.1.2 CPU虚拟化原理 CPU虚拟化是VMM中最核心的部分由于内存和IO访问的指令都是敏感指令所以内存和IO虚拟化都依赖于CPU虚拟化的实现。CPU虚拟化的目标是让虚拟机中执行的所有敏感指令都能产生异常而“陷入”并由VMM进行捕获模拟。VMM的陷入是通过CPU的保护机制、中断或异常来完成的。通常VMM的陷入方式有如下3种 1、由CPU的保护机制触发。CPU在执行敏感指令之前会检查执行条件是否满足执行条件主要包括当前特权级别、运行模式、内存映射关系等只要有任一条件不满足就会VM-Exit陷入到VMM进行进一步处理。 2、异步中断。包括处理器内部的中断源和外设的中断源当中断信号到达CPU时CPU会强行中断Guest OS当前执行的指令然后VM-Exit到VMM注册的中断服务程序进行进一步处理。 3、虚拟机主动触发的异常也就是通常所说的陷阱。虚拟机可以通过陷阱指令主动VM-Exit到VMM中。 在KVM虚拟化解决方案中利用了硬件的虚拟化特性(比如Intel VT-x和AMD-V)实现CPU的虚拟化。如之前介绍VT-x提供了一套称作VMX的新的工作模式工作在该模式下的处理器又具有两类操作模式VMX root operation和VMX non-root operation。对操作系统来说VMX non-root operation模式与传统的x86处理器兼容最大的差别在于当虚拟机执行一些访问全局资源的指令时将导致虚拟机退出操作VM-Exit从而使虚拟机监控器获得控制权以便对访问全局资源的指令进行模拟。以后虚拟机监控器可以通过虚拟机进入操作VM-Entry使虚拟机重新获得控制权。 其次VT-x为系统编程接口状态的切换提供硬件支持。VT-x为每个虚拟机维护至少一个VMCSVirtual Machine Control Structure结构其中保存了虚拟机和虚拟机监控器的系统编程接口状态。当执行VM exit和VM entry操作时VT-x自动根据VMCS中的内容完成虚拟机和虚拟机监控器间的系统编程接口状态切换。另外VT-x还提供了一组指令使得虚拟机监控器通过一条指令就可以完成虚拟机间的切换。
2.1.3 VMX Root/Non-Root模式切换流程 如下图KVM内核加载时执行VMXON指令进入VMX操作模式VMM进入VMX Root模式可执行VMXOFF指令退出。GuestOS执行特权或敏感指令时触发VM Exit系统挂起GuestOS通过VMCALL调用VMM切换到Root模式执行VMExit开销是比较大的。VMM执行完成后可执行VMLANCH或VMRESUME指令触发VM Entry切换到Non-root模式系统自动加载GuestOS运行。 VMX定义了VMCSVirtual Machine Control Structure数据结构来记录vCPU相关的寄存器内容与控制信息发生VMExit或VMEntry时需要查询和更新VMCS。VMM为每个vCPU维护一个VMCS大小不超过4KB存储在内存中VMCS区域通过VMCS指针进行管理。VMCS主要包括3部分信息control data主要保存触发模式切换的事件及原因Guest state 保存Guest运行时状态在VM Entry时加载Host state保存VMM运行时状态在VM Exit时加载。通过读写VMCS结构对Guest进行控制。 VMX Root/Non-Root 模式切换图
2.1.4 中断虚拟化 在物理平台上中断架构和外部中断处理流程如下图所示 外部中断处理流程图 大致处理过程为
IO设备通过中断控制器(IO APIC或PIC)向CPU发送中断请求IO APIC将中断转发至目标CPU和Local APIC目标APIC对该中断进行处理 在虚拟化环境中VMM为Guest OS虚拟了一个与物理中断架构类似的虚拟中断架构如下图所示 虚拟中断架构示意图 每个VCPU对应一个虚拟Local APIC用于接收中断同时还模拟了虚拟IO APIC(或虚拟PIC)用于接收外设(虚拟设备)发出的中断请求并进行转发。虚拟Local APIC、虚拟IO APIC、虚拟PIC等都是VMM中的软件实体(对应于相应的数据结构)。主要处理流程如下
当虚拟设备需要发送中断时虚拟设备会调用虚拟IO APIC的接口发送中断而虚拟IO APIC根据中断请求选出适合的虚拟Local APIC并调用其接口发送中断请求虚拟Local APIC进一步利用CPU硬件虚拟化功能(Intel VT-x或AMD SVM)的事件注入机制将中断注入到相应的VCPU当相应VCPU得到调度时即处理相应的中断
2.1.4 调度 在KVM虚拟化环境中KVM虚拟机作为一个系统进程运行因此虚拟机的调度实际上就是利用了Linux自身的调度器完成。本文不做详述。
2.2 内存虚拟化
2.2.1 内存虚拟化背景简述 在物理机上虚拟地址通过Guest页表即可转换为物理地址。但是在虚拟化环境中由于VMM和VM都需要独立的地址空间则产生了冲突。 为实现内存虚拟化让客户机使用一个隔离的、从零开始且具有连续的内存空间KVM 引入一层新的地址空间即客户机物理地址空间 (Guest Physical Address, GPA)该地址空间并不是真正的物理地址空间它只是宿主机(Host主机)虚拟地址空间在Guest地址空间的一个映射。对Guest来说客户机物理地址空间都是从零开始的连续地址空间但对于宿主机来说客户机的物理地址空间并不一定是连续的客户机物理地址空间有可能映射在若干个不连续的宿主机地址区间如下图所示 由于物理MMU只能通过Host机的物理地址(Host Physical Address, HPA)进行寻址所以实现内存虚拟化关键是需要将Guest机的虚拟地址(Guest Virtual Address, GVA)转换为HPA。传统的实现方案中这个过程需要经历GVA - GPA - HVA - HPA的转换过程需要对地址进行多次转换而且需要KVM的介入效率非常低。为提高GVA到HPA的地址转换效率KVM提供了两种地址转换方式
影子页表(Shadow Page Table)方案是纯软件的实现方式基于硬件特性的地址转换如基于Intel EPT(Extended Page Table扩展页表)或AMD NPT(Nested Page Table嵌套页表) 另外CPU使用TLBTranslation Lookaside Buffer缓存线性虚拟地址到物理地址的映射地址转换时CPU先根据GPA先查找TLB如果未找到映射的HPA将根据页表中的映射填充TLB再进行地址转换。如果基于影子页表方案不同Guest的vCPU切换执行时需要刷新TLB严重影响了内存访问效率。因此Intel引入了VPIDVirtual-Processor Identifier技术在硬件上为TLB增加一个标志每个TLB表项与一个VPID关联唯一对应一个vCPU当vCPU切换时可根据VPID找到并保留已有的TLB表项减少TLB刷新。
2.2.2 影子页表(Shadow Page Table)方案
2.2.2.1 基本原理 由于内存虚拟化在将GVA转换为HPA的过程中需要经历多次转换无法直接使用Guest机页表和CR3页表寄存器。使用影子页表(Shadow Page Table)可以实现客户机虚拟地址(GVA)到宿主机物理地址(HPA)的直接转换与传统方式的转换过程对比如下 影子页表中记录的是GVA跟HPA的对应关系每个页表项指向的都是宿主机的物理地址。由于客户机中每个进程都有自己的虚拟地址空间所以 KVM 需要为客户机中的每个进程页表都要维护一套相应的影子页表。在Guest机访问内存时VMM在物理MMU中载入的是Guest机当前页表所对应的影子页表从而实现GVA到HPA的直接转换。 Guest机中的每一个页表项都有一个影子页表项与之相对应。为了快速检索Guest机页表所对应的的影子页表KVM 为每个客户机都维护了一个哈希表影子页表和Guest机页表通过此哈希表进行映射基本原理如下 对于每一个Guest机来说Guest机的页目录/页表都有唯一的GPA通过页目录/页表的GPA就可以在哈希链表中快速地找到对应的影子页目录/页表。在检索哈希表时KVM 把Guest页目录/页表的客户机物理地址低10位作为键值进行索引根据其键值定位到对应的链表然后遍历此链表找到对应的影子页目录/页表。当然如果没有找到对应的影子页目录/页表则说明影子页表项和Guest页表项的对应关系还没有建立 此时KVM 会为其分配新的物理页并建立起Guest页目录/页表和对应的影子页目录/页表之间的映射。 2.2.2.2 影子页表的建立与更新 影子页表的建立和更新过程交织在一起影子页表的建立和更新主要发生在如下3种情况下 1、Guest OS修改Guest CR3寄存器。由于相关指令为敏感指令所以相关操作会被VMM截获此时VMM会根据相关情况进行影子页表的维护。比如当客户机切换进程时客户机操作系统会把待切换进程的页表基址载入 CR3而该特权指令将被VMM截获进行新的处理即在哈希表中找到与此页表基址对应的影子页表基址载入客户机 CR3使客户机在恢复运行时CR3 实际指向的是新切换进程对应的影子页表。 2、因Guest主机页表和影子页表不一致而触发的缺页异常此时也会VM-Exit到VMM进而可进行相关维护操作。 3、Guest OS中执行INVLPG指令刷新TLB时由于INVLPG指令为敏感指令所以该操作也会被VMM进行截获并进行影子页表相关维护操作。 其中第2种情况发生几率最高相关处理也最复杂如下做重点描述。不同的缺页异常处理方式不用常见的缺页异常包括如下3类 1影子页表初始化时产生的缺页异常在虚拟机运行之初VMM中与Guest主机页表对应的影子页表都没有建立而宿主机CR3寄存器中载入的却是影子页目录地址所以此时任何的内存操作都会引发异常如果此时Guest主机的相应页表已经建立那么处理这种异常即是建立相应的影子页表可如果Guest主机的页表项尚未建立那就是Guest主机自身的缺页异常即为如下的第2种情况。 2Guest主机上的缺页异常。如果Guest OS尚未给这个GVA分配Guest主机物理页即相应的Guest主机页表项尚未建立此时将引发缺页异常。另外当Guest机访问的Guest页表项存在位(Present Bit)为0或相关访问权限不匹配时也将引发缺页异常。 3VMM将Host机物理页换出到硬盘上时引发的缺页异常。 影子页表缺页异常的默认处理流程 VMM截获缺页异常(VM-Exit)并检查此异常是否由Guest即自身引发如果是则将直接返回Guest OS(Vm-Entry)然后由Guest OS自身的page fault流程处理如果不是则为影子页表和Guest机页表不一致导致这样的异常也叫“影子缺页异常”此时VMM会根据Guest机页表同步影子页表过程如下
VMM根据Guest机页表项建立影子页目录和页表结构VMM根据发生缺页异常的GVA在Guest机页表的相应表项中得到对应的GPAVMM根据GPA在GPA与HPA的映射表中(通过之前描述的HASH表建立)得到相应的HPA再将HPA填入到影子页表的相应表项中 影子页表和Guest主机页表不是时刻同步的只有在需要时才进行通过从某种角度看影子页表可以看做是Guest页表的TLB常称为虚拟TLB(VTLB)。 影子页表解决了传统IA32架构下的内存虚拟化问题由于影子页表可被载入物理 MMU 为客户机直接寻址使用 所以客户机的大多数内存访问都可以在没有 KVM 介入的情况下正常执行没有额外的地址转换开销也就大大提高了客户机运行的效率。但也有比较明显的缺点
实现复杂影子页表同步需要考虑各种情况因为TLB中缓存的仅是当前虚拟主机GVA到HPA的转换关系所以每一次虚拟主机切换都需要清空TLB从而导致虚拟机性能低下内存开销大需要为每个Guest机进程维护一个影子页表。
2.2.3 EPT 页表技术
2.2.3.1 简述 为解决影子页表的问题Intel在CPU中使用EPT技术AMD也提供的类似技术叫做NPT即Nested Page Tables都是直接在硬件上支持GVA--GPA--HPA的两次地址转换从而降低内存虚拟化实现的复杂度也进一步提升了内存虚拟化的性能。本文主要以Intel EPT技术为例进行阐述。
2.2.3.2 EPT页结构 Intel处理器设计了EPT的页结构用来将保存GPA到HPA的映射因此说EPT是硬件支持的EPT的页结构如下图的咖啡色部分整个页结构有4级和4-level分页模式的结构一模一样只有存放PML4 Table物理地址的地方不一样4-level将PML4 Table的地址存放在CR3寄存器中EPT没有EPT中PML4 Table的地址被称为EPTP它存放在VMCS中的VM-execution 控制字段。VMM用VMCS来配置VM的运行环境以及控制VM的运行。 进入客户态之前VMM首先通过特殊指令VMPTRLD加载VMCS内存地址的指针其中就包括EPTP字段因此理论上EPTP是针对每个虚机而言的EPT对每个虚机是可以配置的。有人会问客户态的所有物理地址都是GPA那么存放EPT物理地址的EPTP是什么地址呢个人理解这个地址不是GPA它非常特殊因为它是在未进入客户态之前由VMM加载的不可能是GPA应该是主机上的地址HPA。 2.2.3.3 EPTP —— Extended Page Table Pointer EPTPextended-page-table pointer是VMCS中存放EPT页表物理地址的字段它的格式如下 EPTP格式 EPTP指向的下一级是PML4 Table它是页对齐的因此至少是4K对齐所以低12 bit没有用可以拿来复用其中bit 6是和脏页跟踪有关的标志位它被置1时CPU访问EPTP指向的任何下级页结构的时候Access位都会被置1最后的页表项中的Dirty位会被置1。 EPTP bit 12之上的字段用来存放下一级表PML4 Table的物理地址。
2.2.3.4 PML4E —— Page Map Level 4 Entry PML4E格式
Accessed flagbit 8访问标志位当EPTP中的accessed and dirty flags置位时CPU每次通过EPT多级页表结构转换物理地址时只要使用到这一级的页表项就需要将Accessed flag置位。
Physical addressbit 12 ~ bit (N-1)存放下一级页表结构的物理地址。
2.2.3.5 PDPT —— Page Directory Pointer Table PDPT格式
Accessed flagbit 8访问标志位当EPTP中的accessed and dirty flags置位时CPU每次通过EPT多级页表结构转换物理地址时只要使用到这一级的页表项就需要将Accessed flag置位。
Physical addressbit 30 ~ bit (N-1)存放下一级页表结构的物理地址。
2.2.3.6 PDE —— Page-Directory Entry PDE格式
Accessed flagbit 8访问标志位当EPTP中的accessed and dirty flags置位时CPU每次通过EPT多级页表结构转换物理地址时只要使用到这一级的页表项就需要将Accessed flag置位。
Physical addressbit 12 ~ bit (N-1)存放下一级页表结构的物理地址。
2.2.3.7 PTE —— Page Table Entry PTE格式
Accessed flagbit 8访问标志位当CPU对该页表项指向的物理页又访问操作时读写操作都算会将Dirty flag置位。
Dirty flagbit 9脏页标志位当CPU对该页表项指向的物理页进行写操作时会将Dirty flag置位。
Physical addressbit 12 ~ bit (N-1)存放下一级页表结构的物理地址。
2.2.3.8 EPT缺页异常处理 在GPA到HPA转换的过程中由于缺页、写权限不足等原因也会导致客户机退出产生 EPT 异常。对于 EPT 缺页异常处理过程大致如下
KVM 首先根据引起异常的GHA映射到对应的HVA然后为此虚拟地址分配新的物理页最后 KVM 再更新 EPT 页表建立起引起异常的GPA到HPA的映射。 EPT 页表相对于影子页表其实现方式大大简化主要地址转换工作都由硬件自动完成而且Guest内部的缺页异常也不会导致VM-Exit因此Guest运行性能更好开销更小。
2.2.3.9 EPT地址转换流程 EPT地址转换流程图 完整的地址翻译流程描述为
Guest OS加载Guest进程的gCR3gCR3中存放的是Guest进程页目录表的GPA。处于非根模式的CPU的MMU查询硬件EPT TLB如果有所请求的GPA到HPA的映射则使用其对应的HPA作为Guest页目录表的基址。如没有所请求的GPA到HPA的映射则查询EPT获得gCR3所映射的HPA并将其作为Guest页目录表的基址并同步到EPT TLB。若第3步依然没找到则根据GVA获得页目录偏移(图中的Dir Offset)获得用于索引Guest页表的基址该地址为GPA。再由VCPU的MMU查询硬件EPT TLB如果有所请求的GPA到HPA的映射则使用其对应的HPA作为Guest页表的基址。如依然没有所请求的GPA到HPA的映射则查询EPT将其转换为HPA使用该HPA再加上GVA中的页表偏移(图中的Table Offset)即可得到PTE(页表项)的GPA。再由VCPU的MMU查询硬件EPT TLB如果有所请求的GPA到HPA的映射则其对应的HPA加上GVA中的Offset即为最终的宿主机物理地址(HPA)。如没有所请求的GPA到HPA的映射则查询EPT将其转换为HPA使用该HPA加上GVA中的Offset即为最终的宿主机物理地址(HPA)。 EPT页表实现GPA到HPA的转换的原理与Guest页表实现GVA到HPA的转换原理相同需要经历多级页表的查询图中没有详细画出。假设Guest机有m级页表宿主机EPT有n级在TLB均miss的最坏情况下会产生m*n次内存访问完成一次客户机的地址翻译EPT硬件通过增大硬件EPT TLB来尽量减少内存访问。
2.2.3.10 EPT页表的建立流程 初始情况下Guest CR3指向的Guest物理页面为空页面 Guest页表缺页异常KVM采用不处理Guest页表缺页的机制不会导致VM Exit由Guest的缺页异常处理函数负责分配一个Guest物理页面GPA将该页面物理地址回填建立Guest页表结构 完成该映射的过程需要将GPA翻译到HPA此时该进程相应的EPT页表为空产生EPT_VIOLATION虚拟机退出到根模式下执行由KVM捕获该异常建立该GPA到HOST物理地址HPA的映射完成一套EPT页表的建立中断返回切换到非根模式继续运行。 VCPU的mmu查询下一级Guest页表根据GVA的偏移产生一条新的GPAGuest寻址该GPA对应页面产生Guest缺页不发生VM_Exit由Guest系统的缺页处理函数捕获该异常从Guest物理内存中选择一个空闲页将该Guest物理地址GPA回填给Guest页表 此时该GPA对应的EPT页表项不存在发生EPT_VIOLATION切换到根模式下由KVM负责建立该GPA-HPA映射再切换回非根模式 如此往复直到非根模式下GVA最后的偏移建立最后一级Guest页表分配GPA缺页异常退出到根模式建立最后一套EPT页表。 至此一条GVA对应在真实物理内存单元中的内容便可通过这一套二维页表结构获得。
2.2.4 VPID机制 TLB(Translation Lookaside Buffer)转换检测缓冲区是一个内存管理单元,用于改进虚拟地址到物理地址转换速度的缓存。 VPIDVirtualProcessor Identifiers虚拟处理器标识是在硬件上对TLB资源管理的优化通过在硬件上为每个TLB项增加一个标识用于不同的虚拟处理器的地址空间从而能够区分开Hypervisor和不同处理器的TLB。 硬件区分了不同的TLB项分别属于不同虚拟处理器因此可以避免每次进行VM-Entry和VM-Exit时都让TLB全部失效提高了VM切换的效率。 由于有了这些在VM切换后仍然继续存在的TLB项硬件减少了一些不必要的页表访问减少了内存访问次数从而提高了Hypervisor和客户机的运行速度。 VPID也会对客户机的实时迁移Live Migration有很好的效率提升会节省实时迁移的开销会提升实时迁移的速度降低迁移的延迟Latency。 VPID与EPT是一起加入到CPU中的特性也是Intel公司在2009年推出Nehalem系列处理器上新增的与虚拟化相关的重要功能。 基于EPT与VPID的内存地址转换
2.2.5 透明大页THP x86 CPU默认使用4KB的内存页面目前已经支持2MB1GB的内存大页Huge Page。使用大页内存可减少内存页数与页表项数节省了页表所占用的CPU缓存空间同时也减少内存地址转换次数以及TLB失效和刷新的次数从而提升内存使用效率与性能。但使用内存大页也有一些弊端如大页必须在使用前准备好应用程序必须显式地使用大页一般是调用mmap、shmget或使用libhugetlbfs库进行封装需要超级用户权限来挂载hugetlbfs文件系统如果大页内存没有实际使用会造成内存浪费等。 2009年实现的透明大页THPTransparent Hugepage技术创建了一个抽象层能够自动创建、管理和使用传统大页实现发挥大页优势同时也规避以上弊端。当前主流的Linux版本都默认支持。KVM中可以在Host和Guest中同时使用THB技术。
2.2.6 内存超分Over-Commit 由于Guest使用内存时采用瘦分配按需增加的模式KVM通过支持内存超分Over-Commit可使得分配给Guest的内存总量大于实际物理内存总量从系统访问性能与稳定性考虑在生产环境中一般不建议使用内存超分。内存超分有三种实现方式 1 内存交换Swapping当系统内存不够用时把部分长时间未操作的内存交换到磁盘上配置的Swap分区等相关程序需要运行时再恢复到内存中。 2 气球Ballooning通过virtio_balloon驱动实现动态调整Guest与Host的可用内存空间。气球中的内存是Host可使用Guest不能使用。当Host内存不足时可以使气球膨胀从而回收部分已分配给Guest的内存。当Guest内存不足时可请求压缩气球从Host申请更多内存使用。 3 页共享Page Sharing通过KSMKernel Samepage Merging让内核扫描正在运行进程的内存。如果发现完全相同的内存页就会合并为单一内存页并标志位写时复制COWCopy On Write。如果有进程尝试修改该内存页将复制一个新的内存页供其使用。KVM中QEMU通过madvise系统调用告知内核那些内存可以合并通过配置开关控制是否企业KSM功能。Guest就是QEMU进程如果多个Guest运行相同OS或应用且不常更新使用KSM能大幅提升内存使用效率与性能。当然扫描和对比内存需要消耗CPU资源对性能会有一定影响。
2.3 IO设备虚拟化
2.3.1 IO设备虚拟化概述 在虚拟化环境中Guest的IO操作需要经过特殊处理才能在底层IO设备上执行。如表1KVM支持5种IO虚拟化技术。 虚拟化环境IO处理方式对比
2.3.2 设备模拟与virtio驱动 如下图所示全虚拟化的设备模拟与半虚拟化的virtio驱动都是通过软件实现IO虚拟化。
设备模拟 KVM中通过QEMU来模拟网卡或磁盘设备。Guest发起IO操作时被KVM的内核捕获处理后发送到IO共享页并通知QEMUQEMU获取IO交给硬件模拟代码模拟IO操作并发送IO请求到底层硬件处理处理结果返回到IO共享页然后通知IO捕获代码读取结果并返回到Guest中。
Virtio驱动 在Guest中部署virtio前端驱动如virtio-net、virtio-blk、virtio_pci、virtio_balloon、virtio_scsi、virtio_console等然后在QEMU中部署对应的后端驱动前后端之间定义了虚拟环形缓冲区队列virtio-ring用于保存IO请求与执行信息。Guest的IO从前端驱动发送到virtio-ring然后后端驱动获取IO请求并转发到底层设备处理处理完后返回结果。目前主流的Linux与windows都支持virtio以获得更好的IO性能。 基于软件的IO虚拟化 vhost-net与vhost-user virtio中后端驱动由用户空间的QEMU提供但网络协议栈处于内核中如果通过内核空间来处理网络IO可以减少了网络IO处理过程中的多次上下文切换从而提高网络吞吐量与性能。所以新的内核中提供vhost-net驱动使前端网络驱动virtio-net的后端处理任务从用户态的QEMU改到Host内核空间执行。 大规模云计算环境中会使用OVSOpen vSwitch或SDN方案而进程运行在用户态如果继续使用内核态的vhost-net依然存在大量用户态与内核态的切换所以引入了vhost-user内核态vhost功能在用户态实现。vhost-user定义了MasterQEMU进程和slaveOVS进程作为通信两端Master与slave之间控制面通过共享的虚拟队列virtqueure交换控制逻辑数据面通过共享内存交换信息。结合vhost-user、vSwitch与DPDK可以在用户态完成网络数据包交换处理从而大幅提升了网络虚拟化性能。
2.3.3 设备直通与共享 通过软件方式实现IO虚拟化存在一定系统开销与性能损耗所以Intel推出了硬件辅助虚拟化技术AMD-V类似。 Intel 硬件辅助虚拟化技术
2.3.3.1 设备直通PCI Pass-through 基于硬件辅助虚拟化技术KVM支持将Host的PCI/PCI-E物理设备如网卡、磁盘、USB、显卡、GPU等直接分配给Guest使用。Guest的对该设备的IO操作与物理设备一样不经过QEMU/KVM处理。直通设备不能共享给多个Guest使用且不能随Guest进行动态迁移需要通过热插拔或libvirt工具来解决。
2.3.3.2 设备共享SR-IOV标准 为了实现多个Guest可以共享同一个物理设备PCI-SIG发布了SR-IOVSingle Root-IO Virtualization标准让一个物理设备支持多个虚拟功能接口可以独立分配给不同的Guest使用。SR-IOV定义了两个功能 1 物理功能PFPhysical Function拥有物理PCI-e设备的完整功能可独立使用以及SR-IOV扩展能力可配置管理VF。 2 虚拟功能VFVirtual Function由PF衍生的轻量级PCI-e功能包括数据传送的必要资源。不同的VF可以分配给不同的GuestSR-IOV为Guest独立使用VF提供了独立的内存、中断与DMA流数据传输过程无需VMM介入。
2.3.3.3 DPDK/SPDK 为了更好地提升虚拟化网络或磁盘IO性能Intel等公司推出了数据平面开发工具集DPDKData Plance Development Kit与存储性能开发工具集SPDKStorage Performace Development Kit。DPDK、vhost-user、OVS结合使用可跳过Linux内核直接在用户态完成网络数据包交换处理。SPDK通过环境抽象层EAL与UIO将存储驱动放在用户态处理同时通过PMD轮询机制代替传统中断模式来处理IO。DPDK与SPDK大幅缩短了IO处理路径与系统开销 IO性能提升非常明显。DPDK与SPDK详情参考云网络介绍与云存储介绍部分。
2.3.4 其他IO设备特性虚拟化方案
2.3.4.1 图像与声音虚拟化处理 QEMU中对Guest的图像显示默认使用SDLSimple DirectMedia Layer实现。SDL是一个基于C语言的、跨平台的开源的多媒体程序库提供了简单的接口用于操作硬件平台的图形显示、声音、输入设备等广泛应用于各种操作系统。同时在虚拟化环境中也广泛使用虚拟网络控制台VNCVirtual Network Computing采用RFBRemote Frame Buffer协议将客户端浏览器或VNC Viewer的键盘与鼠标输入传递到远程服务端VNC Server中并返回输出结果。VNC不依赖操作系统在VNC窗口断开后已发出操作仍然会在服务端继续执行。由于VNC是GPL授权衍生出多个版本RealVNC、TightVNC与UltraVNC其对比如表3。 VNC版本对比
2.3.4.2 热插拔Hot Plugging 热插拔就是在服务器运行时插上或拔出硬件以保障服务器的扩展性与灵活性。热插拔需要总线电气特性、主板BIOS、操作系统以及设备驱动的支持。目前支持服务器硬盘、CPU、内存、网卡、USB设备与风扇等部件热插拔。在KVM中在Guest不关机情况下支持PCI设备如模拟、半虚拟化或直通的网卡、硬盘、USB设备热拔插但CPU和内存热插拔硬件平台和OS层面的限制还比较多。
2.4 KVM内核模块中重要数据结构
2.4.1 kvm结构体 Kvm结构体代表一个具体的虚拟机当通过KVM_CREATE_VM指令字创建一个虚拟机后就会创建一个新的kvm结构体对象。Kvm结构体中包括了VCPU、内存、APIC、IRQ、MMU、Event事件等相关信息该结构体主要在KVM虚拟机内部使用用于跟踪虚拟机状态。 重要的结构体成员说明如下:
struct kvm { …
struct kvm_memslots *memslots; //KVM虚拟机分配的内存slot用于GPAàHVA的转换内存虚拟化使用 … struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; //KVM虚拟机中包含的VCPU结构体数组一个VCPU对应一个数组成员。 … struct kvm_io_bus *buses[KVM_NR_BUSES]; //KVM虚拟机中包括的IO总线结构体数组一条总线对应一个kvm_io_bus结构体如ISA总线、PCI总线。 … struct kvm_vm_stat stat; //KVM虚拟机中的运行时状态信息比如页表、MMU等状态。 struct kvm_arch arch; //KVM中跟arch相关的参数。 …
}
2.4.2 kvm_run结构体 kvm_run结构体记录了KVM的内部运行状态如VM-Exit发生的原因等。重要字段说明如下
struct kvm_run { __u8 request_interrupt_window; //向VCPU注入一个中断让VCPU做好相关准备工作 … __u8 ready_for_interrupt_injection; //响应request_interrupt_window的中断请求当设置时说明VCPU可以接收中断。 __u8 if_flag; //中断使能标识如果使用了APIC则无效 struct { __u64 hardware_exit_reason; //当发生VM-Exit时该字段保存了由于硬件原因导致VM-Exit的相关信息。 } hw; struct {
#define KVM_EXIT_IO_IN 0
#define KVM_EXIT_IO_OUT 1 __u8 direction; __u8 size; /* bytes */ __u16 port; __u32 count; __u64 data_offset; /* relative to kvm_run start */ } io; //当由于IO操作导致发生VM-Exit时该结构体保存IO相关信息。 …
};
2.4.3 kvm_vcpu结构体 在通过KVM_CREATE_VCPU指令字为VM创建VCPU后KVM模块将创建kvm_vcpu结构体其中包含了VCPU相关的信息重要字段说明如下
struct kvm_run { __u8 request_interrupt_window; //向VCPU注入一个中断让VCPU做好相关准备工作 … __u8 ready_for_interrupt_injection; //响应request_interrupt_window的中断请求当设置时说明VCPU可以接收中断。 __u8 if_flag; //中断使能标识如果使用了APIC则无效 struct { __u64 hardware_exit_reason; //当发生VM-Exit时该字段保存了由于硬件原因导致VM-Exit的相关信息。 } hw; struct {
#define KVM_EXIT_IO_IN 0
#define KVM_EXIT_IO_OUT 1 __u8 direction; __u8 size; /* bytes */ __u16 port; __u32 count; __u64 data_offset; /* relative to kvm_run start */ } io; //当由于IO操作导致发生VM-Exit时该结构体保存IO相关信息。 …
};
2.4.4 kvm_x86_ops结构体 kvm_x86_ops结构体只包含了针对具体CPU架构进行虚拟化时的函数指针在kvm-intel.ko和kvm-amd.ko模块中提供了不同的接口和实现。在KVM初始化和正常运行过程中将使用相关接口进行实际的硬件操作。主要包括如下几种类型的操作
VMM状态初始化
VCPU管理
中断管理
寄存器管理
时钟管理 Kvm_x86_ops结构体在intel架构中初始化为
static struct kvm_x86_ops vmx_x86_ops { .cpu_has_kvm_support cpu_has_kvm_support, .disabled_by_bios vmx_disabled_by_bios, .hardware_setup hardware_setup, .hardware_unsetup hardware_unsetup, .check_processor_compatibility vmx_check_processor_compat, .hardware_enable hardware_enable, .hardware_disable hardware_disable, .cpu_has_accelerated_tpr report_flexpriority, .vcpu_create vmx_create_vcpu, .vcpu_free vmx_free_vcpu, .vcpu_reset vmx_vcpu_reset, .prepare_guest_switch vmx_save_host_state, .vcpu_load vmx_vcpu_load, .vcpu_put vmx_vcpu_put, .set_guest_debug set_guest_debug, .get_msr vmx_get_msr, .set_msr vmx_set_msr, .get_segment_base vmx_get_segment_base, .get_segment vmx_get_segment, .set_segment vmx_set_segment, .get_cpl vmx_get_cpl, .get_cs_db_l_bits vmx_get_cs_db_l_bits, .decache_cr0_guest_bits vmx_decache_cr0_guest_bits, .decache_cr4_guest_bits vmx_decache_cr4_guest_bits, .set_cr0 vmx_set_cr0, .set_cr3 vmx_set_cr3, .set_cr4 vmx_set_cr4, .set_efer vmx_set_efer, .get_idt vmx_get_idt, .set_idt vmx_set_idt, .get_gdt vmx_get_gdt, .set_gdt vmx_set_gdt, .cache_reg vmx_cache_reg, .get_rflags vmx_get_rflags, .set_rflags vmx_set_rflags, .fpu_deactivate vmx_fpu_deactivate, .tlb_flush vmx_flush_tlb, .run vmx_vcpu_run, .handle_exit vmx_handle_exit, .skip_emulated_instruction skip_emulated_instruction, .set_interrupt_shadow vmx_set_interrupt_shadow, .get_interrupt_shadow vmx_get_interrupt_shadow, .patch_hypercall vmx_patch_hypercall, .set_irq vmx_inject_irq, .set_nmi vmx_inject_nmi, .queue_exception vmx_queue_exception, .interrupt_allowed vmx_interrupt_allowed, .nmi_allowed vmx_nmi_allowed, .get_nmi_mask vmx_get_nmi_mask, .set_nmi_mask vmx_set_nmi_mask, .enable_nmi_window enable_nmi_window, .enable_irq_window enable_irq_window, .update_cr8_intercept update_cr8_intercept, .set_tss_addr vmx_set_tss_addr, .get_tdp_level get_ept_level, .get_mt_mask vmx_get_mt_mask, .exit_reasons_str vmx_exit_reasons_str, .gb_page_enable vmx_gb_page_enable, .set_tsc_khz vmx_set_tsc_khz, .write_tsc_offset vmx_write_tsc_offset, .adjust_tsc_offset vmx_adjust_tsc_offset, .compute_tsc_offset vmx_compute_tsc_offset,
};
2.5 KVM内核模块重要流程分析
2.5.1 KVM初始化流程 如之前描述KVM中主要包括kvm.kokvm-intel.ko和kvm-amd.ko 3个模块主要的初始化流程如下 主要过程包括两部分
通过module_init宏执行平台相关的模块(kvm-intel.ko或kvm-amd.ko)初始化代码进入kvm_init执行kvm核心初始化工作
2.5.2 虚拟机创建流程 内核函数kvm_create_vm(linux/virt/kvm/kvm_main.c)用于创建虚拟机用户态Qemu-kvm通过如下过程最终进入内核的此函数中由该函数完成虚拟机的创建。
ioctl(KVM_CREATE_VM..)
- kvm_dev_ioctl
- kvm_dev_ioctl_create_vm
- kvm_create_vm kvm_create_vm主要完成KVM虚拟机结构体的创建、KVM的MMU操作接口的安装、KVM的IO总线、事件通道的初始化等操作主要流程如下 kvm_arch_create_vm()初始化kvm结构体hardware_enable_all()针对每一个CPU调用kvm_x86_ops中硬件相关的函数进行硬件使能主要设置相关寄存器和标记使CPU进入虚拟化相关模式中(如Intel VMX)初始化memslots结构体信息初始化BUS总线结构体信息初始化事件通知信息和内存管理相关结构体信息将新创建的虚拟机加入KVM的虚拟机列表
2.5.3 VCPU创建流程 在通过ioctl(KVM_CREATE_VM…)创建虚拟机并获得相应的fd后可以通过ioctl(KVM_CREATE_VCPU…)创建VCPU(相关操作通常在Qemu-kvm用户态进程中进行)。
ioctl(KVM_CREATE_VCPU..)
- kvm_vm_ioctl
- kvm_dev_ioctl_create_vcpu VCPU的创建过程只要是创建VCPU描述符即kvm_vcpu结构体该结构体内容较多包括硬件相关的内容。具体实现由内核函数kvm_dev_ioctl_create_vcpu()完成主要流程如下 kvm_arch_vcpu_create()创建kvm_vcpu结构体具体实现跟架构相关直接调用kvm_x86_ops中的create_cpu方法执行主要完成相关寄存器和CPUID的初始化为调度运行做准备kvm_arch_vcpu_setup()初始化kvm_vcpu结构体判断当前VCPU数量是否达到上限如果是则销毁刚创建的实例判断当前VCPU是否已经加入了某个KVM主机如果是则销毁刚创建的实例create_vcpu_fd()创建vcpu_fd将创建的kvm_vcpu结构体加入kvm的VCPU数组中增加online vcpu数量释放锁结束
2.5.4 VCPU启动运行流程 在VM和VCPU创建好并完成初始化后就可以调度该VCPU运行了。VCPU(虚拟机)的运行主要任务是要进行上下文切换主要就是相关寄存器、APIC状态、TLB等通常上下文切换的过程如下
保存当前的上下文使用kvm_vcpu结构体中的上下文信息加载到物理CPU中执行kvm_x86_ops中的run_vcpu函数调用硬件相关的指令(如VMLAUNCH)进入虚拟机运行环境中
Qemu-kvm可以通过ioctl(KVM_RUN…)使虚拟机运行。过程如下
ioctl(KVM_CREATE_VCPU..)
- kvm_vcpu_ioctl
- kvm_arch_vcpu_ioctl_run 具体由内核函数kvm_arch_vcpu_ioctl_run完成相关工作。主要流程如下 Sigprocmask()屏蔽信号防止在此过程中受到信号的干扰设置当前VCPU状态为KVM_MP_STATE_UNINITIALIZED配置APIC和mmio相关信息将VCPU中保存的上下文信息写入指定位置然后的工作交由__vcpu_run完成__vcpu_run最终调用vcpu_enter_guest该函数实现了进入Guest并执行Guest OS具体指令的操作 vcpu_enter_guest最终调用kvm_x86_ops中的run函数运行。对应于Intel平台该函数为vmx_vcpu_run(设置Guest CR3和其他寄存器、EPT/影子页表相关设置、汇编代码VMLAUNCH切换到非根模式执行Guest目标代码)Guest代码执行到敏感指令或因其他原因(比如中断/异常)VM-Exit退出非根模式返回到vcpu_enter_guest函数继续执行vcpu_enter_guest函数中会判断VM-Exit原因并进行相应处理处理完成后VM-Entry到Guest重新执行Guest代码或重新等待下次调度
3 QEMU-KVM
3.1 QEMU与KVM的关系简述 QEMU是一套由Fabrice Bellard编写的模拟处理器的自由软件其代码地址 http://git.qemu.org/qemu.git 。它是一个完整的可以单独运行的软件可以独立模拟出整台计算机包括CPU内存IO设备通过一个特殊的“重编译器”对特定的处理器的二进制代码进行翻译从而具有了跨平台的通用性。QEMU有两种工作模式系统模式可以模拟出整个电脑系统另一种是用户模式可以运行不同与当前硬件平台的其他平台上的程序比如在x86平台上运行跑在ARM平台上的程序。在0.9.1及之前版本还可以使用kqemu加速器可以理解为QEMU的一个插件用来提高QEMU的翻译性能支持Windows平台但1.0以后版本就只能使用qemu-kvm只支持Linux进行加速了1.3版本后QEMU和QEMU-KVM合二为一了。 QEMU-KVM从前面对KVM内核模块的介绍知道它只负责CPU和内存的虚拟化加载了它以后用户就可以进一步通过工具创建虚拟机KVM提供接口但仅有KVM还不够因KVM是运行在内核态用户是无法直接操控的故还须有个运行在用户空间的管理工具来配合KVM的开发者选择了比较成熟的开源虚拟化软件QEMU来作为这个虚拟资源管理工具并对其进行了修改最后形成了QEMU-KVM。 在QEMU-KVM中KVM运行在内核空间QEMU运行在用户空间来实际模拟创建、管理各种虚拟硬件。QEMU将KVM整合了进来通过/ioctl 调用 /dev/kvm从而将CPU指令处理部分交给内核态KVM模块来做以此提升虚拟化性能。但KVM只实现了CPU和内存的虚拟化不能虚拟其他硬件设备因此qemu还提供模拟IO设备磁盘、网卡、显卡等等功能故KVM加上QEMU才是完整意义上的KVM服务器虚拟化解决方案。
3.2 QEMU架构 QEMU的架构如下图所示由几个基本的组件组成 QEMU架构图 如图所示QEMU由以下几个部分组成
Hypervisor虚拟化控制模块Tiny Code GeneratorTCG负责在虚拟机器代码和宿主机代码之间进行转换软件内存管理单元MMU负责处理内存访问磁盘子系统负责处理不同的磁盘映像格式设备子系统负责处理网卡和其他硬件设备
3.2.1 Hypervisor管理程序 Hypervisor虚拟机管理程序是一种创建和运行虚拟机的虚拟机监视器。 QEMU中的Hypervisor虚拟机管理程序从磁盘映像加载二进制机器代码使用TCG将其转换为本机机器代码连接到虚拟或实际设备并启动软件MMU然后开始在磁盘映像中虚拟操作系统。其中TCG和软件MMU是实现虚拟化CPU和内存的关键。 而集成KVM后QEMU将使用Linux内核的KVM功能以全虚拟化模式执行虚拟机。KVM基本上是Linux内核中的Hypervisor虚拟机管理程序。它可以并行运行多个操作系统。QEMU可以在KVM中启动一个新线程以执行虚拟操作系统然后由KVM控制执行。从这部分来说KVM的Hypervisor虚拟机管理程序替换掉了QEMU的Hypervisor虚拟机管理程序。
3.2.2 微代码生成器TCG 在QEMU中Tiny Code GeneratorTCG将基于源处理器编译的机器代码转换为目标虚拟机所运行处理器的机器代码如x86机器代码。单从CPU架构与指令集格式的角度来说是不可能在一类处理器例如Intel x86上运行基于另一类处理器例如ARM指令集所编译的机器代码的例如想在x86处理器上执行基于ARM指令集编译的程序。因此引入中间转换环节对不同处理器指令集架构ISA进行翻译和转换是实现虚拟化通用性的可预见的技术途径和解决方案。 在Tiny Code GeneratorTCG中这些已经翻译的代码块放在转换缓存中并通过跳转指令将源处理器的指令集和目标处理器的指令集链接在一起。当Hypervisor虚拟机管理程序在执行代码时存放于转换缓存中的链接指令可以跳转到指定的代码块并且执行过程中可以在不同的已翻译代码块上切换运行直到需要翻译新块为止。在执行的过程中如果遇到了需要翻译的代码块执行动作就会暂停并回会跳回到Hypervisor虚拟机管理程序Hypervisor虚拟机管理程序就会协调TCG对需要进行二进制翻译的源处理器指令集进行转换并存储到转换缓存中以便继续程序执行。 下图显示了QEMU的TCG工作原理 微代码生成器工作原理 在TCG运行过程中存在一个小缺点即它无法识别并正确运行自修改代码因为它没有将修改后的代码页进行标记再次运行时需要重新翻译。这影响了QEMU的二进制运行效率当然从另外一个角度来说这也增加了一定的运行安全性。自修改代码在软件世界中容易被漏洞利用特别是缓冲区溢出攻击等内存损坏漏洞这些漏洞利用威胁代理例如后门提供的特殊代码覆盖易受攻击的应用程序代码。如果已经被覆盖的代码已经被运行并因此被缓存就会导致TCG运行和翻译失败从而导致程序复现异常或崩溃由此规避了此类漏洞攻击。 此外在翻译的过程中如果新处理器使用的寄存器多于x86处理器并且具有许多复杂指令那么对TCG进行编程以处理和适应新的CPU仿真就需要更多的工作量。目前来说QEMU所支持的大部分处理器都拥有部分相同的指令集例如“MOV”指令几乎存在于所有处理器中并且可以简单地复制。除非CPU寄存器中存在一些位大小差异例如在32位处理器上模拟64位处理器可能需要新增许多额外的指令这也需要更多时间在TCG转换器中进行编程。 在QEMU的源代码中有一个名为tcg的子目录其中包含将机器指令转换为相应的x86机器指令的代码。此代码是一个用C编写的简单翻译状态机还有用于内存访问和跳转的特殊转换因为它们可以生成对软件内存管理单元的调用。而虚拟化CPU和内存也往往是在一起的因为从本质上来说CPU的工作就是对内存的区域数据进行搬运CPU是内存的搬运工。在QEMU保护代码块之外的其他内存区域机器代码中的跳转和分支也必须确保到达正确的存储器地址。 所以通过二进制翻译技术针对CPU的仿真和虚拟化就非常简单了。TCG和Hypervisor虚拟机管理程序能够实现基于CPU的仿真。其中CPU仿真流程如下图所示 从上图我们可以看到针对CPU的仿真和虚拟化本质上就是将源处理器的指令集转换成目标处理器的指令集。CPU仿真和虚拟化就是通过中间的转换和翻译来实现的由此针对CPU虚拟化的第一种技术就完全实现了。这种二进制翻译技术是最早的CPU虚拟化技术诞生了VMware这样的虚拟化巨头也诞生了QEMU这样的开源虚拟化鼻祖。 3.2.3 硬件设备虚拟化 此部分内容参见2.3节。 3.2.4 磁盘映像虚拟化 QEMU可以处理几种不同的磁盘映像格式。首选格式为raw或qcow2。Raw是一种非常简单的格式它将文件系统中的字节逐字节存储在文件中。大多数其他仿真器都支持此格式。Qcow2是QEMU自己的图像格式对小图像很有用。并且支持磁盘映像压缩以及捕获磁盘映像状态的快照。还支持另外两种格式在VirtualBox中使用的vdi和在VMWare中使用的vmdk。 QEMU的磁盘映像通过其存储IO协议栈来进行支持其存储协议栈如下图所示 QEMU存储协议栈 从QEMU的存储协议栈来说应用程序和虚拟机内核的工作类似于裸机。虚拟机通过仿真硬件与QEMU交互并将IO执行情况的控制流和数据流交互给QEMUQEMU代表虚拟机对磁盘镜像文件执行I / O操作。而从主机内核层面上主机内核会将虚拟机I / O视为一种用户空间的应用程序IO请求进行正常的执行处理。
3.2.5 软件MMU 传统处理器中的内存管理单元MMU处理对计算机内存位置的访问。当处理器想要访问某个存储器地址时MMU获取该地址的内容此内容可以来自处理器芯片上的本地快速缓存也可来自随机存取存储器RAM或来自光盘它甚至可以做出一些关于缓存某些内存位置的控制决定。 QEMU有一个基于软件的MMU其工作方式与硬件MMU类似。它使用地址转换缓存其中包含访客地址、主机地址和偏移值以提高转换速度。它还允许智能链接代码块以便在没有内存故障的情况下实现更快的执行其中必须重新加载和重新转换内存块。 在寻找在QEMU中运行的虚拟机的漏洞时软件MMU是否正在进行翻译和正确放置块会是其测试和Fuzz的重点。 参考链接
虚拟化技术发展编年史
Linux内核态、用户态简介与IntelCPU特权级别--Ring0-3_weixin_30784501的博客-CSDN博客
云计算技术 — 云计算技术发展编年史_烟云的计算-CSDN博客_云计算编年史
虚拟化技术原理CPU、内存、IO_joneslee的博客-CSDN博客_虚拟化原理
虚拟化原理介绍
虚拟化原理介绍 - 张朝锋 - 博客园
CPU硬件辅助虚拟化技术
CPU硬件辅助虚拟化技术 - 又是火星人 - 博客园
五种主流的虚拟化技术_远有青山-CSDN博客_虚拟化技术
Vmware虚拟化概念原理_曹世宏的博客-CSDN博客_vmware虚拟化
Xen原理
Xen原理 - 张朝锋 - 博客园
Xen虚拟化技术原理_IT人生活的技术博客_51CTO博客
Xen虚拟化基本原理详解
Xen虚拟化基本原理详解 - stardsd - 博客园
Xen工作原理_为幸福写歌的博客-CSDN博客_xen原理
Xen_百度百科