:第二章 KVM内部原理

在本章中,咱们将讨论libvirt、QEMU和KVM的重要数据结构和内部实现。而后,咱们将深刻了解KVM下vCPU的执行流程。html

在这一章,咱们将讨论:node

  • libvirt、QEMU和KVM的内部运做方式。
  • libvirt、QEMU和KVM的重要数据结构和代码路径。
  • vCPU的执行流程
  • 全部这些组件如何通讯以提供虚拟化

熟悉libvirt及其实现

上一章中提到,libvirt做为额外的管理层能够跟各类hypervisors(例如KVM/QEMU,LXC,OpenVZ,UML)进行通讯。libvirt API是开源的。与此同时,它是一个守护进程和管理工具,用于管理前面提到的不一样hypervisor。libvirt被各类虚拟化程序和平台普遍采用,例如,图形用户界面工具GNOME boxes和virt-manager(http://virt manager.org/)。不要将libvirt与咱们在第1章讨论的VMM/hypervisor混淆起来。git

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是如下形式的:session

  • qemu://xxxx/system
  • 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内部是如何工做的,您彻底能够跳过这一部分。若是你有那么一点想知道,那就来完成它吧。

libvirt的内部运做机制

待补充

Time to think more about QEMU

Quick Emulator(QEMU)由Fabrice Bellard(FFmpeg的创做者)编写,属于自由软件(free software),在GPL协议下许可开发。

QEMU是一个通用和开源的机器仿真和虚拟化软件。看成为机器仿真使用时,QEMU能够在一种机器上(好比你的PC)运行为另外一种机器(好比ARM开发板)编写的操做系统和应用程序。经过使用动态转译技术,它的性能很是好(见www.QEMU.org)。

让我从新解释一下前面的段落,并给出一个更具体的解释。QEMU其实是一个执行硬件虚拟化的Hypervisor/VMM。你是否是感受有些困惑?若是是的,别担忧。在本章末尾,您将得到一个更好的了解,特别是当您浏览完每一个相关的组件并关联用于执行虚拟化的整个路径。QEMU既能够充当模拟器(Emulator)也能够用做虚拟机(Virtualizer):

  • Qemu as an Emulator:在第1章《理解Linux虚拟化》中,咱们简单讨论了二进制转译。当QEMU做为一个仿真器运行时,它可以在一种机器上运行为另外类型机器设计的OS/应用程序。它是如何作到的?这其中就用到了二进制转译的方法。这种模式下,QEMU经过动态二进制转译模拟CPU,并提供一组设备模型。所以,能够在不一样的架构下运行多种不一样的未修改的Guest OS。将Guest code运行在Host CPU上就须要用到二进制转译技术。完成这项工做的二进制转译程序称为Tiny Code Generator(TCG);这是一个即时编译器。它将为给定处理器编写的二进制代码转换为运行在另外一个处理器的二进制代码(例如:ARM运行在X86上):

TCG旨在消除依赖于特定版本GCC或其余编译器的这一缺点,而是将编译器(代码生成器)和运行时由QEMU执行的其余任务结合起来。整个转译任务由两部分组成:target code(TBs)被重写成TCG ops(一种独立于机器的中间代码),随后由TCG根据主机的架构编译此中间代码。并根据须要进行相关优化。

TCG须要编写专用代码来支持它运行的每一个架构。(TCG info from Wikipedia https://en.wikipedia.org/wiki/QEMU#Tiny_Code_Generator)

  • QEMU as virtualizer:这种模式QEMU直接在主机CPU上执行Guest code,从而达到了原生性能。例如,当在Xen/KVM hypervisor下工做时,QEMU能够在这种模式下运行。若是KVM是底层hypervisor,那么QEMU能够对嵌入式客户机进行虚拟化,如Power PC、S390、x86等。简而言之,QEMU能够在没有KVM的状况下,之前文提到的二进制转译的方式运行。与启用KVM的硬件辅助虚拟化相比,这种方式的执行速度会慢一些。不管在哪一种模式下,QEMU不只模拟处理器,还模拟了不一样的外围设备,如磁盘、网络、VGA、PCI、串口和并行端口、USB等。除了I/O设备的模拟,在使用KVM时,QEMU-KVM建立并初始化虚拟机。它还为每一个vCPU(引用下图)初始化不一样的posix线程。此外,它还提供了一个框架,在QEMU-KVM用户模式地址空间中,模拟虚拟机的物理地址空间:

为了在物理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-KVM internals

在开始研究QEMU内部以前,让咱们先git clone QEMU的仓库:

#git clone git://git.qemu-project.org/qemu.git

一旦git clone完成,您就能够看到repo内部的文件层次结构,以下面的截图所示:

一些重要的数据结构和ioctls()构成了QEMU用户空间和KVM内核空间。一些重要的数据结构是KVMState,CPU {X86} State,MachineState等等。在咱们进一步探讨内部构成以前,我想指出的是,对它们的详细介绍超出了本书的范围;然而,我依然将提供足够的指引来理解在底层发生的事情,并为进一步的解释提供额外的参考。

Data structures

待补充

Threading models in QEMU

QEMU-KVM是一个多线程的、事件驱动的(带有一个大锁)应用程序。重要的线程有:

  • 主线程
  • 后端虚拟磁盘I/O的工做线程
  • 对应每一个vCPU的线程

对应每一个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的时间。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。

KVM APIs

如前所述,ioctl()的主要类型有三种。

三套ioctls组成了KVM API。KVM API是一组用于控制虚拟机的各个方面的ioctls。这些ioctls分为这三类:

  • System ioctls:它能够查询并设置了全局属性,影响整个KVM子系统。另外,系统ioctl用于建立虚拟机。
  • VM ioctls:这些查询和设置的属性将影响整个VM,例如内存分布。此外,VM ioctl用于建立虚拟CPU(vCPUs)。它们从建立VM的同一进程(地址空间)运行VM ioctls。
  • vCPU ioctl:这些查询和设置的属性控制单个vCPU的操做。它们从建立vCPU的同一线程上运行vCPU 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)

Anonymous inodes and file structures

待补充

Data structures

待补充

Execution flow of vCPU

待补充

概要

在本章中,咱们讨论了定义libvirt、QEMU和KVM的内部实现的重要数据结构和函数。咱们还讨论了vCPU执行的生命周期以及QEMU和KVM如何在主机CPU上运行Guest OS。咱们还讨论了虚拟化的硬件支持、围绕它的重要概念以及它如何在KVM虚拟化中发挥做用。有了这些概念和说明,咱们能够更详细地探索KVM虚拟化的细节。

在下一章中,咱们将介绍如何使用libvirt管理工具创建一个独立的KVM。

相关文章
相关标签/搜索