:第二章 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。

相關文章
相關標籤/搜索