汇总在虚拟化世界里经常会涉及到的术语或缩写等。
术语 | 含义 |
---|---|
VM | Virtual Machine,虚拟机 |
VMM | 在系统虚拟化中,管理全局物理资源的软件叫作虚拟机监控器(Virtual MachineMonitor,VMM),VMM之于虚拟机就如同操作系统之于进程,VMM利用时分复用或者空分复用的办法将硬件资源在各个虚拟机之间进行分配 |
Xen | 2001年剑桥大学开发了Xen,早期的开源虚拟化方案,出现在各种硬件虚拟化技术之前。现在远不如KVM |
KVM | 2006年,以色列的初创公司Qumranet利用Intel的硬件虚拟化技术在Linux内核上开发了KVM(Kernel Virtual Machine),是一款非常成功的虚拟化VMM,称为云计算的基石 |
QEMU | Quick Emulator,模拟器 |
VMX | Virtual Machine eXtensions. intel在x86 cpu架构基础上增加VMX架构来实现CPU的硬件虚拟化 VMX架构下有:VMM(虚机监控器) 和 VM(虚机) CPU支持VT-x技术的标志为vmx,Linux中/proc/cpuinfo可查看 |
VT-x | Intel virtualization,intel虚拟化技术。可在BIOS中设置是否启用VT-x技术 所以可以认为 VMX = VT-x,都是指 intel 硬件虚拟化技术 |
虚拟化解决方案 | |
---|---|
VMware Workstation | VMware最早的产品,至今仍有大量用户使用。VMware Workstation能够很方便地在PC上构建一个虚拟机,用户可以在其上安装各种操作系统,能够非常方便地完成多种任务,比如跨平台的开发测试 |
VirtualBox | 最早由一个德国公司开发,后来被甲骨文收购。它的优点是性能不错并且开源,能够很方便地用来实现一些定制需求 |
HyperV | 微软提供的虚拟化解决方案,微软用它来构建自己的云计算平台 |
Xen | Xen作为早期的开源VMM,其诸多思想直到今天也在影响着虚拟化社区 |
VMX架构中,每个VM都是一个虚拟机实例,能够支持操作系统以及各种软件栈和应用程序,VM本身不会意识到其处在虚拟化环境中。每一个VM都相互独立,有自己独立的CPU、内核、中断和设备等,这些资源都是VMM提供的。
VMM需要对各个VM进行管理,包括创建、配置、删除VM实例、为其分配资源、确保各个VM之间的隔离与独立,还需要处理VM对资源的访问、确保公平,所有这些都需要VMM运行的权限高于VM,只有这样,VMM才能够实现对整个系统资源和对VM的管理。但是传统上,操作系统内核已经运行在ring0最高级了,所以为了让CPU支持VMM和VM两种软件,Intel为CPU引入了一种新的模式,叫作VMX operation。
VMM执行的模式叫作VMX root operation模式,VM执行的模式叫作VMX non-root operation模式,这两种模式之间的转换叫作VMX转换。从VMX root转换到VMXnon-root叫作VM Entry,而从VMX non-root转换到VMX root则叫作VM Exit。
每种模式都有自己的ring0和ring3结构。VMX operation与CPU特权级是正交的。在普通的QEMU/KVM架构中,QEMU等用户态软件以及KVM等宿主机的内核都运行在VMX root模式下,在虚拟机也有自己的ring0和ring3。当然,VM中执行指令的行为肯定不能完全与VMM中相同,否则也用不着VMX架构了。在VMX non-root模式中,各种指令是严格受到限制的,执行一些特殊的指令(如之前所说的影响系统全局的指令)或者发生一些特殊的事件都会导致VM Exit,使VM退出到VMM。
CPU在运行包括QEMU在内的普通进程和宿主机的操作系统内核时,CPU处在VMX root模式。CPU在运行虚拟机中的用户程序和操作系统代码的时候处于VMX non-root模式。需要注意的是,CPU的运行模式与CPU运行时的特权等级是相互正交的,虚拟机在VMXroot模式和VMX non-root模式下都有ring 0到ring 3四个特权级别。
可以按上图中的色框线来理解QEMU/KVM架构,整体可以分为3大部分:
从图中可以看出,QEMU的主要任务是模拟芯片组,创建CPU线程来表示虚拟机的CPU执行流,在QEMU虚拟地址空间中分配空间作为虚拟机的物理地址,创建用户指定的虚拟设备等,QEMU通过监听事件的方式来响应KVM转过来的虚拟机请求。
虚拟机中本身也有自己的操作系统内核和应用层,虚拟机的一个CPU对应QEMU进程中的一个vCPU线程,可以被host主机正常调度。
虚拟机中的物理内存对应QEMU进程的虚拟内存,VM通过guest os的页表管理系统完成VM虚拟地址到VM物理地址的转换,最终经由KVM的页表完成VM物理地址到host物理地址的转换。
Linux内核中的KVM驱动,主要做两件事:
QEMU与KVM配合起来共同完成了虚拟机各个组件的虚拟化,比如CPU虚拟化、内存虚拟化、设备虚拟化、中断虚拟化等。
CPU虚拟化
QEMU创建vCPU线程,初始化时就设置好vCPU寄存器的值,然后通过KVM API,运行虚拟机。
KVM需要截获虚拟机中的敏感指令,当虚拟机的代码是敏感指令或者满足一定的退出条件时,CPU从VMX non-root模式退出到KVM(VMX root模式),这叫 VM Exit(类似于用户态陷入内核态)。VMX Exit后首先在KVM中处理,如果KVM无法处理,再转给QEMU处理。当QEMU或KVM处理好VM Exit事件后,又将CPU置于VMX non-root模式,这叫VM Entry。虚拟机就这样不停的VM Exit和VM Entry。
KVM中用一个结构来记录虚拟机的VM Exit和VM Entry状态,叫VMCS。
内存虚拟化
QEMU在初始化的时候会通过mmap系统调用分配虚拟内存空间作为虚拟机的物理内存,QEMU在不断更新内存布局的过程中会持续调用KVM接口通知内核KVM模块虚拟机的内存分布。
在CPU支持EPT(Extended Page Table,扩展页表)之前,虚拟机通过影子页表实现从虚拟机虚拟地址到宿主机物理地址的转换,是一种软件实现。当CPU支持EPT之后,CPU会自动完成虚拟机物理地址到宿主机物理地址的转换。虚拟机在第一次访问内存的时候就会陷入到KVM,KVM会逐渐建立起所谓的EPT页面。这样虚拟机的虚拟CPU在后面访问虚拟机虚拟内存地址的时候,首先会被转换为虚拟机物理地址,接着会查找EPT页表,然后得到宿主机物理地址,整个过程由硬件完成,很高效。
设备虚拟化
虚拟化的一个烦琐任务就是为虚拟机提供大量的设备支持,如同Linux内核中最多的代码是设备驱动,QEMU最多的代码是设备模拟。
QEMU在初始化过程中会创建好模拟芯片组和必要的模拟设备,包括南北桥芯片、PCI根总线、ISA根总线等总线系统,以及各种PCI设备、ISA设备等。
三种设备虚拟化技术方案:
中断虚拟化
操作系统通过写设备的I/O端口或者MMIO地址来与设备交互,设备通过发送中断来通知虚操作系统事件。如下图所示。QEMU在初始化主板芯片的时候初始化中断控制器。QEMU支持单CPU的Intel 8259中断控制器以及SMP的I/O APIC(I/O Advanced Programmable Interrupt Controller)和LAPIC(Local Advanced Programmable Interrupt Controller)中断控制器。传统上,如果虚拟外设通过QEMU向虚拟机注入中断,需要先陷入到KVM,然后由KVM向虚拟机注入中断,这是一个非常费时的操作,为了提高虚拟机的效率,KVM自己也实现了中断控制器Intel 8259、I/O APIC以及LAPIC。用户可以有选择地让QEMU或者KVM模拟全部中断控制器,也可以让QEMU模拟Intel 8259中断控制器和I/O APIC,让KVM模拟LAPIC。QEMU/KVM一方面需要完成这项中断设备的模拟,另一方面需要模拟中断的请求。中断请求的形式大体上包括传统ISA设备连接Intel 8259中断控制器产生的中断请求,PCI设备的INTx中断请求以及MSI和MSIX中断请求。
/*vl.c,qemu主循环函数简化版*/
void qemu_main_loop(void)
{
while (!main_loop_should_exit()) {
main_loop_wait(false);
}
}
QEMU-KVM架构中,一个QEMU进程代表一个虚拟机。QEMU会有若干个线程,其中对于每个CPU会创建一个线程,还有其他的线程,如VNC线程、I/O线程、热迁移线程。
QEMU主事件循环所在的线程由于会不断监听各种I/O事件,所以被称为I/O线程。现在的I/O线程通常是指块设备层面的单独用来处理I/O事件的线程。每一个CPU都会有一个线程,通常叫作VCPU线程,其主要的执行函数是kvm_cpu_exec。QEMU为了完成其他功能还会有一些辅助线程,如热迁移时候的migration线程、支持远程连接的VNC和SPICE线程等
线程模型通常使用QEMU大锁进行同步,获取锁的函数为 qemu_mutex_lock_iothread,解锁函数为 qemu_mutex_unlock_iothread。cpus.c中的static变量qemu_global_mutex是BQL用于main loop的锁。
x86架构中,qemu-kvm架构创建vcpu线程大体流程如下,kvm_init_vcpu()函数中通过KVM_CREATE_VCPU字段ioctl下发给内核kvm创建vcpu线程,kvm_cpu_exec()函数通过ioctl下发KVM_RUN字段给内核kvm模块进行vcpu run运行。
vnc_init_func对VNC模块进行初始化,经过vnc_display_init->vnc_start_worker_thread的调用最终创建VNC线程,VNC线程用来与VNC客户端进行交互。
设备模拟过程中可能会占用QEMU的大锁,所以如果是用磁盘类设备进行读写,会导致占用该锁较长时间。为了提高性能,会将这类操作单独放到一个线程中去。QEMU抽象出了一个新的类型TYPE_IOTHREAD,可以用来进行I/O线程的创建。
QEMU还会有其他线程,比如说热迁移线程以及一些设备模拟自己创建的线程。
如同Linux内核中的大锁,BQL会对QEMU虚拟机的性能造成很大影响。早期的QEMU代码在握有BQL时做的事情很多,QEMU多线程的主要动力是减少QEMU主线程的运行时间,QEMU在进行一些设备模拟的时候,VCPU线程会退出到QEMU,抢占QEMU大锁,如果这个时候有其他线程占据大锁,再做长时间的工作就会导致VCPU被挂起比较长的时间,所以将一些没有必要占据QEMU大锁的任务放到单独线程进行处理就能够增加VCPU的运行时间,这也是QEMU社区在多线程方向的努力方向,即尽量将任务从QEMU大锁中拿出来。