《虚拟化技术实现 — 虚拟化技术发展编年史》
KVM(Kernel-based Virtual Machine,基于内核的虚拟机)是一种用于 Linux 内核中的虚拟化基础设施。本质是一个嵌入到 Linux 内核中的虚拟化功能模块 kvm.ko(kvm-intel.ko/kvm-AMD.ko),该模块在利用 Linux 内核所提供的部分操作系统能力(e.g. 任务调度、内存管理、硬件设备交互)的基础上,再加入了处理器和内存虚拟化的能力,使得 Linux 内核具备了成为 VMM 的条件。KVM 于 2007 年 2 月 5 日被集成到 Linux 2.6.20 内核中。使用 KVM 的前提是宿主机必须拥有支持硬件虚拟化拓展特性(Intel VT 或者 AMD-V)的处理器。
KVM 的功能清单:
以 Intel VT 为例,当启动 Linux 操作系统并加载 KVM 内核模块时:
但需要注意的是,KVM 是运行在内核态的且本身不能进行任何设备的模拟。所以,KVM 还必须借助于一个运行在用户态的应用程序来模拟出虚拟机所需要的虚拟设备(e.g. 网卡、显卡、存储控制器和硬盘)同时为用户提供操作入口。目前这个应用程序的最佳选择就是 QEMU。
QEMU(Quick Emulator)是一款免费的、开源的、纯软件实现的、可执行硬件虚拟化的 VMM。与 Bochs,PearPC 等模拟器相比,QEMU 具有高速(配合 KVM)以及跨平台的特性。
事实上,QEMU 本身作为一套完整的 VMM 实现,包括了处理器虚拟化,内存虚拟化,以及模拟各类虚拟设备的功能。QEMU 4.0.0 版本甚至几乎可以模拟任何硬件设备,但由于这些模拟都是纯软件实现的,所以其性能低下。在 KVM 开发者在对 QEMU 进行稍加改造后,QEMU 可以通过 KVM 对外暴露的 /dev/kvm 接口来对其进行调用。从 QEMU 角度来看,也可以说是 QEMU 使用了 KVM 的处理器和内存虚拟化功能,为自己的虚拟机提供了硬件辅助虚拟化加速。除此以外,虚拟机的配置和创建、虚拟机运行所依赖的虚拟设备、虚拟机运行时的用户环境和用户交互,以及一些虚拟机的特定技术,比如:动态迁移,都是交由 QEMU 来实现的。
总的来说,QEMU 具有以下几种使用方式:
KVM 官方提供的软件包下载包含了 KVM 内核模块、QEMU、qemu-kvm 以及 virtio 四个文件。其中,qemu-kvm 本质是专门针对 KVM 的 QEMU 分支代码包(一个特殊的 QEMU 版本)。
QEMU-KVM 相比原生 QEMU 的改动:
然而在 QEMU 1.3 版本之后两者又保持一致了,但我们能仍习惯在 KVM 语境中将其称之为 QEMU-KVM。
NOTE:在 RHEL6/CentOS6 中,qemu-kvm 存放在 /usr/libexec 目录下。不过 PATH 环境变量缺省是不包含此目录的,所以用户无法直接使用 qemu-kvm,这样做是为了防止 QEMU 替代了 KVM 作为 VMM 的角色。如果希望启用 QEMU 作为 VMM 的话,可以通过将 /usr/libexec/qemu-kvm 链接为 /usr/bin/qemu 来完成。
在 QEMU-KVM 中,KVM 运行在内核空间,提供 CPU 和内存的虚级化,以及 Guest OS 的 I/O 拦截。QEMU 运行在用户空间,提供硬件 I/O 虚拟化,并通过 ioctl 调用 /dev/kvm 接口将 KVM 模块相关的 CPU 指令传递到内核中执行。当 Guest OS 的 I/O 被 KVM 拦截后,就会将 I/O 请求交由 QEMU 处理。例如:
open("/dev/kvm", O_RDWR|O_LARGEFILE) = 3
ioctl(3, KVM_GET_API_VERSION, 0) = 12
ioctl(3, KVM_CHECK_EXTENSION, 0x19) = 0
ioctl(3, KVM_CREATE_VM, 0) = 4
ioctl(3, KVM_CHECK_EXTENSION, 0x4) = 1
ioctl(3, KVM_CHECK_EXTENSION, 0x4) = 1
ioctl(4, KVM_SET_TSS_ADDR, 0xfffbd000) = 0
ioctl(3, KVM_CHECK_EXTENSION, 0x25) = 0
ioctl(3, KVM_CHECK_EXTENSION, 0xb) = 1
ioctl(4, KVM_CREATE_PIT, 0xb) = 0
ioctl(3, KVM_CHECK_EXTENSION, 0xf) = 2
ioctl(3, KVM_CHECK_EXTENSION, 0x3) = 1
ioctl(3, KVM_CHECK_EXTENSION, 0) = 1
ioctl(4, KVM_CREATE_IRQCHIP, 0) = 0
ioctl(3, KVM_CHECK_EXTENSION, 0x1a) = 0
kvmfd = open("/dev/kvm", O_RDWR);
vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);
ioctl(kvmfd, KVM_SET_USER_MEMORY_REGION, &mem);
将虚拟机镜像数据映射到内存,相当于物理机的 boot 过程,把操作系统内核映射到内存。
创建 vCPU,并为 vCPU 分配内存空间。KVM_CREATE_VCPU 时,KVM 为每一个 vCPU 生成对应的文件句柄,对其执行相应的 ioctl 调用,就可以对 vCPU 进行管理。
ioctl(kvmfd, KVM_CREATE_VCPU, vcpuid);
vcpu->kvm_run_mmap_size = ioctl(kvm->dev_fd, KVM_GET_VCPU_MMAP_SIZE, 0);
ioctl(kvm->vcpus->vcpu_fd, KVM_RUN, 0);
open("/dev/kvm")
ioctl(KVM_CREATE_VM)
ioctl(KVM_CREATE_VCPU)
for (;;) {
ioctl(KVM_RUN)
switch (exit_reason) { /* 分析退出原因,并执行相应操作 */
case KVM_EXIT_IO: /* ... */
case KVM_EXIT_HLT: /* ... */
}
}