- QEMU/KVM源碼解析與應用
- 李強編著
- 4351字
- 2021-08-24 11:53:36
1.2 QEMU與KVM架構介紹
1.2.1 QEMU與KVM歷史
QEMU和KVM經常被人們放在一起討論,其實兩者的關系完全可以解耦合。QEMU最開始是由法國程序員Fabrice Bellard開發的一個模擬器。QEMU能夠完成用戶程序模擬和系統虛擬化模擬。用戶程序模擬指的是QEMU能夠將為一個平臺編譯的二進制文件運行在另一個不同的平臺,如一個ARM指令集的二進制程序,通過QEMU的TCG(Tiny Code Generator)引擎的處理之后,ARM指令被轉換成TCG中間代碼,然后再轉換成目的平臺的代碼。系統虛擬化模擬指的是QEMU能夠模擬一個完整的系統虛擬機,該虛擬機有自己的虛擬CPU、芯片組、虛擬內存以及各種虛擬外部設備,能夠為虛擬機中運行的操作系統和應用軟件呈現出與物理計算機完全一致的硬件視圖。QEMU能夠模擬的平臺很多,包括x86、ARM、MIPS、PPC等,早期的QEMU都是通過TCG來完成各種硬件平臺的模擬,所有的虛擬機指令需要經過QEMU的轉換。
系統虛擬機天生適用于云計算。云計算提供了一種按需服務的模式,讓用戶能夠很方便地根據自己的需求使用各種計算、網絡、存儲資源。以計算資源中的虛擬機為例,用戶可以指定不同CPU模型和內存規格的虛擬機。云計算平臺可以通過系統虛擬化技術很方便地滿足用戶的需求。如果用戶刪除資源,云計算平臺可以直接刪除其對應的虛擬機。早期的QEMU都是軟件模擬的,很明顯其在性能上是不能滿足要求的。所以早期的云計算平臺通常使用Xen作為其底層虛擬化平臺。前面提到過,Xen早期是在x86架構上直接完成的虛擬化,這需要修改虛擬機內部的操作系統,也使得Xen的整個VMM非常復雜,缺陷比較多。
Intel和AMD在2005年左右開始在CPU層面提供對系統虛擬化的支持,叫作硬件虛擬化,Intel在x86指令集的基礎上增加了一套VMX擴展指令VT-x,為CPU增加了新的運行模式,完成了x86虛擬化漏洞的修補。通過新的硬件虛擬化指令,可以非常方便地構造VMM,并且x86虛擬機中的代碼能夠原生地運行在物理CPU上。
以色列初創公司Qumranet基于新的虛擬化指令集實現了KVM,并推廣到Linux內核社區。KVM本身是一個內核模塊,導出了一系列的接口到用戶空間,用戶空間可以使用這些接口創建虛擬機。最開始KVM只負責最核心的CPU虛擬化和內存虛擬化部分,使用QEMU作為其用戶態組件,負責完成大量外設的模擬,當時的方案被稱為QEMU-KVM。KVM的具體設計與實現可以參考Avi Kivity等人在2007年發表的論文“KVM:The Linux Virtual Machine Monitor”。由于KVM的設計架構精簡,能夠跟現有的Linux內核無縫吻合,因此在社區獲得了極大的關注與支持。特別是隨著Red Hat投入大量的人力去完善QEMU和KVM,QEMU社區得到了飛速發展。直到現在,QEMU社區依然非?;钴S,但是其主要用途已經不是作為一個模擬器了,而是作為以QEMU-KVM為基礎的為云計算服務的系統虛擬化軟件。當然,不僅僅是KVM將QEMU作為應用層組件,Xen后來支持的硬件虛擬機也使用QEMU作為其用戶態組件來完成虛擬機的設備模擬。
1.2.2 QEMU與KVM架構
QEMU與KVM的完整架構如圖1-5所示。該圖來自QEMU官網,比較完整地展現了QEMU與KVM虛擬化的各個方面,包括QEMU的運行機制,KVM的組成,QEMU與KVM的關系,虛擬機CPU、內存、外設等的虛擬化,下面對其進行簡要介紹。
QEMU與KVM架構整體上分為3個部分,對應圖中的3個部分。左邊上半部分是所謂的VMX root模式的應用層,下面是VMX root模式的內核層。所謂VMX root,其實是相對于VMX non-root模式而言的。VMX root和VMX non-root都是CPU引入了支持硬件虛擬化的指令集VT-x之后出現的概念。VT-x的概念會在第4章CPU虛擬化中進行詳細介紹,現在可以將VMX root理解成宿主機模式,將VMX non-root理解成虛擬機模式。右邊上半部分表示的是虛擬機的運行,虛擬機運行在VMX non-root模式下。VMX root模式與未引入VT-x之前是一樣的,CPU在運行包括QEMU在內的普通進程和宿主機的操作系統內核時,CPU處在該模式。CPU在運行虛擬機中的用戶程序和操作系統代碼的時候處于VMX non-root模式。需要注意的是,CPU的運行模式與CPU運行時的特權等級是相互正交的,虛擬機在VMX root模式和VMX non-root模式下都有ring 0到ring 3四個特權級別。
圖1-5左邊上半部分列出了QEMU的主要任務,QEMU在初始化的時候會創建模擬的芯片組,創建CPU線程來表示虛擬機的CPU執行流,在QEMU的虛擬地址空間中分配空間作為虛擬機的物理地址,QEMU還需要根據用戶在命令行指定的設備為虛擬機創建對應的虛擬設備。在虛擬機運行期間,QEMU會在主線程中監聽多種事件,這些事件包括虛擬機對設備的I/O訪問、用戶對虛擬機管理界面、虛擬設備對應的宿主機上的一些I/O事件(比如虛擬機網絡數據的接收)等。QEMU應用層接收到這些事件之后會調用預先定義好的函數進行處理。

圖1-5 QEMU與KVM整體架構圖
圖1-5右邊上半部分表示的是虛擬機的運行。對虛擬機本身來講,它也有自己的應用層和內核層,只不過是VMX non-root下的。QEMU和KVM對虛擬機中的操作系統來說是完全透明的,常用的操作系統可以不經修改就直接運行在虛擬機中。虛擬機的一個CPU對應為QEMU進程中的一個線程,通過QEMU和KVM的相互協作,這些線程會被宿主機操作系統正常調度,直接執行虛擬機中的代碼。虛擬機中的物理內存對應為QEMU進程中的虛擬內存,虛擬機中的操作系統有自己的頁表管理,完成虛擬機虛擬地址到虛擬機物理地址的轉換,再經過KVM的頁表完成虛擬機物理地址到宿主機物理地址的轉換。虛擬機中的設備是通過QEMU呈現給它的,操作系統在啟動的時候進行設備枚舉,加載對應的驅動。在運行過程中,虛擬機操作系統通過設備的I/O端口(Port IO、PIO)或者MMIO(Memory Mapped I/O)進行交互,KVM會截獲這個請求,大多數時候KVM會將請求分發到用戶空間的QEMU進程中,由QEMU處理這些I/O請求。
圖1-5下半部分表示的是位于Linux內核中的KVM驅動。KVM驅動以雜項(misc)設備驅動的方式存在于內核中。一方面,KVM通過“/dev/kvm”設備導出了一系列的接口,QEMU等用戶態程序可以通過這些接口來控制虛擬機的各個方面,比如CPU個數、內存布局、運行等。另一方面,KVM需要截獲虛擬機產生的虛擬機退出(VM Exit)事件并進行處理。
QEMU和KVM聯合起來共同完成虛擬機各個組件的虛擬化,這里對幾個重要組件的虛擬化進行簡單介紹。
首先介紹CPU虛擬化。QEMU創建虛擬機CPU線程,在初始化的時候會設置好相應的虛擬CPU寄存器的值,然后調用KVM的接口,將虛擬機運行起來,在物理CPU上執行虛擬機的代碼。當虛擬機運行起來之后,KVM需要截獲虛擬機中的敏感指令,當虛擬機中的代碼是敏感指令或者說滿足了一定的退出條件時,CPU會從VMX non-root模式退出到KVM,這叫作VM Exit,這就像在用戶態執行指令陷入內核一樣。虛擬機的退出首先陷入到KVM中進行處理,如果KVM無法處理,比如說虛擬機寫了設備的寄存器地址,那么KVM會將這個寫操作分派到QEMU中進行處理,當KVM或者QEMU處理好了退出事件之后,又可以將CPU置于VMX non-root模式運行虛擬機代碼,這叫作VM Entry。虛擬機就這樣不停地進行VM Exit和VM Entry,CPU會加載對應的宿主機狀態或者虛擬機狀態,如圖1-6所示。KVM使用一個結構來保存虛擬機VM Exit和VM Entry的狀態,叫作VMCS。

圖1-6 CPU虛擬化原理
其次介紹內存虛擬化。如同物理機運行需要內存一樣,虛擬機的運行同樣離不開內存,QEMU在初始化的時候需要調用KVM的接口向KVM告知虛擬機所需要的所有物理內存。QEMU在初始化的時候會通過mmap系統調用分配虛擬內存空間作為虛擬機的物理內存,QEMU在不斷更新內存布局的過程中會持續調用KVM接口通知內核KVM模塊虛擬機的內存分布。虛擬機在運行過程中,首先需要將虛擬機的虛擬地址(Guest Virtual Address,GVA)轉換成虛擬機的物理地址(Guest Physical Address,GPA),然后將虛擬機的物理地址轉換成宿主機的虛擬地址(Host Virtual Address,HVA),最終轉換成宿主機的物理地址(Host Physical Address,HPA)。在CPU支持EPT(Extended Page Table,擴展頁表)之前,虛擬機通過影子頁表實現從虛擬機虛擬地址到宿主機物理地址的轉換,是一種軟件實現。當CPU支持EPT之后,CPU會自動完成虛擬機物理地址到宿主機物理地址的轉換。虛擬機在第一次訪問內存的時候就會陷入到KVM,KVM會逐漸建立起所謂的EPT頁面。這樣虛擬機的虛擬CPU在后面訪問虛擬機虛擬內存地址的時候,首先會被轉換為虛擬機物理地址,接著會查找EPT頁表,然后得到宿主機物理地址,其內存尋址過程如圖1-7所示,整個過程全部由硬件完成,效率很高。由于現在EPT都是標配,本書將只關注EPT存在的情況。

圖1-7 EPT原理
再次介紹外設虛擬化。一個計算機系統離不開大量的外部設備,網卡、磁盤等通常都是計算機系統必不可少的組成部分。虛擬化的一個煩瑣任務就是為虛擬機提供大量的設備支持,如同Linux內核中最多的代碼是設備驅動,QEMU最多的代碼是設備模擬。設備模擬的本質是要為虛擬機提供一個與物理設備接口完全一致的虛擬接口。虛擬機中的操作系統與設備進行的數據交互或者由QEMU和(或)KVM完成,或者由宿主機上對應的后端設備完成。QEMU在初始化過程中會創建好模擬芯片組和必要的模擬設備,包括南北橋芯片、PCI根總線、ISA根總線等總線系統,以及各種PCI設備、ISA設備等。QEMU的命令行可以指定可選的設備以及設備配置項。大部分情況下,用戶對虛擬機的需求都體現在對虛擬設備的需求上,比如常見的網絡、存儲資源對應QEMU的網卡模擬和硬盤模擬。這些需求也導致了QEMU虛擬設備的快速發展。設備模擬經歷了非常大的發展,最開始的QEMU只有純軟件模擬,虛擬機內核不用做任何修改,每一次對設備的寄存器讀寫都會陷入到KVM,進而到QEMU,QEMU再對這些請求進行處理并模擬硬件行為,純軟件模擬設備如圖1-8a所示。顯然,軟件模擬會導致非常多的QEMU/KVM介入,效率不高。為了提高虛擬設備的性能,社區提出了virtio設備方案。virtio設備是一類特殊的設備,并沒有對應的物理設備,所以需要虛擬機內部操作系統安裝特殊的virtio驅動,virtio設備模擬如圖1-8b所示。virtio設備將QEMU變成了半虛擬化方案,因為其本質上修改了虛擬機操作系統內核,與之相對的完全不用修改虛擬機操作系統的方案叫作完全虛擬化。virtio仍然不能完全滿足一些高性能的場景,于是又有了設備直通方案,也就是將物理硬件設備直接掛到虛擬機上,虛擬機直接與物理設備交互,盡可能在I/O路徑上減少QEMU/KVM的參與,直通設備原理如圖1-8c所示。與設備直通經常一起使用的有設備的硬件虛擬化支持技術SRIOV(Single Root I/O Virtualization,單根輸入/輸出虛擬化),SRIOV能夠將單個的物理硬件高效地虛擬出多個虛擬硬件。通過將SRIOV虛擬出來的硬件直通到虛擬機中,虛擬機能夠非常高效地使用這些設備。

圖1-8 QEMU的各種設備模擬方式
a) 純軟件的設備模擬 b) virtio設備模擬 c) 直通設備
最后介紹中斷虛擬化。中斷系統是一個計算系統必不可少的組成部分。操作系統通過寫設備的I/O端口或者MMIO地址來與設備交互,設備通過發送中斷來通知虛操作系統事件,圖1-9顯示了模擬設備向虛擬機注入中斷的狀態。QEMU在初始化主板芯片的時候初始化中斷控制器。QEMU支持單CPU的Intel 8259中斷控制器以及SMP的I/O APIC(I/O Advanced Programmable Interrupt Controller)和LAPIC(Local Advanced Programmable Interrupt Controller)中斷控制器。傳統上,如果虛擬外設通過QEMU向虛擬機注入中斷,需要先陷入到KVM,然后由KVM向虛擬機注入中斷,這是一個非常費時的操作,為了提高虛擬機的效率,KVM自己也實現了中斷控制器Intel 8259、I/O APIC以及LAPIC。用戶可以有選擇地讓QEMU或者KVM模擬全部中斷控制器,也可以讓QEMU模擬Intel 8259中斷控制器和I/O APIC,讓KVM模擬LAPIC。QEMU/KVM一方面需要完成這項中斷設備的模擬,另一方面需要模擬中斷的請求。中斷請求的形式大體上包括傳統ISA設備連接Intel 8259中斷控制器產生的中斷請求,PCI設備的INTx中斷請求以及MSI和MSIX中斷請求。

圖1-9 虛擬設備的中斷注入