虚拟化是云计算的基石,抛开虚拟化谈云计算无异于缘木求鱼,不得要领。前端
虚拟化简介
虚拟化是一种技术,它是对物理硬件资源的虚拟。经过虚拟化使得应用运行在虚拟化以后的虚拟机上,达到充分利用物理资源的目的。
根据虚拟化的类型可将虚拟化分为 I 型虚拟化和 II 型虚拟化。I 型虚拟化是直接做用于裸机上的,如 Xen 等虚拟化技术。II 型虚拟化是在操做系统之上的虚拟化,如 qemu-kvm,HyperV 等虚拟化技术。
根据虚拟化技术可将虚拟化分为:全虚拟化,半虚拟化和硬件辅助的虚拟化。
全虚拟化
如上图所示,全虚拟化是经过 QEMU 的模拟代码来模拟 Guest 请求的虚拟化。全虚拟化须要通过捕获/拦截/模拟等一系列流程。
物理上的 CPU 做用在不一样的运行级别上,分别是 Ring3,Ring2,一直到 Ring0。对于 kernel 的代码,CPU 是运行在 Ring0 级别,能够执行特权指令,例如访问硬件资源等指令。而,做用在其它级别上时,CPU 是处于受限模式的。对于 QEMU 和 Guest 来讲,CPU 在执行它们的指令时没有用 Ring0 级别去执行,当 Guest 执行 I/O 请求的时候,CPU 会报异常,这个异常会被内核模块的 KVM 捕获到,KVM 将该异常消息放入 I/O sharing page 中,而后经过 /dev/kvm 接口,告知 QEMU 去读取 sharing page 中的异常消息,QEMU 读取到该异常消息,经过本身的模拟代码来驱动内核中相应的设备模块实现 I/O 操做。当操做成功后,把结果放到 I/O sharing page 中,并通知 KVM 内核去读取该操做成功后,把结果,KVM 读取到该结果,将结果发给 Guest 的驱动程序实现整个 I/O 请求。
全虚拟化的指令都须要通过捕获/拦截/模拟的过程,很复杂,效率很低。
半虚拟化
如图所示,半虚拟化是对 Guest 的模块进行修改的虚拟化。如今的半虚拟化一般是基于 virtio 框架的虚拟化。在 Guest 中包含 virtio 的 Front-end,在 Host 上安装 virtio 的 Back-end。当有 I/O 请求时,Guest 上的前端 virtio driver 会直接和 Host 上的 virtio devcie 进行通讯,实现对硬件资源的访问。
相比于 qemu 的全虚拟化,半虚拟化绕过了 QEMU ,从而少了指令捕获/拦截/模拟的过程,效率要更高。
完整的 Guest I/O 流程以下图所示:
硬件辅助的全虚拟化
硬件辅助的全虚拟化最少须要硬件 CPU 支持。Intel VT 技术支持硬件辅助的全虚拟化方案,在使用时须要进入 BIOS 中打开 VT。
在硬件辅助的全虚拟化中,CPU 有两种执行模式,root 模式和 non-root 模式,对于 QEMU + kernel 代码的运行,CPU 是运行在 root 模式,而对于 Guest 代码的执行,CPU 运行在 non-root 模式。在 non-root 模式中,CPU 处于受限状态,只能执行限制的指令,没法执行特权指令。当 Guest 须要执行特权指令时,CPU 会从 non-root 模式切到 root 模式来执行该指令,此时 Guest 会被置于挂起状态。当执行完特权指令以后,退出到 non-root 模式,继续 Guest 的运行。
从 root 到 non-root 模式的切换叫 VM-Entry,从 non-root 到 root 模式的切换叫 VM-Exit。
相比于全虚拟化,硬件辅助的虚拟化不须要逐条翻译 Guest 指令,大部分指令均可以经过 CPU 执行,从而缩短了“流程”,提高了虚拟化的效率。
虚拟化原理
虚拟化从本质上来讲是对 CPU / 内存 以及 I/O 资源的虚拟,使得 Guest 有本身的硬件资源,从 Guest 角度看,它就是一台独立的 server。
CPU 虚拟化
CPU 的虚拟化是经过 KVM 内核模块完成的,KVM 虚拟出 vCPU,Guest 上看到的 socket / core 其实是绑定到 vCPU 上的。
vCPU 有三种模式,kernel / user 和 guest 模式,当 Guest 执行 I/O 相关的操做时,vCPU 是运行在 user 模式下的,当执行非 I/O 的用户空间操做,vCPU 是处在 guest 模式下的,当执行 kernel 代码时,vCPU 是处在 kernel 模式下的。
从 Host 上来看,Guest 就是一个 qemu-kvm 进程,而 Guest 上的 CPU 就是 qemu-kvm 上的一个线程,物理 CPU 会像调用普通进程同样调度它们。正由于 vCPU 是普通线程,因此它并不能真正代替 CPU 去执行 CPU 的指令,真正执行 Guest 上指令的仍是 Host 上的物理 CPU。
从上图能够看到,vCPU 上的操做经过 CPU Scheduler 层层调度到指定的物理 CPU core 上执行,真正完成 Guest 操做的是物理 CPU(这也是为何硬件辅助虚拟化最少须要物理 CPU 支持的缘由)。
固然,能够经过绑核的机制实现 vCPU 和物理 CPU core 的绑定。
内存虚拟化
KVM 实现客户机内存的方式是,利用 mmap 系统调用,在 QEMU 主线程的虚拟地址空间中申明一段连续大小的空间用于客户机物理内存映射。
在 VM 中,进程的虚拟内存(VA)会映射到 VM 的物理内存(PA),而 PA 向下被映射到 Host 的机器内存(MA)。 可是 VM 操做系统不能直接实现 PA 到 MA 的映射,须要 VMM(KVM) 实现 PA 到 MA 的映射。
VMM 实现 PA 到 MA 的映射有两种方式:
1. 软件方式:经过影子页表技术实现内存地址的翻译;
2. 硬件方式:基于 CPU 的硬件辅助虚拟化,如 Intel 的 EPT 和 AMD 的 NPT 技术。
I/O 资源的虚拟化
I/O 资源的虚拟化,分为:
1. 设备模拟:在虚拟机监控器中模拟传统 I/O 设备的特性,好比在 QEMU 中模拟一个 Intel 的网卡和 IDE 硬盘驱动器,在客户机中就暴露为对应的硬件设备。客户机中的 I/O 请求都由虚拟机监控器捕获并模拟执行后返回给客户机(前面全虚拟化方式介绍的就是这种)。
2. 先后端驱动接口:在 VMM 和 VM 之间定义一种全新的适合于虚拟化环境的交互接口,常见的如 virtio 框架下,暴露给客户机的 virtio-net,virtio-blk 等网络和磁盘设备(前端设备),在 QEMU 中实现为相应的后端驱动。
3. 设备直接分配:将 Host 上的物理设备直接分配给 VM,如网卡或硬盘驱动器直接分配给 VM 等。
4. 设备共享分配:设备直接分配的 I/O 虚拟化分配的设备是极为有限的,设备共享分配将物理设备虚拟为多个虚拟机功能接口,该接口独立地分配给客户机使用,从而支持多个 VM 的设备分配。如 SRIOV 就是一种常见的设备共享分配的虚拟化 I/O 方式。
搭建 KVM 虚拟机
这里实现的是 qemu-kvm 硬件辅助的虚拟化方式。首先搭建虚拟化环境,打开 BIOS 中的 VT,编译 kvm 模块进内核,下载安装 qemu 软件,准备好 VMM 环境:
[root@localhost home]# lsmod | grep -i kvm
kvm_intel 170181 5
kvm 554609 1 kvm_intel
irqbypass 13503 15 kvm,vfio_pci
[root@localhost home]# rpm -qa | grep qemu
qemu-img-rhev-2.6.0-28.el7_3.6.x86_64
qemu-kvm-common-rhev-2.6.0-28.el7_3.6.x86_64
qemu-kvm-rhev-2.6.0-28.el7_3.6.x86_64
libvirt-daemon-driver-qemu-2.0.0-10.el7_3.4.x86_64
ipxe-roms-qemu-20160127-5.git6366fa7a.el7.noarch
环境准备好以后建立磁盘镜像,将操做系统加载到磁盘镜像中,经过 qemu-kvm 命令建立 VM:
[lianhua@localhost qemu-kvm]$ /usr/libexec/qemu-kvm -m 1G -smp 4 Virtualized.qcow2 -monitor stdio
QEMU 2.6.0 monitor - type 'help' for more information
(qemu) VNC server running on '::1;5900'
(qemu) info status
VM status: running
(qemu) info cp
cpus cpustats
(qemu) info cpus
* CPU #0: pc=0xffffffff9141c0ee (halted) thread_id=825030
CPU #1: pc=0xffffffff9141c0ee (halted) thread_id=825031
CPU #2: pc=0xffffffff9141c0ee (halted) thread_id=825032
CPU #3: pc=0xffffffff9141c0ee (halted) thread_id=825033
[lianhua@localhost ~]$ ps -lef | grep -i qemu
2 S lianhua 825024 819281 32 80 0 - 537525 poll_s 16:56 pts/0 00:02:16 /usr/libexec/qemu-kvm -m 1G -smp 4 Virtualized.qcow2 -monitor stdio
这里磁盘镜像已经事先制做好了,经过 qemu-kvm 启动 VM,其中 -m 表示 VM 的运行内存;-smp 表示 VM 的 CPU 架构, -smp 4 表示有 4 个 core;-monitor 表示直接将 VMM monitor 重定向到当前命令行所在的标准输入输出设备上。
进入 monitor 中使用 info status 和 info cpus 查询 VM 的状态和 CPU 的使用状况。能够看到 CPU 实际上是 Host 上的线程,CPU 0 对应的线程 id 是 825030,CPU 1 对应的线程 id 是 825031,依次类推...,而 VM 则是 Host 上的一个 qemu 进程,其进程 id 为 825024。
值得注意的是,即时使用一个空的磁盘镜像,qemu-kvm 仍是能让它运行,可是能够想见,因为空磁盘镜像并无安装操做系统,其实是没有任何意义的(能想到的用处是测试 OpenStack 上 cinder / swift 组件是否正常,一般会使用一个测试镜像测试该镜像是否能够上传/安装/被 VM 使用):
[lianhua@localhost qemu-kvm]$ ll -h lianhua.raw
-rw-r--r--. 1 lianhua lianhua 20G Jul 5 16:52 lianhua.raw
[lianhua@localhost qemu-kvm]$ du lianhua.raw
0 lianhua.raw
[lianhua@localhost qemu-kvm]$ /usr/libexec/qemu-kvm -m 1G -smp 5 lianhua.raw -monitor stdio
WARNING: Image format was not specified for 'lianhua.raw' and probing guessed raw.
Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
Specify the 'raw' format explicitly to remove the restrictions.
QEMU 2.6.0 monitor - type 'help' for more information
(qemu) VNC server running on '::1;5900'
(qemu) info status
VM status: running
(qemu) info cpus
* CPU #0: pc=0x000000003fefa56a thread_id=888039
CPU #1: pc=0x00000000000fd374 (halted) thread_id=888041
CPU #2: pc=0x00000000000fd374 (halted) thread_id=888042
CPU #3: pc=0x00000000000fd374 (halted) thread_id=888043
CPU #4: pc=0x00000000000fd374 (halted) thread_id=888044
(qemu) info cpus
* CPU #0: pc=0x00000000000fc373 (halted) thread_id=888039
CPU #1: pc=0x00000000000fd374 (halted) thread_id=888041
CPU #2: pc=0x00000000000fd374 (halted) thread_id=888042
CPU #3: pc=0x00000000000fd374 (halted) thread_id=888043
CPU #4: pc=0x00000000000fd374 (halted) thread_id=888044
仔细看在建立 image 的时候, qemu 会聪明的不让镜像占用磁盘空间,而是等须要占用的时候再为它分配磁盘空间。使用 prealloction=full 选项能够在建立的时候为镜像分配磁盘空间。git