在硬件虚拟化技术的支持下,内核的KVM模块和QEMU的设备模拟协同工作,就构成了一整套与物理计算机系统完全一致的虚拟化的计算机软硬件系统。
硬件平台
一般来说,使用支持硬件辅助虚拟化(如Intel的VT-x)的硬件平台即可。在第5章中介绍的一些特性(如AVX、SMEP、VT-d等)需要某些特定的CPU或芯片组的支持。
在BIOS中打开VT-x和VT-d的支持。
KVM内核
下载Linux内核源代码 或者 下载KVM源代码(kvm.git也包含了其他内核源码),再配置、编译、安装。
qemu-kvm
下载qemu-kvm源代码,再编译、安装。
QEMU命令行开启KVM加速功能
在QEMU monitor中运行“info kvm”命令来查看是否显示”kvm support: enabled”(详见3.6节).
若使用的并非qemu-kvm而是普通的QEMU,有可能KVM没有被默认打开,这时候需要在QEMU启动命令上加上“-enable-kvm”参数。
另外,如果已经安装了支持KVM的Linux发行版,则不一定需要自己重新编译内核(包括KVM模块)和用户态程序qemu-kvm. 如果已经安装了RHEL6.3系统且选择了其中的虚拟化组件,则只需检查当前内核是否支持KVM(查看/boot/config-xx文件中的KVM相关配置),以及kvm和kvm_intel模块是否被正确加载(lsmod | grep kvm),然后找到qemu-kvm的命令行工具(/usr/libexec/qemu-kvm),就用这个qemu-kvm命令行工具来进行后面的具体实践以便了解KVM即可。只需将本书中的“qemu-system-x86_64”命令替换为系统中实际的qemu-kvm的路径即可。
QEMU提供对CPU的模拟,展现给客户机一定的CPU数目和CPU的特性;在KVM打开的情况下,客户机中的CPU指令的执行由硬件处理器的虚拟化功能(如Intel VT-x和AMD AMD-v)来辅助执行,具有很高的执行效率。
在KVM环境中,每个客户机都是一个标准的Linux进程(QEMU进程),而每一个vCPU在宿主机中是QEMU进程派生的一个普通线程。
在普通的Linux系统中,进程一般有2种执行模式:内核模式和用户模式。而在KVM环境中,还有第3种模式:客户模式。
vCPU在三种执行模式下的不同分工如下:
(1)用户模式(User Mode)
主要处理I/O的模拟和管理,由QEMU的代码实现。
(2)内核模式(Kernel Mode)
主要处理特别需要高性能和安全相关的指令,如处理客户模式到内核模式的转换,处理客户模式下的I/O指令或其他特权指令引起的退出(VM-Exit),处理影子内存管理(shadow MMU).
(3)客户模式(Guest Mode)
主要执行Guest中的大部分指令,I/O和一些特权指令除外(它们会引起VM-Exit,被Hypervisor截获并模拟)。
vCPU在KVM中的这3种执行模式下的转换如图4-1所示。
在KVM环境中,整个系统的基本架构如图4-2所示。
SMP (Symmetric Multi-Processor,对称多处理器)
硬件并行的技术:多CPU、多核、超线程
cat /proc/cpuinfo
在qemu监视器中运行:(qemu) info cpus
若要对客户机进行CPU的热插拔(hot-plug),则需要在启动客户机的qemu-kvm命令行参数中加上“maxcpus=num”这个选项。(注:当时写作时,hot-plug功能有一些bug,处于不可用状态。参考 http://www.linux-kvm.org/page/CPUHotPlug)
KVM允许客户机过载使用(over-commit)物理资源,即允许为客户机分配的CPU和内存数量多于物理上实际存在的资源。
CPU的过载使用,是让一个或多个客户机使用vCPU的总数量超过实际拥有的物理CPU的数量。QEMU会启动更多的线程为客户机提供服务,这些线程也是被Linux内核调度运行在物理CPU硬件上。
关于CPU的过载使用,推荐的做法是对多个单CPU的客户机使用over-commit. 比如,在拥有4个逻辑CPU的宿主机中,运行多于4个的客户机,其中每个客户机都分配一个vCPU.
关于CPU的过载使用,最不推荐的做法是让某一个客户机的vCPU的数量超过物理系统上存在的CPU数量。比如,在拥有4个逻辑CPU的宿主机上,同时运行一个或多个客户机,其中每个客户机的vCPU的数量多于4个。
KVM允许CPU的过载使用,但并不推荐在实际的生产环境中过载使用CPU. 如需在生产环境使用,需要在部署前进行严格的性能和稳定性测试。
每一种虚拟机管理程序(VMM(Virtual Machine Monitor)或称Hypervisor)都会定义自己的策略,让客户机看起来有一个默认的CPU类型。有的Hypervisor会简单地将宿主机中的CPU类型和特性直接传递给客户机,而QEMU/KVM在默认情况下会向客户机提供一个名为qemu32或qemu64的基本CPU模型。QEMU/KVM的这种策略会带来一些好处,如可以对CPU特性提供一些高级的过滤功能,还可以将物理平台根据提供的基本CPU模型进行分组,从而让客户机在同一组硬件平台上的动态迁移更加平滑和安全。
通过如下命令可以查看当前的QEMU支持的所有的CPU模型:
qemu-system-x86_64 -cpu ?
其中,加了方括号的“qemu64”等CPU模型是QEMU中自带的(在 target-i386/cpu.c的结构体数组builtin_x86_defs[]中定义);
而未加方括号的”SandyBridge”等是在配置文件sysconfigs/target/cpus-x86_64.conf中配置的(安装后的路径一般为 /usr/local/share/qemu/cpus-x86_64.conf).
进程的处理器亲和性(Processor Affinity),即CPU的绑定设置,是指将进程绑定到特定的一个或多个CPU上去执行,而不允许将进程调度到其他的CPU上。Linux内核对进程的调度算法也是遵守进程的处理器亲和性设置的。
设置进程的处理器亲和性的好处是可以减少进程在多个CPU之间切换运行时带来的缓存命中失效(cache missing),因而可以带来性能的提高。但是问题是,也可能破坏原有SMP系统中各个CPU的负载均衡,进而导致整个系统的进程调度变得低效。
每个vCPU是宿主机中的一个普通的QEMU线程,可以使用taskset工具对其设置处理器亲和性,使其绑定到某一个或几个固定的CPU上去调度。
在虚拟化环境中,有时有必要将客户机的QEMU进程或线程绑定到固定的逻辑CPU上。示例见P75.
在Linux内核启动的命令行加上 “isolcpus=” 参数,可以实现CPU的隔离。
命令
ps -eLo psr
ps -eLo ruser,pid,ppid,lwp,psr,args
-e 参数: 用于显示所有的进程
-L 参数: 用于将线程显示出来(LWP,light weight process)
-o 参数: 表示以用户自定义格式输出
psr: 当前分配给进程运行的处理器的编号
lwp: 线程ID
ruser: 运行进程的用户
pid: 进程ID
ppid: 父进程ID
args: 所运行的命令及其参数
启动一个拥有2个vCPU的客户机,并将其vCPU绑定到宿主机的2个CPU上:
qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize
查看代表vCPU的QEMU线程:
ps -eLo ruser,pid,ppid,lwp,psr,args | grep qemu | grep -v grep
绑定代表整个客户机的QEMU进程,使其运行在CPU2上:
taskset -p 0x4
绑定第一个vCPU的线程,使其运行在CPU2上:
taskset -p 0x4
绑定第二个vCPU的线程,使其运行在CPU3上:
taskset -p 0x8
对于taskset命令,此处使用的语法是: taskset -p
其中,mask是一个掩码,代表了处理器亲和性。转化为二进制表示后,其值从最低位到最高位,分别代表了第一个到最后一个逻辑CPU.
如何查看vCPU和QEMU线程之间的关系呢?可以切换到QEMU monitor中(通过Ctrl+Alt+2),运行”info cpus”命令进行查看。
在KVM中,一般来说,不推荐手动设置QEMU进程的处理器亲和性来绑定vCPU. 但是在非常了解系统硬件架构的基础上,根据实际需要,可以将其绑定到特定的CPU上,从而提高客户机中的CPU执行效率或实现CPU资源独享的隔离性。
QEMU对客户机分配的内存大小默认为128MB,所以一般都必须手动指定内存大小。方法是:
-m xxx
默认单位是MB.
查看内存使用情况:
free -m
dmesg 命令: 打印或控制内核ring buffer.
EPT(Extended Page Tables,扩展页表),属于Intel的第二代硬件虚拟化技术,它是针对内存管理单元MMU的虚拟化扩展。
在虚拟环境下,内存使用需要2层的地址转换,即客户机应用程序可见的客户机虚拟地址(GVA,Guest Virtual Address)到客户机物理地址(Guest Physical Address,GPA)的转换,再从客户机物理地址(GPA)到宿主机物理地址(Host Physical Address, HPA)的转换。其中,前一个转换有客户机OS来完成,而后一个转换由Hypervisor来负责。
在EPT特性出现前,影子页表(Shadow Page Tables)是从软件上维护了从客户机虚拟地址(GVA)到宿主机物理地址(HPA)之间的映射。
影子页表虽然避免了2次转换,但是其实现非常复杂,导致其开发、调试和维护都比较困难。
x86(包括x86-32和x86_64)架构的CPU默认使用4KB大小的内存页面,但是它们也支持较大的内存页,如x86-64系统就支持2MB大小的页。
如果在系统中使用了huge page,则内存页的数量会减少,从而需要更少的页表(page table),节约了页表所占用的内存数量。
可以将huge page的特性应用到客户机中。qemu-kvm就提供了”-mem-path FILE”参数选项用于使用huge page.
另外,还有一个参数”-mem-prealloc”可以让宿主机在启动客户机的时候就全部分配好客户机的内存。
可以通过宿主机中的如下几个操作让客户机使用huge page:
1. 检查宿主机目前的状态,检查默认的内存大小和内存使用情况:
getconf PAGESIZE
cat /proc/meminfo
挂载hugetlbfs文件系统,命令为:
mount -t hugetlbfs hugetlbfs /dev/hugepages
设置hugepage的数量,命令为:
sysctl vm.nr_hugepages=
启动客户机,使其使用hugepage内存,使用”-mem-path”参数:
qemu-system-x86_64 -m 1024 -smp 2 rhel6u3.img -mem-path /dev/hugepages
查看宿主机中huge page的使用情况,可以看到“HugePages_Free”数量减少。
总的说来,对于内存访问密集型的应用,使用huge page是可以比较明显地提高客户机的性能;
不过,它也有一个缺点,使用huge page的内存不能被换出(swap out),也不能通过ballooning的方式自动增长。
在KVM中内存是允许过载使用的(over-commit)。
一般来说,有如下3种方式来实现过载使用内存:
1. 内存交换(swapping):用交换空间(swap space)来弥补内存的不足;
2. 膨胀(ballooning):通过virio_balloon驱动来实现宿主机Hypervisor和客户机之间的协作;
3. 页共享(page sharing):通过KSM(Kernel Samepage Merging)合并多个客户机进程使用的相同的内存页。
第一种方式最成熟,不过效率较低;ballooning和KSM将在第5章中介绍。
从理论上来说,供客户机过载使用的内存可以达到实际物理内存的几倍甚至几十倍;不过,一般不建议过多地过载使用内存。一方面,交换空间通常是由磁盘来实现的,读写速度比物理内存慢得多,性能不好;另一方面,过多过载内存,也可能导致系统的稳定性降低。