在本章中,我们将讨论 libvirt、QEMU 和 KVM 的重要数据结构和内部实现。然后,我们将深入了解 KVM 下 vCPU 的执行流程。
在这一章,我们将讨论:
上一章中提到,libvirt 作为额外的管理层可以跟各种 hypervisors(例如 KVM/QEMU,LXC,OpenVZ,UML) 进行通信。libvirt API 是开源的。与此同时,它是一个守护进程和管理工具,用于管理前面提到的不同 hypervisor。libvirt 被各种虚拟化程序和平台广泛采用,例如,图形用户界面工具 GNOME boxes 和 virt-manager(http://virt manager.org/)。不要将 libvirt 与我们在第 1 章讨论的 VMM/hypervisor 混淆起来。
libvirt 的 cli 命令行接口叫做 virsh。libvirt 也被其他高级管理工具采用,如 oVirt(www.ovirt.org)。
很多人以为 libvirt 只能在单个节点或者运行 libvirt 的本地节点上运行,这并不正确。libvirt 库支持远程功能。因此,任何 libvirt 工具(例如 virt-manager)都可以通过传递一个额外的 --connect 参数,远程连接到网络上的 libvirt 守护进程。Fedora,CentOS 等绝大多数发行版都安装了 libvirt 的客户端 virsh(由 libvirt -client package 提供)。
正如前面所讨论的,libvirt 的目标是为管理在 hypervisor 上运行的 VM 提供一个通用的和稳定的抽象层。简而言之,作为管理层,它负责提供执行管理任务的 API,如虚拟机置备、创建、修改、监视、控制、迁移等。在 Linux 中,您会注意到一些进程成为了守护进程。libvirt 进程也有一个守护进程,叫做 libvirtd。与其他守护进程一样,libvirtd 为其 client 发起的请求提供服务。让我们试着了解 libvirt client(如 virsh 或 virt-manager)向 libvirtd 请求服务时,到底发生了什么。根据客户端传递的连接 URI(将在接下来的部分中讨论),libvirtd 建立起与 hypervisor 的连接。客户端程序 virsh 或 virt-manager 就是通过这样的方式使 libvirtd 与 hypervisor 建立通信的。在本书的范围内,我们的目标是 KVM 虚拟化技术。因此,最好是用 QEMU/KVM hypervisor 而不是其他 hypervisor 为场景来考虑与 libvirtd 的通信。你可能会对使用 QEMU/KVM 而不是 QEMU 或 KVM 作为底层 hypervisor 的名称有些困惑。但别担心,一切都会在适当的时候变得明了。下面会讨论 QEMU 和 KVM 之间的联系。目前你只需了解该 hypervisor 同时用到了 QEMU 和 KVM 技术。
让我们回到通过 libvirt 客户端 virsh 传递的连接 URI。当我们将注意力集中在 QEMU/KVM 虚拟化时,从客户端传递的连接 URI 包含 "QEMU" 字符串,或者传递给 libvirt 的连接 URI 是以下形式的:
qemu://xxxx/session
前一个(qemu://xxxx/system)请求以 root 身份连接到本地管理的 QEMU 和 KVM 域,或者 VM 的守护进程。后一个(qemu://xxxx/session)请求以普通用户身份连接到它自身的 QEMU 和 KVM 域。之前,我们提到 libvirt 也支持远程连接;幸运的是要实现这个功能,只需在连接 URI 上做一个小小的更改。也就是说,可以通过在连接 URI 中更改某些字符串来建立远程连接。例如,连接 URI 的通用格式如下:
- driver[+transport]://[username@][hostname][:port]/[path][?extraparameters]
远程连接的 virsh 简单命令行示例如下:
- $ virsh --connect qemu+ssh://root@remoteserver.yourdomain.com/system list --all
如 virsh 命令示例所示(qemu+ssh://root@remoteserver.yourdomain.com/system),远程 URI 是由本地 URI,主机名和 / 或 transport name 组成:
前面的图显示了远程连接是如何与其他系统上的 libvirt 进行通信的。稍后将介绍驱动程序 API 或驱动程序实现的细节。 When using a URI scheme of "remote",it will tell the remote libvirtd server to probe for the optimal hypervisor driver。以下内容将提供一些关于 "remote driver" 的详细信息。有关 remote connection URI 的更多选项,请参阅下面的 URL 以获取更多细节:
要了解 libvirt 的工作原理,我们来看看代码。本节包含一些面向开发者的细节,如果您一点都不想知道 libvirt 内部是如何工作的,您完全可以跳过这一部分。如果你有那么一点想知道,那就来完成它吧。
待补充
Quick Emulator(QEMU)由 Fabrice Bellard(FFmpeg 的创作者)编写,属于自由软件(free software),在 GPL 协议下许可开发。
QEMU 是一个通用和开源的机器仿真和虚拟化软件。当作为机器仿真使用时,QEMU 可以在一种机器上(比如你的 PC)运行为另一种机器(比如 ARM 开发板)编写的操作系统和应用程序。通过使用动态转译技术,它的性能非常好(见 www.QEMU.org)。
让我重新解释一下前面的段落,并给出一个更具体的解释。QEMU 实际上是一个执行硬件虚拟化的 Hypervisor/VMM。你是不是感觉有些困惑?如果是的,别担心。在本章末尾,您将获得一个更好的了解,特别是当您浏览完每个相关的组件并关联用于执行虚拟化的整个路径。QEMU 既可以充当模拟器(Emulator)也可以用作虚拟机(Virtualizer):
TCG 旨在消除依赖于特定版本 GCC 或其他编译器的这一缺点,而是将编译器(代码生成器)和运行时由 QEMU 执行的其他任务结合起来。整个转译任务由两部分组成:target code(TBs)被重写成 TCG ops(一种独立于机器的中间代码),随后由 TCG 根据主机的架构编译此中间代码。并根据需要进行相关优化。
TCG 需要编写专用代码来支持它运行的每个架构。(TCG info from Wikipedia https://en.wikipedia.org/wiki/QEMU#Tiny_Code_Generator)
为了在物理 CPU 中执行 Guest code,QEMU 使用了 posix 线程。也就是说,Guest 虚拟 CPU 在主机内核中作为 posix 线程执行。这本身就带来了许多好处,因为从上层来看它们只是主机内核的一些进程。从另一个角度看,KVM hypervisor 的用户空间部分由 QEMU 提供。QEMU 通过 KVM 内核模块运行客户代码。在使用 KVM 时,QEMU 也执行 I/O 仿真、I/O 设备设置、实时迁移等。
QEMU 访问 KVM 模块创建的设备文件(/dev/kvm),并执行 ioctls()。请参阅 KVM 的下一节,了解这些 ioctls()。最后,KVM 利用 QEMU 成为一个完整的 hypervisor,而 KVM 是利用 CPU 提供的硬件虚拟化扩展 (VMX 或 SVM) 的加速器或促成器,与 CPU 架构紧密耦合。间接地,这表明 VM 也必须使用相同的架构以使用硬件虚拟化扩展 / 功能。一旦启用 KVM,它肯定会比其他技术 (如二进制转译) 提供更好的性能。
在开始研究 QEMU 内部之前,让我们先 git clone QEMU 的仓库:
- #git clone git://git.qemu-project.org/qemu.git
一旦 git clone 完成,您就可以看到 repo 内部的文件层次结构,如下面的截图所示:
一些重要的数据结构和 ioctls() 构成了 QEMU 用户空间和 KVM 内核空间。一些重要的数据结构是 KVMState,CPU {X86} State,MachineState 等等。在我们进一步探讨内部构成之前,我想指出的是,对它们的详细介绍超出了本书的范围;然而,我依然将提供足够的指引来理解在底层发生的事情,并为进一步的解释提供额外的参考。
待补充
QEMU-KVM 是一个多线程的、事件驱动的 (带有一个大锁) 应用程序。重要的线程有:
对应每个 VM,都有一个 QEMU 进程在主机系统中运行。如果 Guest 关闭,这个过程将被销毁 / 退出。除了 vCPU 线程之外,还有专门的 iothreads 运行一个 select(2) 事件循环来处理 I/O,例如处理网络数据包和磁盘 I/O。IO 线程也由 QEMU 派生。简而言之,情况将是这样的:
在我们进一步讨论之前,总有一个关于 Guest 物理内存的问题:它位于哪里?情况是这样的:如之前的图片所示,Guest RAM 是在 QEMU 进程的虚拟地址空间内分配的。也就是说,Guest 的物理内存在 QEMU 进程地址空间内。
NOTE:关于线程的更多细节可以从线程模型中获取:http://blog.vmsplice.net/2011/03/qemu-internals-overall-architecutre-and-html?m=1
事件循环线程也称为 iothread。事件循环用于计时器、文件描述符监控等。main_loop_wait() 是 QEMU 主事件循环线程,它的定义如下所示。这个 main event loop thread 负责 main loop services:包括文件描述符回调、 bottom halves 和计时器(在 qemu-timer.h 中定义)。Bottom halve 类似于立即执行的计时器,但开销要更低一些,调度它们是无等待的、线程安全的和信号安全的。
File:
- vl.c
- static void main_loop(void) {
- bool nonblocking;
- int last_io = 0;
- ...
- do {
- nonblocking = !kvm_enabled() && !xen_enabled() && last_io > 0;
- …...
- last_io = main_loop_wait(nonblocking);
- …...
- } while (!main_loop_should_exit());
- }
在我们离开 QEMU 代码库之前,我想指出,设备代码主要有两个部分。例如,目录 hw/block / 包含 host 端的块设备代码,hw/block / 包含设备模拟的代码。
终于到了讨论 KVM 的时间。KVM 开发者遵循了和 Linux kernel 开发者的一样的理念:不要重新发明轮子。也就是说,他们并没有尝试改变内核代码来创建一个 hypervisor;相反,代码是围绕硬件供应商的虚拟化(VMX 和 SVM)的新硬件支持,以可加载内核模块的形式开发的。有一个通用的内核模块 kvm.ko 和其他硬件相关的内核模块,如 kvm-intel.ko(基于 Intel CPU 系统)或 kvm-amd.ko(基于 AMD CPU 系统)。相应的,KVM 将载入 kvm-intel.ko(如果存在 vmx 标志)或 kvm-amd.ko(如果存在 svm 标志)模块。这将 Linux 内核变成了一个 hypervisor,从而实现了虚拟化。KVM 是由 qumranet 开发的,自 2.6.20 后成为 Linux 内核主线的一部分。后来,qumranet 被红帽收购。
KVM 暴露设备文件 / dev/kvm 以供应用程序调用 ioctls()。QEMU 利用这个设备文件与 KVM 通信,并创建、初始化和管理 VM 的内核模式上下文。前面,我们提到 QEMU-KVM 用户空间在 QEMU-KVM 的用户模式地址空间内,提供了 VM 的物理地址空间,其中包括 memory-mapped I/O。KVM 帮助实现这一点。在 KVM 的协助下还实现了更多的事情。以下是其中一些:
- Emulation of certain I/O devices, for example (via"mmio") the per-CPU local APIC and the system-wide IOAPIC.
- Emulation of certain "privileged" (R/W of system registers CR0, CR3 and CR4) instructions.
- The facilitation to run guest code via VMENTRY and handling of "intercepted events" at VMEXIT.
- "Injection" of events such as virtual interrupts and page faults into the flow of execution of the virtual machine and so on are also achieved with the help of KVM.
再重复一遍,KVM 不是 hypervisor!是不是有些迷糊?好的,让我重新解释一下。KVM 不是一个完整的 hypervisor,但是借助 QEMU 和模拟器的帮助(一个为 I/O 设备仿真和 BIOS 轻度修改的 QEMU),它可以成为一个 hypervisor。KVM 需要支持硬件虚拟化的处理器才能运行。通过使用这些功能,KVM 将标准 Linux 内核变成了一个 hypervisor。当 KVM 运行虚拟机时,每个 VM 都是一个普通的 Linux 进程,很明显,它可以在 Host kernel 的 CPU 上运行,就像在 Host kernel 中运行其他进程一样。在第 1 章《理解 Linux 虚拟化》中,我们讨论了不同的 CPU 运行模式。如果您还记得,主要有 USER 模式和 Kernel/Supervisor 模式。KVM 是 Linux 内核中的一个虚拟化特性,它允许像 QEMU 这样的程序直接在主机 CPU 上执行客户代码。只有当目标架构受到主机 CPU 的支持时,这才可以实现。
然而,KVM 引入了一种名为 "guest mode" 的模式!简而言之,guest 模式是客户系统代码的执行。它可以运行 Guest 的 user 或者 kernel 代码。在具备虚拟化特性的硬件支持下,KVM 虚拟化了进程状态、内存管理等。
通过其硬件虚拟化功能,处理器通过 Virtual Machine Control Structure(VMCS)和 Virtual Machine Control Block(VMCB)来管理 host 和 guest os 的处理器状态,并代表虚拟机的 OS 管理 I/O 和中断。也就是说,随着这种类型的硬件的引入,诸如 CPU 指令拦截、寄存器读 / 写支持、内存管理支持(扩展页表 (EPT) 和 NPT)、中断处理支持(APICv)、IOMMU 等等,得到了支持。
KVM 使用标准的 Linux 调度器、内存管理和其他服务。简而言之,KVM 所做的是帮助用户空间程序利用硬件虚拟化功能。在这里,您可以将 QEMU 作为一个用户空间程序来对待,因为它对不同的用户场景进行了良好的集成。当我们说 "硬件加速虚拟化" 时,我们主要指的是 Intel VT-x 和 ADM-Vs SVM。引入虚拟化支持的处理器带来了额外的指令集,称为 Virtual Machine Extensions 或 VMX。
使用 Intel 的 VT-x,VMM 在 "VMX 根模式" 中运行,而 Guest(未修改的 OSs)在 "VMX 非根模式" 中运行。这个 VMX 给 CPU 带来了额外的虚拟化指令,比如 VMPTRLD、VMPTRST、VMCLEAR、VMREAD、VMWRITE、VMCALL、VMLAUNCH、VMRESUME、VMXOFF 和 VMXON。VMXON 可以打开虚拟化模式 (VMX),VMXOFF 可以禁用。要执行客户代码,必须使用 VMLAUNCH/VMRESUME 指令,并使用 VMEXIT 离开。VMEXIT 表示从非根操作切换到根操作。显然,当我们进行这个切换时,需要保存一些信息,以便稍后可以获取它。Intel 提供了一种结构,以促进这种被称为 Virtual Machine Control Structure(VMCS)的切换;它将用来处理大部分虚拟化管理功能。例如在 VMEXIT 中,exit 的原因将被记录在这个结构中。那么,我们如何从这个结构中读取或写入信息呢?VMREAD 和 VMWRITE 指令可以用于读取或写入 VMCS 结构中的字段。
最近 Intel 处理器也提供了一项功能,允许每个 guest 有自己的页面表来跟踪内存地址。如果没有 EPT,hypervisor 必须退出虚拟机以执行地址转换,性能就会降低。正如我们在 Intel 虚拟化 CPU 中观察到的操作模式一样,AMD 的 Secure Virtual Machine (SVM)也有一系列操作模式,称为 Host mode 和 Guest mode。正如您所猜测的那样,hypervisor 运行在 Host mode 下,Guest OS 运行在 Guest mode。显然,在 Guest mode 下,一些指令可以导致 VMEXIT,并以特定的方式处理 Guest mode。AMD 也有一个等效于 VMCS 的结构,它被称为 Virtual Machine Control Block(VMCB);正如前面所讨论的,它记录 VMEXIT 的原因。AMD 增加了 8 种新的指令码支持支持 SVM。例如,VMRUN 指令启动 Guest OS 的操作,VMLOAD 指令从 VMCB 加载处理器状态,VMSAVE 指令将处理器状态保存到 VMCB。此外,为了提高内存管理单元的性能,AMD 引入了一种称为 NPT(嵌套分页) 的技术,类似于 Intel 的 EPT。
如前所述,ioctl() 的主要类型有三种。
三套 ioctls 组成了 KVM API。KVM API 是一组用于控制虚拟机的各个方面的 ioctls。这些 ioctls 分为这三类:
要进一步了解 KVM 对外发布的 ioctl() 和属于特定的 fd 组的 ioctls(),请参阅 KVM.h:
- /* ioctls for /dev/kvm fds: */
- #define KVM_GET_API_VERSION _IO(KVMIO, 0x00)
- #define KVM_CREATE_VM _IO(KVMIO, 0x01) /* returns a VM fd */
- …..
- /* ioctls for VM fds */
- #define KVM_SET_MEMORY_REGION _IOW(KVMIO, 0x40, struct kvm_memory_region)
- #define KVM_CREATE_VCPU _IO(KVMIO, 0x41)
- …
- /* ioctls for vcpu fds */
- #define KVM_RUN _IO(KVMIO, 0x80)
- #define KVM_GET_REGS _IOR(KVMIO, 0x81, struct kvm_regs)
- #define KVM_SET_REGS _IOW(KVMIO, 0x82, struct kvm_regs)
待补充
待补充
待补充
在本章中,我们讨论了定义 libvirt、QEMU 和 KVM 的内部实现的重要数据结构和函数。我们还讨论了 vCPU 执行的生命周期以及 QEMU 和 KVM 如何在主机 CPU 上运行 Guest OS。我们还讨论了虚拟化的硬件支持、围绕它的重要概念以及它如何在 KVM 虚拟化中发挥作用。有了这些概念和说明,我们可以更详细地探索 KVM 虚拟化的细节。
在下一章中,我们将介绍如何使用 libvirt 管理工具建立一个独立的 KVM。
来源: http://www.cnblogs.com/echo1937/p/7338582.html