版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
QEMUKVM源碼解析與應(yīng)用TOC\h目錄\h第1章QEMU與KVM概述\h1.1虛擬化簡(jiǎn)介\h1.2QEMU與KVM架構(gòu)介紹\h1.3KVMAPI使用實(shí)例\h第2章QEMU基本組件\h2.1QEMU事件循環(huán)機(jī)制\h2.2QEMU線程模型\h2.3QEMU參數(shù)解析\h2.4QOM介紹\h2.5hmp與qmp介紹\h第3章主板與固件模擬\h3.1Intel440FX主板簡(jiǎn)介\h3.2QEMU的主板模擬與初始化\h3.3fw_cfg設(shè)備介紹\h3.4SeaBIOS分析\h第4章CPU虛擬化\h4.1CPU虛擬化介紹\h4.2KVM模塊初始化介紹\h4.3虛擬機(jī)的創(chuàng)建\h4.4QEMUCPU的創(chuàng)建\h4.5KVMCPU的創(chuàng)建\h4.6VCPU的運(yùn)行\(zhòng)h4.7VCPU的調(diào)度\h第5章內(nèi)存虛擬化\h5.1內(nèi)存虛擬化簡(jiǎn)介\h5.2QEMU內(nèi)存初始化\h5.3內(nèi)存布局的提交\h5.4內(nèi)存的分派\h5.5KVM內(nèi)存虛擬化\h5.6MMIO機(jī)制\h5.7虛擬機(jī)臟頁(yè)跟蹤\h第6章中斷虛擬化\h6.1中斷機(jī)制簡(jiǎn)介\h6.2中斷模擬\h6.3APIC虛擬化\h第7章設(shè)備虛擬化\h7.1設(shè)備虛擬化簡(jiǎn)介\h7.2PCI設(shè)備模擬\h7.3設(shè)備模擬后端\h7.4virtio設(shè)備模擬\h7.5ioeventfd和irqfd\h7.6vhostnet簡(jiǎn)介\h7.7設(shè)備直通與VFIO\h第8章虛擬化雜項(xiàng)\h8.1QEMUGuestAgent\h8.2QEMU虛擬機(jī)熱遷移\h8.3QEMU及虛擬化安全\h8.4容器與虛擬化\h附錄重要術(shù)語(yǔ)第1章QEMU與KVM概述1.1虛擬化簡(jiǎn)介1.1.1虛擬化思想計(jì)算機(jī)科學(xué)家DavidWheeler有一句名言:“計(jì)算機(jī)科學(xué)中的任何問(wèn)題都可以通過(guò)增加一個(gè)中間層來(lái)解決。”這句話簡(jiǎn)潔而深刻地說(shuō)明了虛擬化的思想存在于計(jì)算機(jī)科學(xué)中的各個(gè)領(lǐng)域。虛擬化的主要思想是,通過(guò)分層將底層的復(fù)雜、難用的資源虛擬抽象成簡(jiǎn)單、易用的資源,提供給上層使用。本質(zhì)上,計(jì)算機(jī)的發(fā)展過(guò)程也是虛擬化不斷發(fā)展的過(guò)程。CPU內(nèi)部遍布的數(shù)字邏輯電路只能夠識(shí)別二進(jìn)制數(shù)據(jù)0、1,機(jī)器碼是計(jì)算機(jī)唯一能夠識(shí)別的數(shù)據(jù)。匯編語(yǔ)言的出現(xiàn)讓程序員能夠比較簡(jiǎn)單地實(shí)現(xiàn)CPU的執(zhí)行和內(nèi)存訪問(wèn)。C語(yǔ)言的出現(xiàn)進(jìn)一步方便了程序員,使得程序員可以從具體CPU架構(gòu)指令脫離出來(lái),大部分情況下只需要考慮業(yè)務(wù)即可。Python、Java等現(xiàn)代高級(jí)語(yǔ)言更是重新定義了自己的指令,由各個(gè)平臺(tái)的虛擬機(jī)去解釋執(zhí)行,實(shí)現(xiàn)了完全的跨平臺(tái)。從機(jī)器碼、匯編語(yǔ)言到C語(yǔ)言,再到高級(jí)語(yǔ)言,其本質(zhì)就是一個(gè)不斷虛擬的過(guò)程,將底層復(fù)雜的接口轉(zhuǎn)變成了上層容易使用的接口。硬盤由柱面、磁道、扇區(qū)構(gòu)成,里面存放著文件等數(shù)據(jù)信息,計(jì)算機(jī)用戶在進(jìn)行文件訪問(wèn)的時(shí)候并不需要關(guān)心這些底層細(xì)節(jié)。這些細(xì)節(jié)經(jīng)過(guò)操作系統(tǒng)的抽象,變成了文件與目錄的概念,使得復(fù)雜的硬盤數(shù)據(jù)管理變得簡(jiǎn)單方便,應(yīng)用程序能夠通過(guò)文件管理的接口方便地創(chuàng)建、讀取、寫入文件,這本質(zhì)上也是一種虛擬。TCP/IP協(xié)議棧模型是另一個(gè)虛擬化的例子。網(wǎng)卡設(shè)備傳遞的都是二進(jìn)制數(shù)據(jù),經(jīng)過(guò)網(wǎng)絡(luò)層、傳輸層的抽象之后,應(yīng)用程序不需要直接跟網(wǎng)絡(luò)數(shù)據(jù)包的收發(fā)細(xì)節(jié)打交道,只需要關(guān)心協(xié)議棧最上層的接口,也不需要關(guān)心其他使用網(wǎng)卡設(shè)備的程序,只需要將要發(fā)送的數(shù)據(jù)和地址提供給協(xié)議棧,協(xié)議棧會(huì)自動(dòng)處理好IP路由、分片等細(xì)節(jié)?,F(xiàn)代操作系統(tǒng)中都有很多進(jìn)程,每一個(gè)進(jìn)程都是對(duì)計(jì)算機(jī)的抽象,進(jìn)程都認(rèn)為自己獨(dú)占整個(gè)計(jì)算機(jī)系統(tǒng)的資源,有著獨(dú)立的CPU和內(nèi)存。但這些都是操作系統(tǒng)呈現(xiàn)給進(jìn)程的假象,操作系統(tǒng)通過(guò)在各個(gè)進(jìn)程之間共享CPU,為每個(gè)進(jìn)程創(chuàng)建獨(dú)立的虛擬內(nèi)存,這不僅能夠?qū)崿F(xiàn)資源的充分利用,也能夠?qū)崿F(xiàn)進(jìn)程之間的安全隔離。操作系統(tǒng)會(huì)提供一組接口給應(yīng)用程序,方便開(kāi)發(fā)者編寫應(yīng)用程序,比如創(chuàng)建進(jìn)程操作文件、發(fā)送網(wǎng)絡(luò)數(shù)據(jù)等,這種抽象本質(zhì)上也是虛擬化的一種體現(xiàn)。開(kāi)發(fā)者只需要關(guān)注上層的接口而不需要關(guān)心底層的細(xì)節(jié)實(shí)現(xiàn)。這樣底層的實(shí)現(xiàn)即使發(fā)生了變化,也不會(huì)影響上層應(yīng)用程序的運(yùn)行,如Wine項(xiàng)目和Cygwin項(xiàng)目,前者能夠讓為Windows編寫的程序運(yùn)行在Linux上,后者能夠讓為L(zhǎng)inux編寫的程序運(yùn)行在Windows上。以上的例子都是虛擬化思想的體現(xiàn)。底層的資源或者通過(guò)空間的分割,或者通過(guò)時(shí)間的分割,將下層的資源通過(guò)一種簡(jiǎn)單易用的方式轉(zhuǎn)換成另一種資源,提供給上層使用。經(jīng)典的操作系統(tǒng)書(shū)籍OperatingSystems:ThreeEasyPieces從三個(gè)方面講述了操作系統(tǒng)的基本原理,第一部分即是虛擬化思想,其他兩部分是并行和持久化。1.1.2虛擬機(jī)簡(jiǎn)介上一節(jié)闡述了虛擬化的思想,本節(jié)介紹一下虛擬機(jī)(VirtualMachine,VM)。虛擬機(jī),顧名思義,其重點(diǎn)在“機(jī)”上,也就是機(jī)器。理論上講,只要能提供一個(gè)執(zhí)行環(huán)境,完成用戶指定任務(wù)的對(duì)象都可以叫作機(jī)器。所以可以從多種角度來(lái)解釋虛擬機(jī)。最簡(jiǎn)單的虛擬機(jī)是進(jìn)程,這種虛擬機(jī)太過(guò)于普通,以至于很多人都沒(méi)有意識(shí)到它們是虛擬機(jī)。進(jìn)程可以看作是一組資源的集合,有自己獨(dú)立的進(jìn)程地址空間以及獨(dú)立的CPU和寄存器,執(zhí)行程序員編寫的指令,完成一定的任務(wù)。一個(gè)進(jìn)程在執(zhí)行指令、訪問(wèn)內(nèi)存的時(shí)候并不會(huì)影響其他進(jìn)程。這是通過(guò)操作系統(tǒng)完成的,操作系統(tǒng)把CPU按照時(shí)間分配復(fù)用,把內(nèi)存按照空間分配復(fù)用,通過(guò)管理底層資源,使得進(jìn)程都能夠使用整個(gè)計(jì)算機(jī)的物理資源,每個(gè)進(jìn)程都認(rèn)為自己擁有整個(gè)機(jī)器。操作系統(tǒng)上可以創(chuàng)建很多個(gè)進(jìn)程,每一個(gè)進(jìn)程都可以看成是一個(gè)獨(dú)立的虛擬機(jī)。進(jìn)程虛擬機(jī)如圖1-1所示。圖1-1進(jìn)程虛擬機(jī)模擬器是另一種形式的虛擬機(jī)。進(jìn)程的指令都是可以直接運(yùn)行在硬件CPU上的,模擬器則不同,它可以使為一種硬件指令集(InstructionSetArchitecture,ISA)編譯的程序運(yùn)行在另一種硬件指令集上。應(yīng)用程序在源ISA(如ARM)上被編譯出來(lái),在模擬器的幫助下,運(yùn)行在不同的目標(biāo)ISA(比如x86)上。模擬器可以通過(guò)解釋來(lái)實(shí)現(xiàn),即對(duì)程序的源ISA指令一條一條進(jìn)行分析,然后執(zhí)行相應(yīng)的ISA指令上的操作。模擬器也可以通過(guò)二進(jìn)制翻譯實(shí)現(xiàn),即首先將程序中所有的源ISA指令翻譯成目標(biāo)ISA上具有同樣功能的指令,然后在目標(biāo)ISA指令機(jī)器上執(zhí)行。模擬器的基本原理如圖1-2所示。典型的模擬器有QEMU(QuickEmulator)的用戶態(tài)程序模擬、Bochs模擬器等。高級(jí)語(yǔ)言虛擬機(jī)在模擬器的基礎(chǔ)上更進(jìn)一步,將源ISA和目標(biāo)ISA完全分離開(kāi)。在高級(jí)語(yǔ)言虛擬機(jī)中,通常會(huì)設(shè)計(jì)一種全新的虛擬ISA,并在其中定義新的指令集、數(shù)據(jù)操作、寄存器的使用等類似于物理ISA中的規(guī)范。不同于普通程序和模擬器運(yùn)行的程序,高級(jí)語(yǔ)言虛擬機(jī)的程序中沒(méi)有任何具體物理ISA指令字節(jié),而是自己定義虛擬的指令字節(jié),這些指令字節(jié)通常叫作字節(jié)碼。任何想要運(yùn)行這種虛擬ISA指令的物理ISA平臺(tái)都需要實(shí)現(xiàn)一個(gè)虛擬機(jī),該虛擬機(jī)能夠執(zhí)行虛擬機(jī)ISA指令到物理ISA指令的轉(zhuǎn)換。程序員通過(guò)使用高級(jí)語(yǔ)言編寫程序,不需要考慮其具體的運(yùn)行平臺(tái),即可非常方便地實(shí)現(xiàn)程序的跨平臺(tái)分發(fā)。高級(jí)語(yǔ)言虛擬機(jī)如圖1-3所示。典型的高級(jí)語(yǔ)言虛擬機(jī)有JVM虛擬機(jī)、Python虛擬機(jī)等。圖1-2模擬器原理圖1-3高級(jí)語(yǔ)言虛擬機(jī)在高級(jí)語(yǔ)言虛擬機(jī)中,虛擬ISA是公開(kāi)的規(guī)范,每個(gè)人都可以獲得,并且可以自己寫出反編譯的工具,通過(guò)字節(jié)碼來(lái)還原程序的源碼。這也是為什么使用Java語(yǔ)言的程序常常需要進(jìn)行代碼混淆。假設(shè)我們自己定義一個(gè)虛擬的ISA,但是并不公開(kāi)其規(guī)范,并且可以時(shí)不時(shí)地修改這些規(guī)范,然后將自帶的虛擬機(jī)和字節(jié)碼合起來(lái)一起進(jìn)行分發(fā),這樣使用基于物理ISA的反編譯工具就無(wú)法還原出程序的匯編代碼,這就是軟件保護(hù)中虛擬機(jī)保護(hù)的原理。進(jìn)程、模擬器、高級(jí)語(yǔ)言虛擬機(jī)提供的都是指令的執(zhí)行環(huán)境,而系統(tǒng)虛擬機(jī)提供的是一個(gè)完整的系統(tǒng)環(huán)境。在這個(gè)環(huán)境中,能夠運(yùn)行多個(gè)用戶的多個(gè)進(jìn)程。通過(guò)系統(tǒng)虛擬化技術(shù),能夠在單個(gè)的宿主機(jī)硬件平臺(tái)上運(yùn)行多個(gè)虛擬機(jī),每個(gè)虛擬機(jī)都有著完整的虛擬機(jī)硬件,如虛擬的CPU、內(nèi)存、虛擬的外設(shè)等,并且虛擬機(jī)之間能夠?qū)崿F(xiàn)完整的隔離。早期系統(tǒng)虛擬機(jī)誕生的原因主要是大型計(jì)算機(jī)系統(tǒng)非常龐大且昂貴,需要多個(gè)用戶共享,而用戶希望可以自由地運(yùn)行其需要的操作系統(tǒng)。在系統(tǒng)虛擬化中,管理全局物理資源的軟件叫作虛擬機(jī)監(jiān)控器(VirtualMachineMonitor,VMM),VMM之于虛擬機(jī)就如同操作系統(tǒng)之于進(jìn)程,VMM利用時(shí)分復(fù)用或者空分復(fù)用的辦法將硬件資源在各個(gè)虛擬機(jī)之間進(jìn)行分配。系統(tǒng)虛擬機(jī)原理如圖1-4所示。典型的系統(tǒng)虛擬化解決方案包括VMwareWorkstation、QEMU、VirtualBox和HyperV等。圖1-4系統(tǒng)虛擬機(jī)1.1.3系統(tǒng)虛擬化的歷史虛擬化基本上是與操作系統(tǒng)同時(shí)出現(xiàn)的,早在大型機(jī)時(shí)代就已經(jīng)存在了,如20世紀(jì)60年代IBM的分時(shí)系統(tǒng)。那個(gè)時(shí)候的計(jì)算機(jī)普遍比較昂貴,系統(tǒng)虛擬化的主要目的是在多用戶之間實(shí)現(xiàn)物理資源的共享。隨著之后計(jì)算機(jī)的不斷發(fā)展、計(jì)算機(jī)價(jià)格的下降以及個(gè)人計(jì)算機(jī)的普及,用戶對(duì)虛擬化的需求大大減少,系統(tǒng)虛擬化技術(shù)的發(fā)展也逐漸沒(méi)落了下來(lái)。隨著硬件技術(shù)的再次發(fā)展,普通PC也能夠支持多個(gè)系統(tǒng)同時(shí)運(yùn)行,虛擬化又重新出現(xiàn)在人們的視野中,VMware在1998年的成立標(biāo)志著虛擬化的全面復(fù)興,隨后2001年劍橋大學(xué)開(kāi)發(fā)了Xen。隨著云計(jì)算概念的提出與實(shí)踐的落地,虛擬化更加有了用武之地。虛擬化能夠?qū)⒁慌_(tái)小型服務(wù)器或者普通PC虛擬出多個(gè)虛擬機(jī),每個(gè)虛擬機(jī)都可以以計(jì)算資源的形式出售給租戶,這樣不僅能夠提升資源的利用率,還能夠非常方便地刪除/創(chuàng)建各種規(guī)格的虛擬機(jī),提供按需分配的功能。系統(tǒng)虛擬化在云計(jì)算的支持下得到了非常迅速的發(fā)展,如之前的x86架構(gòu)不支持硬件層面的虛擬機(jī),導(dǎo)致VMM的設(shè)計(jì)和實(shí)現(xiàn)都比較麻煩,并且性能也不是很好。為了克服x86架構(gòu)的虛擬化缺陷,Intel和AMD都相繼在CPU硬件層面增加了虛擬化的支持。隨著用戶對(duì)性能需求的不斷提升,內(nèi)存、外設(shè)等也在硬件層面提供了對(duì)虛擬化的支持。2006年,以色列的初創(chuàng)公司Qumranet利用Intel的硬件虛擬化技術(shù)在Linux內(nèi)核上開(kāi)發(fā)了KVM(KernelVirtualMachine)。KVM架構(gòu)精簡(jiǎn),與Linux內(nèi)核天然融合,得以很快進(jìn)入內(nèi)核。后來(lái)RedHat收購(gòu)了Qumranet,全力投入到KVM的建設(shè)中。KVM現(xiàn)在已經(jīng)是一個(gè)非常成功的虛擬化VMM,廣泛應(yīng)用在各種開(kāi)源云平臺(tái)上,成為云計(jì)算的基石。如同操作系統(tǒng)一樣,虛擬化方案也有很多,這里簡(jiǎn)單介紹幾個(gè)。●VMwareWorkstation:VMware最早的產(chǎn)品,至今仍有大量用戶使用。VMwareWorkstation能夠很方便地在PC上構(gòu)建一個(gè)虛擬機(jī),用戶可以在其上安裝各種操作系統(tǒng),能夠非常方便地完成多種任務(wù),比如跨平臺(tái)的開(kāi)發(fā)測(cè)試,不需要再獨(dú)立使用一個(gè)單獨(dú)的宿主機(jī)。比如在進(jìn)行惡意軟件的分析時(shí),一般情況下不需要擔(dān)心病毒會(huì)破壞自己的計(jì)算機(jī)。●VirtualBox:最早由一個(gè)德國(guó)公司開(kāi)發(fā),后來(lái)被甲骨文收購(gòu)。它的優(yōu)點(diǎn)是性能不錯(cuò)并且開(kāi)源,能夠很方便地用來(lái)實(shí)現(xiàn)一些定制需求,但是不如VMwareWorkstation穩(wěn)定?!馠yperV:微軟提供的虛擬化解決方案,微軟用它來(lái)構(gòu)建自己的云計(jì)算平臺(tái)?!馲en:早期的開(kāi)源虛擬化方案,出現(xiàn)在各種硬件虛擬化技術(shù)之前。它的設(shè)計(jì)有很多不可避免的問(wèn)題,比如虛擬機(jī)的內(nèi)存管理需要與Xen一起協(xié)作完成,這導(dǎo)致了非常多的安全問(wèn)題。雖然后來(lái)Xen也支持利用硬件虛擬化的虛擬機(jī),但是其發(fā)展已經(jīng)遠(yuǎn)不如KVM。即便如此,Xen作為早期的開(kāi)源VMM,其諸多思想直到今天也在影響著虛擬化社區(qū)。1.2QEMU與KVM架構(gòu)介紹1.2.1QEMU與KVM歷史QEMU和KVM經(jīng)常被人們放在一起討論,其實(shí)兩者的關(guān)系完全可以解耦合。QEMU最開(kāi)始是由法國(guó)程序員FabriceBellard開(kāi)發(fā)的一個(gè)模擬器。QEMU能夠完成用戶程序模擬和系統(tǒng)虛擬化模擬。用戶程序模擬指的是QEMU能夠?qū)橐粋€(gè)平臺(tái)編譯的二進(jìn)制文件運(yùn)行在另一個(gè)不同的平臺(tái),如一個(gè)ARM指令集的二進(jìn)制程序,通過(guò)QEMU的TCG(TinyCodeGenerator)引擎的處理之后,ARM指令被轉(zhuǎn)換成TCG中間代碼,然后再轉(zhuǎn)換成目的平臺(tái)的代碼。系統(tǒng)虛擬化模擬指的是QEMU能夠模擬一個(gè)完整的系統(tǒng)虛擬機(jī),該虛擬機(jī)有自己的虛擬CPU、芯片組、虛擬內(nèi)存以及各種虛擬外部設(shè)備,能夠?yàn)樘摂M機(jī)中運(yùn)行的操作系統(tǒng)和應(yīng)用軟件呈現(xiàn)出與物理計(jì)算機(jī)完全一致的硬件視圖。QEMU能夠模擬的平臺(tái)很多,包括x86、ARM、MIPS、PPC等,早期的QEMU都是通過(guò)TCG來(lái)完成各種硬件平臺(tái)的模擬,所有的虛擬機(jī)指令需要經(jīng)過(guò)QEMU的轉(zhuǎn)換。系統(tǒng)虛擬機(jī)天生適用于云計(jì)算。云計(jì)算提供了一種按需服務(wù)的模式,讓用戶能夠很方便地根據(jù)自己的需求使用各種計(jì)算、網(wǎng)絡(luò)、存儲(chǔ)資源。以計(jì)算資源中的虛擬機(jī)為例,用戶可以指定不同CPU模型和內(nèi)存規(guī)格的虛擬機(jī)。云計(jì)算平臺(tái)可以通過(guò)系統(tǒng)虛擬化技術(shù)很方便地滿足用戶的需求。如果用戶刪除資源,云計(jì)算平臺(tái)可以直接刪除其對(duì)應(yīng)的虛擬機(jī)。早期的QEMU都是軟件模擬的,很明顯其在性能上是不能滿足要求的。所以早期的云計(jì)算平臺(tái)通常使用Xen作為其底層虛擬化平臺(tái)。前面提到過(guò),Xen早期是在x86架構(gòu)上直接完成的虛擬化,這需要修改虛擬機(jī)內(nèi)部的操作系統(tǒng),也使得Xen的整個(gè)VMM非常復(fù)雜,缺陷比較多。Intel和AMD在2005年左右開(kāi)始在CPU層面提供對(duì)系統(tǒng)虛擬化的支持,叫作硬件虛擬化,Intel在x86指令集的基礎(chǔ)上增加了一套VMX擴(kuò)展指令VT-x,為CPU增加了新的運(yùn)行模式,完成了x86虛擬化漏洞的修補(bǔ)。通過(guò)新的硬件虛擬化指令,可以非常方便地構(gòu)造VMM,并且x86虛擬機(jī)中的代碼能夠原生地運(yùn)行在物理CPU上。以色列初創(chuàng)公司Qumranet基于新的虛擬化指令集實(shí)現(xiàn)了KVM,并推廣到Linux內(nèi)核社區(qū)。KVM本身是一個(gè)內(nèi)核模塊,導(dǎo)出了一系列的接口到用戶空間,用戶空間可以使用這些接口創(chuàng)建虛擬機(jī)。最開(kāi)始KVM只負(fù)責(zé)最核心的CPU虛擬化和內(nèi)存虛擬化部分,使用QEMU作為其用戶態(tài)組件,負(fù)責(zé)完成大量外設(shè)的模擬,當(dāng)時(shí)的方案被稱為QEMU-KVM。KVM的具體設(shè)計(jì)與實(shí)現(xiàn)可以參考AviKivity等人在2007年發(fā)表的論文“KVM:TheLinuxVirtualMachineMonitor”。由于KVM的設(shè)計(jì)架構(gòu)精簡(jiǎn),能夠跟現(xiàn)有的Linux內(nèi)核無(wú)縫吻合,因此在社區(qū)獲得了極大的關(guān)注與支持。特別是隨著RedHat投入大量的人力去完善QEMU和KVM,QEMU社區(qū)得到了飛速發(fā)展。直到現(xiàn)在,QEMU社區(qū)依然非?;钴S,但是其主要用途已經(jīng)不是作為一個(gè)模擬器了,而是作為以QEMU-KVM為基礎(chǔ)的為云計(jì)算服務(wù)的系統(tǒng)虛擬化軟件。當(dāng)然,不僅僅是KVM將QEMU作為應(yīng)用層組件,Xen后來(lái)支持的硬件虛擬機(jī)也使用QEMU作為其用戶態(tài)組件來(lái)完成虛擬機(jī)的設(shè)備模擬。1.2.2QEMU與KVM架構(gòu)QEMU與KVM的完整架構(gòu)如圖1-5所示。該圖來(lái)自QEMU官網(wǎng),比較完整地展現(xiàn)了QEMU與KVM虛擬化的各個(gè)方面,包括QEMU的運(yùn)行機(jī)制,KVM的組成,QEMU與KVM的關(guān)系,虛擬機(jī)CPU、內(nèi)存、外設(shè)等的虛擬化,下面對(duì)其進(jìn)行簡(jiǎn)要介紹。QEMU與KVM架構(gòu)整體上分為3個(gè)部分,對(duì)應(yīng)圖中的3個(gè)部分。左邊上半部分是所謂的VMXroot模式的應(yīng)用層,下面是VMXroot模式的內(nèi)核層。所謂VMXroot,其實(shí)是相對(duì)于VMXnon-root模式而言的。VMXroot和VMXnon-root都是CPU引入了支持硬件虛擬化的指令集VT-x之后出現(xiàn)的概念。VT-x的概念會(huì)在第4章CPU虛擬化中進(jìn)行詳細(xì)介紹,現(xiàn)在可以將VMXroot理解成宿主機(jī)模式,將VMXnon-root理解成虛擬機(jī)模式。右邊上半部分表示的是虛擬機(jī)的運(yùn)行,虛擬機(jī)運(yùn)行在VMXnon-root模式下。VMXroot模式與未引入VT-x之前是一樣的,CPU在運(yùn)行包括QEMU在內(nèi)的普通進(jìn)程和宿主機(jī)的操作系統(tǒng)內(nèi)核時(shí),CPU處在該模式。CPU在運(yùn)行虛擬機(jī)中的用戶程序和操作系統(tǒng)代碼的時(shí)候處于VMXnon-root模式。需要注意的是,CPU的運(yùn)行模式與CPU運(yùn)行時(shí)的特權(quán)等級(jí)是相互正交的,虛擬機(jī)在VMXroot模式和VMXnon-root模式下都有ring0到ring3四個(gè)特權(quán)級(jí)別。圖1-5左邊上半部分列出了QEMU的主要任務(wù),QEMU在初始化的時(shí)候會(huì)創(chuàng)建模擬的芯片組,創(chuàng)建CPU線程來(lái)表示虛擬機(jī)的CPU執(zhí)行流,在QEMU的虛擬地址空間中分配空間作為虛擬機(jī)的物理地址,QEMU還需要根據(jù)用戶在命令行指定的設(shè)備為虛擬機(jī)創(chuàng)建對(duì)應(yīng)的虛擬設(shè)備。在虛擬機(jī)運(yùn)行期間,QEMU會(huì)在主線程中監(jiān)聽(tīng)多種事件,這些事件包括虛擬機(jī)對(duì)設(shè)備的I/O訪問(wèn)、用戶對(duì)虛擬機(jī)管理界面、虛擬設(shè)備對(duì)應(yīng)的宿主機(jī)上的一些I/O事件(比如虛擬機(jī)網(wǎng)絡(luò)數(shù)據(jù)的接收)等。QEMU應(yīng)用層接收到這些事件之后會(huì)調(diào)用預(yù)先定義好的函數(shù)進(jìn)行處理。圖1-5QEMU與KVM整體架構(gòu)圖圖1-5右邊上半部分表示的是虛擬機(jī)的運(yùn)行。對(duì)虛擬機(jī)本身來(lái)講,它也有自己的應(yīng)用層和內(nèi)核層,只不過(guò)是VMXnon-root下的。QEMU和KVM對(duì)虛擬機(jī)中的操作系統(tǒng)來(lái)說(shuō)是完全透明的,常用的操作系統(tǒng)可以不經(jīng)修改就直接運(yùn)行在虛擬機(jī)中。虛擬機(jī)的一個(gè)CPU對(duì)應(yīng)為QEMU進(jìn)程中的一個(gè)線程,通過(guò)QEMU和KVM的相互協(xié)作,這些線程會(huì)被宿主機(jī)操作系統(tǒng)正常調(diào)度,直接執(zhí)行虛擬機(jī)中的代碼。虛擬機(jī)中的物理內(nèi)存對(duì)應(yīng)為QEMU進(jìn)程中的虛擬內(nèi)存,虛擬機(jī)中的操作系統(tǒng)有自己的頁(yè)表管理,完成虛擬機(jī)虛擬地址到虛擬機(jī)物理地址的轉(zhuǎn)換,再經(jīng)過(guò)KVM的頁(yè)表完成虛擬機(jī)物理地址到宿主機(jī)物理地址的轉(zhuǎn)換。虛擬機(jī)中的設(shè)備是通過(guò)QEMU呈現(xiàn)給它的,操作系統(tǒng)在啟動(dòng)的時(shí)候進(jìn)行設(shè)備枚舉,加載對(duì)應(yīng)的驅(qū)動(dòng)。在運(yùn)行過(guò)程中,虛擬機(jī)操作系統(tǒng)通過(guò)設(shè)備的I/O端口(PortIO、PIO)或者M(jìn)MIO(MemoryMappedI/O)進(jìn)行交互,KVM會(huì)截獲這個(gè)請(qǐng)求,大多數(shù)時(shí)候KVM會(huì)將請(qǐng)求分發(fā)到用戶空間的QEMU進(jìn)程中,由QEMU處理這些I/O請(qǐng)求。圖1-5下半部分表示的是位于Linux內(nèi)核中的KVM驅(qū)動(dòng)。KVM驅(qū)動(dòng)以雜項(xiàng)(misc)設(shè)備驅(qū)動(dòng)的方式存在于內(nèi)核中。一方面,KVM通過(guò)“/dev/kvm”設(shè)備導(dǎo)出了一系列的接口,QEMU等用戶態(tài)程序可以通過(guò)這些接口來(lái)控制虛擬機(jī)的各個(gè)方面,比如CPU個(gè)數(shù)、內(nèi)存布局、運(yùn)行等。另一方面,KVM需要截獲虛擬機(jī)產(chǎn)生的虛擬機(jī)退出(VMExit)事件并進(jìn)行處理。QEMU和KVM聯(lián)合起來(lái)共同完成虛擬機(jī)各個(gè)組件的虛擬化,這里對(duì)幾個(gè)重要組件的虛擬化進(jìn)行簡(jiǎn)單介紹。首先介紹CPU虛擬化。QEMU創(chuàng)建虛擬機(jī)CPU線程,在初始化的時(shí)候會(huì)設(shè)置好相應(yīng)的虛擬CPU寄存器的值,然后調(diào)用KVM的接口,將虛擬機(jī)運(yùn)行起來(lái),在物理CPU上執(zhí)行虛擬機(jī)的代碼。當(dāng)虛擬機(jī)運(yùn)行起來(lái)之后,KVM需要截獲虛擬機(jī)中的敏感指令,當(dāng)虛擬機(jī)中的代碼是敏感指令或者說(shuō)滿足了一定的退出條件時(shí),CPU會(huì)從VMXnon-root模式退出到KVM,這叫作VMExit,這就像在用戶態(tài)執(zhí)行指令陷入內(nèi)核一樣。虛擬機(jī)的退出首先陷入到KVM中進(jìn)行處理,如果KVM無(wú)法處理,比如說(shuō)虛擬機(jī)寫了設(shè)備的寄存器地址,那么KVM會(huì)將這個(gè)寫操作分派到QEMU中進(jìn)行處理,當(dāng)KVM或者QEMU處理好了退出事件之后,又可以將CPU置于VMXnon-root模式運(yùn)行虛擬機(jī)代碼,這叫作VMEntry。虛擬機(jī)就這樣不停地進(jìn)行VMExit和VMEntry,CPU會(huì)加載對(duì)應(yīng)的宿主機(jī)狀態(tài)或者虛擬機(jī)狀態(tài),如圖1-6所示。KVM使用一個(gè)結(jié)構(gòu)來(lái)保存虛擬機(jī)VMExit和VMEntry的狀態(tài),叫作VMCS。圖1-6CPU虛擬化原理其次介紹內(nèi)存虛擬化。如同物理機(jī)運(yùn)行需要內(nèi)存一樣,虛擬機(jī)的運(yùn)行同樣離不開(kāi)內(nèi)存,QEMU在初始化的時(shí)候需要調(diào)用KVM的接口向KVM告知虛擬機(jī)所需要的所有物理內(nèi)存。QEMU在初始化的時(shí)候會(huì)通過(guò)mmap系統(tǒng)調(diào)用分配虛擬內(nèi)存空間作為虛擬機(jī)的物理內(nèi)存,QEMU在不斷更新內(nèi)存布局的過(guò)程中會(huì)持續(xù)調(diào)用KVM接口通知內(nèi)核KVM模塊虛擬機(jī)的內(nèi)存分布。虛擬機(jī)在運(yùn)行過(guò)程中,首先需要將虛擬機(jī)的虛擬地址(GuestVirtualAddress,GVA)轉(zhuǎn)換成虛擬機(jī)的物理地址(GuestPhysicalAddress,GPA),然后將虛擬機(jī)的物理地址轉(zhuǎn)換成宿主機(jī)的虛擬地址(HostVirtualAddress,HVA),最終轉(zhuǎn)換成宿主機(jī)的物理地址(HostPhysicalAddress,HPA)。在CPU支持EPT(ExtendedPageTable,擴(kuò)展頁(yè)表)之前,虛擬機(jī)通過(guò)影子頁(yè)表實(shí)現(xiàn)從虛擬機(jī)虛擬地址到宿主機(jī)物理地址的轉(zhuǎn)換,是一種軟件實(shí)現(xiàn)。當(dāng)CPU支持EPT之后,CPU會(huì)自動(dòng)完成虛擬機(jī)物理地址到宿主機(jī)物理地址的轉(zhuǎn)換。虛擬機(jī)在第一次訪問(wèn)內(nèi)存的時(shí)候就會(huì)陷入到KVM,KVM會(huì)逐漸建立起所謂的EPT頁(yè)面。這樣虛擬機(jī)的虛擬CPU在后面訪問(wèn)虛擬機(jī)虛擬內(nèi)存地址的時(shí)候,首先會(huì)被轉(zhuǎn)換為虛擬機(jī)物理地址,接著會(huì)查找EPT頁(yè)表,然后得到宿主機(jī)物理地址,其內(nèi)存尋址過(guò)程如圖1-7所示,整個(gè)過(guò)程全部由硬件完成,效率很高。由于現(xiàn)在EPT都是標(biāo)配,本書(shū)將只關(guān)注EPT存在的情況。圖1-7EPT原理再次介紹外設(shè)虛擬化。一個(gè)計(jì)算機(jī)系統(tǒng)離不開(kāi)大量的外部設(shè)備,網(wǎng)卡、磁盤等通常都是計(jì)算機(jī)系統(tǒng)必不可少的組成部分。虛擬化的一個(gè)煩瑣任務(wù)就是為虛擬機(jī)提供大量的設(shè)備支持,如同Linux內(nèi)核中最多的代碼是設(shè)備驅(qū)動(dòng),QEMU最多的代碼是設(shè)備模擬。設(shè)備模擬的本質(zhì)是要為虛擬機(jī)提供一個(gè)與物理設(shè)備接口完全一致的虛擬接口。虛擬機(jī)中的操作系統(tǒng)與設(shè)備進(jìn)行的數(shù)據(jù)交互或者由QEMU和(或)KVM完成,或者由宿主機(jī)上對(duì)應(yīng)的后端設(shè)備完成。QEMU在初始化過(guò)程中會(huì)創(chuàng)建好模擬芯片組和必要的模擬設(shè)備,包括南北橋芯片、PCI根總線、ISA根總線等總線系統(tǒng),以及各種PCI設(shè)備、ISA設(shè)備等。QEMU的命令行可以指定可選的設(shè)備以及設(shè)備配置項(xiàng)。大部分情況下,用戶對(duì)虛擬機(jī)的需求都體現(xiàn)在對(duì)虛擬設(shè)備的需求上,比如常見(jiàn)的網(wǎng)絡(luò)、存儲(chǔ)資源對(duì)應(yīng)QEMU的網(wǎng)卡模擬和硬盤模擬。這些需求也導(dǎo)致了QEMU虛擬設(shè)備的快速發(fā)展。設(shè)備模擬經(jīng)歷了非常大的發(fā)展,最開(kāi)始的QEMU只有純軟件模擬,虛擬機(jī)內(nèi)核不用做任何修改,每一次對(duì)設(shè)備的寄存器讀寫都會(huì)陷入到KVM,進(jìn)而到QEMU,QEMU再對(duì)這些請(qǐng)求進(jìn)行處理并模擬硬件行為,純軟件模擬設(shè)備如圖1-8a所示。顯然,軟件模擬會(huì)導(dǎo)致非常多的QEMU/KVM介入,效率不高。為了提高虛擬設(shè)備的性能,社區(qū)提出了virtio設(shè)備方案。virtio設(shè)備是一類特殊的設(shè)備,并沒(méi)有對(duì)應(yīng)的物理設(shè)備,所以需要虛擬機(jī)內(nèi)部操作系統(tǒng)安裝特殊的virtio驅(qū)動(dòng),virtio設(shè)備模擬如圖1-8b所示。virtio設(shè)備將QEMU變成了半虛擬化方案,因?yàn)槠浔举|(zhì)上修改了虛擬機(jī)操作系統(tǒng)內(nèi)核,與之相對(duì)的完全不用修改虛擬機(jī)操作系統(tǒng)的方案叫作完全虛擬化。virtio仍然不能完全滿足一些高性能的場(chǎng)景,于是又有了設(shè)備直通方案,也就是將物理硬件設(shè)備直接掛到虛擬機(jī)上,虛擬機(jī)直接與物理設(shè)備交互,盡可能在I/O路徑上減少Q(mào)EMU/KVM的參與,直通設(shè)備原理如圖1-8c所示。與設(shè)備直通經(jīng)常一起使用的有設(shè)備的硬件虛擬化支持技術(shù)SRIOV(SingleRootI/OVirtualization,單根輸入/輸出虛擬化),SRIOV能夠?qū)蝹€(gè)的物理硬件高效地虛擬出多個(gè)虛擬硬件。通過(guò)將SRIOV虛擬出來(lái)的硬件直通到虛擬機(jī)中,虛擬機(jī)能夠非常高效地使用這些設(shè)備。圖1-8QEMU的各種設(shè)備模擬方式a)純軟件的設(shè)備模擬b)virtio設(shè)備模擬c)直通設(shè)備最后介紹中斷虛擬化。中斷系統(tǒng)是一個(gè)計(jì)算系統(tǒng)必不可少的組成部分。操作系統(tǒng)通過(guò)寫設(shè)備的I/O端口或者M(jìn)MIO地址來(lái)與設(shè)備交互,設(shè)備通過(guò)發(fā)送中斷來(lái)通知虛操作系統(tǒng)事件,圖1-9顯示了模擬設(shè)備向虛擬機(jī)注入中斷的狀態(tài)。QEMU在初始化主板芯片的時(shí)候初始化中斷控制器。QEMU支持單CPU的Intel8259中斷控制器以及SMP的I/OAPIC(I/OAdvancedProgrammableInterruptController)和LAPIC(LocalAdvancedProgrammableInterruptController)中斷控制器。傳統(tǒng)上,如果虛擬外設(shè)通過(guò)QEMU向虛擬機(jī)注入中斷,需要先陷入到KVM,然后由KVM向虛擬機(jī)注入中斷,這是一個(gè)非常費(fèi)時(shí)的操作,為了提高虛擬機(jī)的效率,KVM自己也實(shí)現(xiàn)了中斷控制器Intel8259、I/OAPIC以及LAPIC。用戶可以有選擇地讓QEMU或者KVM模擬全部中斷控制器,也可以讓QEMU模擬Intel8259中斷控制器和I/OAPIC,讓KVM模擬LAPIC。QEMU/KVM一方面需要完成這項(xiàng)中斷設(shè)備的模擬,另一方面需要模擬中斷的請(qǐng)求。中斷請(qǐng)求的形式大體上包括傳統(tǒng)ISA設(shè)備連接Intel8259中斷控制器產(chǎn)生的中斷請(qǐng)求,PCI設(shè)備的INTx中斷請(qǐng)求以及MSI和MSIX中斷請(qǐng)求。圖1-9虛擬設(shè)備的中斷注入1.3KVMAPI使用實(shí)例前面提到KVM導(dǎo)出了一系列接口供用戶態(tài)創(chuàng)建、配置、啟動(dòng)虛擬機(jī),典型的用戶態(tài)軟件是QEMU。之所以將QEMU和KVM經(jīng)常聯(lián)系起來(lái),是因?yàn)镵VM創(chuàng)立之初重用了QEMU的設(shè)備模擬部分,本質(zhì)上來(lái)說(shuō),QEMU和KVM可以不必相互依賴。本節(jié)將以一個(gè)非常簡(jiǎn)單的例子展示QEMU和KVM的關(guān)系。這個(gè)例子包括兩個(gè)部分,第一部分是一個(gè)精簡(jiǎn)版內(nèi)核,這個(gè)內(nèi)核非常簡(jiǎn)單,它的任務(wù)僅僅是向I/O端口寫入數(shù)據(jù)。第二部分可以看作是一個(gè)精簡(jiǎn)版的QEMU,它的任務(wù)也非常簡(jiǎn)單,就是將上述精簡(jiǎn)內(nèi)核的端口數(shù)據(jù)打印出來(lái)。精簡(jiǎn)的內(nèi)核代碼如下:向端口0xf1寫入Hello字符串,然后調(diào)用hlt指令。使用如下命令編譯上述匯編代碼。精簡(jiǎn)版的QEMU代碼如下。編譯并執(zhí)行該文件??梢钥吹竭@個(gè)名為light-qemu的精簡(jiǎn)版QEMU輸出了精簡(jiǎn)版內(nèi)核向端口寫入的數(shù)據(jù)。下面對(duì)light-qemu程序進(jìn)行簡(jiǎn)單介紹。KVM通過(guò)一組ioctl向用戶空間導(dǎo)出接口,這些接口能夠用于虛擬機(jī)的創(chuàng)建、虛擬機(jī)內(nèi)存的設(shè)置、虛擬機(jī)VCPU的創(chuàng)建與運(yùn)行等。按照接口所使用的文件描述符(filedescriptor,fd)不同,KVM的這組ioctl接口可以分為三類:1)系統(tǒng)全局的ioctl,這類ioctl的作用對(duì)象是KVM模塊本身,比如一些全局的配置項(xiàng),創(chuàng)建虛擬機(jī)的ioctl也在此例。2)虛擬機(jī)相關(guān)的ioctl,這類ioctl的作用對(duì)象是一臺(tái)虛擬機(jī),比如設(shè)置虛擬機(jī)的內(nèi)存布局、創(chuàng)建虛擬機(jī)VCPU也在此例。3)虛擬機(jī)VCPU相關(guān)的ioctl,這類ioctl的作用對(duì)象是一個(gè)虛擬機(jī)的VCPU,比如說(shuō)開(kāi)始虛擬機(jī)VCPU的運(yùn)行。在light-qemu中,首先通過(guò)打開(kāi)“/dev/kvm”獲取系統(tǒng)中KVM子系統(tǒng)的文件描述符kvmfd,為了保持應(yīng)用層和內(nèi)核的統(tǒng)一,可以通過(guò)ioctl(KVM_GET_API_VERSION)獲取KVM的版本號(hào),從而使應(yīng)用層知道相關(guān)接口在內(nèi)核是否有支持。接著在kvmfd上面調(diào)用ioctl(KVM_CREATE_VM)創(chuàng)建一個(gè)虛擬機(jī),該ioctl返回一個(gè)代表虛擬機(jī)的文件描述符vmfd。這代表了一個(gè)完整的虛擬機(jī)系統(tǒng),可以通過(guò)vmfd控制虛擬機(jī)的內(nèi)存、VCPU等。內(nèi)存是一個(gè)計(jì)算機(jī)必不可少的組件,所以在創(chuàng)建了虛擬機(jī)之后,需要給虛擬機(jī)分配物理內(nèi)存,虛擬機(jī)的物理內(nèi)存對(duì)應(yīng)QEMU的進(jìn)程地址空間,這里使用mmap系統(tǒng)調(diào)用分配了一頁(yè)虛擬機(jī)內(nèi)存,并且將精簡(jiǎn)版的內(nèi)核代碼讀入了這段空間中。之后用分配的虛擬內(nèi)存地址初始化kvm_userspace_memory_region對(duì)象,然后調(diào)用ioctl(KVM_SET_USER_MEMORY_REGION),這就為虛擬機(jī)指定了一個(gè)內(nèi)存條。其中slot用來(lái)表示不同的內(nèi)存空間,guest_phys_addr表示這段空間在虛擬機(jī)物理內(nèi)存空間的位置,memory_size表示這段物理空間的大小,userspace_addr則表示這段物理空間對(duì)應(yīng)在宿主機(jī)上的虛擬機(jī)地址。設(shè)置好虛擬機(jī)的內(nèi)存后,light-qemu接著在vmfd上調(diào)用ioclt(KVM_CREATE_VCPU)來(lái)創(chuàng)建虛擬機(jī)VCPU。每一個(gè)VCPU都有一個(gè)structkvm_run結(jié)構(gòu),用來(lái)在用戶態(tài)(這里是light-qemu)和內(nèi)核態(tài)(KVM)共享數(shù)據(jù)。用戶態(tài)程序需要將這段空間映射到用戶空間,為此首先調(diào)用ioctl(KVM_GET_VCPU_MMAP_SIZE)得到這個(gè)結(jié)構(gòu)的大小,注意這里是對(duì)kvmfd調(diào)用ioctl,因?yàn)檫@個(gè)對(duì)KVM所有VCPU都是一樣的。接著調(diào)用mmap,將kvm_run映射到了用戶態(tài)空間。為了讓虛擬機(jī)VCPU運(yùn)行起來(lái),需要設(shè)置VCPU的相關(guān)寄存器,其中段寄存器和控制寄存器等特殊寄存器存放在kvm_sregs,通過(guò)ioctl(KVM_GET_SREGS)讀取和修改,通用寄存器存放在結(jié)構(gòu)kvm_regs中,通過(guò)ioctl(KVM_SET_REGS)讀取和修改。最重要的是設(shè)置CS和IP寄存器,light-qemu都將其設(shè)置為0,由于代碼加在了虛擬機(jī)物理地址0處,所以虛擬機(jī)VCPU運(yùn)行的時(shí)候直接從地址0處取指令開(kāi)始運(yùn)行。至此,一個(gè)簡(jiǎn)單的虛擬機(jī)和虛擬機(jī)VCPU、內(nèi)存都已經(jīng)準(zhǔn)備完畢,寄存器也設(shè)置好了,這個(gè)時(shí)候可以讓虛擬機(jī)運(yùn)行起來(lái)了。通常在一個(gè)循環(huán)中對(duì)vcpufd調(diào)用ioctl(KVM_RUN)。內(nèi)核在處理這個(gè)ioctl時(shí)會(huì)把VCPU調(diào)度到物理CPU上運(yùn)行,VCPU在運(yùn)行過(guò)程中遇到一些敏感指令時(shí)會(huì)退出,如果內(nèi)核態(tài)的KVM不能處理就會(huì)交給應(yīng)用層軟件處理,此時(shí)ioctl系統(tǒng)調(diào)用返回,并且將一些信息保存到kvm_run,這樣用戶態(tài)程序就能夠知道導(dǎo)致虛擬機(jī)退出的原因,然后根據(jù)原因進(jìn)行相應(yīng)的處理。在這個(gè)例子中,虛擬機(jī)內(nèi)核向端口寫數(shù)據(jù)會(huì)產(chǎn)生KVM_EXIT_IO的退出,表示虛擬機(jī)內(nèi)部讀寫了端口,在輸出了端口數(shù)據(jù)之后讓虛擬機(jī)繼續(xù)執(zhí)行,執(zhí)行到最后一個(gè)hlt指令時(shí),會(huì)產(chǎn)生KVM_EXIT_HLT類型的退出,此時(shí)虛擬機(jī)運(yùn)行結(jié)束。當(dāng)然,與精簡(jiǎn)的QEMU和精簡(jiǎn)的內(nèi)核相比,實(shí)際的QEMU和實(shí)際的操作系統(tǒng)的內(nèi)核復(fù)雜度都遠(yuǎn)遠(yuǎn)超過(guò)這個(gè)水平,但是其基本原理都是類似的。第2章QEMU基本組件2.1QEMU事件循環(huán)機(jī)制2.1.1glib事件循環(huán)機(jī)制“一切皆文件”是UNIX/Linux的著名哲學(xué)理念,Linux中的具體文件、設(shè)備、網(wǎng)絡(luò)socket等都可以抽象為文件。內(nèi)核中通過(guò)虛擬文件系統(tǒng)(VirtualFileSystem,VFS)抽象出一個(gè)統(tǒng)一的界面,使得訪問(wèn)文件有統(tǒng)一的接口。Linux通過(guò)fd來(lái)訪問(wèn)一個(gè)文件,應(yīng)用程序也可以調(diào)用select、poll、epoll系統(tǒng)調(diào)用來(lái)監(jiān)聽(tīng)文件的變化。QEMU程序的運(yùn)行即是基于各類文件fd事件的,QEMU在運(yùn)行過(guò)程中會(huì)將自己感興趣的文件fd添加到其監(jiān)聽(tīng)列表上并定義相應(yīng)的處理函數(shù),在其主線程中,有一個(gè)循環(huán)用來(lái)處理這些文件fd的事件,如來(lái)自用戶的輸入、來(lái)自VNC的連接、虛擬網(wǎng)卡對(duì)應(yīng)tap設(shè)備的收包等。這種事件循環(huán)機(jī)制在Windows系統(tǒng)或者其他GUI應(yīng)用中非常常見(jiàn)。QEMU的事件循環(huán)機(jī)制基于glib,glib是一個(gè)跨平臺(tái)的、用C語(yǔ)言編寫的若干底層庫(kù)的集合。本節(jié)對(duì)glib提供的事件循環(huán)機(jī)制進(jìn)行簡(jiǎn)單介紹。glib實(shí)現(xiàn)了完整的事件循環(huán)分發(fā)機(jī)制,在這個(gè)機(jī)制中有一個(gè)主循環(huán)負(fù)責(zé)處理各種事件,事件通過(guò)事件源描述,事件源包括各種文件描述符(文件、管道或者socket)、超時(shí)和idle事件等,每種事件源都有一個(gè)優(yōu)先級(jí),idle事件源在沒(méi)有其他高優(yōu)先級(jí)的事件源時(shí)會(huì)被調(diào)度運(yùn)行。應(yīng)用程序可以利用glib的這套機(jī)制來(lái)實(shí)現(xiàn)自己的事件監(jiān)聽(tīng)與分發(fā)處理。glib使用GMainLoop結(jié)構(gòu)體來(lái)表示一個(gè)事件循環(huán),每一個(gè)GMainLoop都對(duì)應(yīng)有一個(gè)主上下文GMainContext。事件源使用GSource表示,每個(gè)GSource可以關(guān)聯(lián)多個(gè)文件描述符,每個(gè)GSource會(huì)關(guān)聯(lián)到一個(gè)GMainContext,一個(gè)GMainContext可以關(guān)聯(lián)多個(gè)GSource。glib的一個(gè)重要特點(diǎn)是能夠定義新的事件源類型,可以通過(guò)定義一組回調(diào)函數(shù)來(lái)將新的事件源添加到glib的事件循環(huán)框架中。新的事件源通過(guò)兩種方式跟主上下文交互。第一種方式是GSourceFuncs中的prepare函數(shù)可以設(shè)置一個(gè)超時(shí)時(shí)間,以此來(lái)決定主事件循環(huán)中輪詢的超時(shí)時(shí)間;第二種方式是通過(guò)g_source_add_poll函數(shù)來(lái)添加fd。glib主上下文的一次循環(huán)包括prepare、query、check、dispatch四個(gè)過(guò)程,分別對(duì)應(yīng)glib的g_main_context_prepare()、g_main_context_query()、g_main_context_check()以及g_main_context_dispatch()四個(gè)函數(shù),其狀態(tài)轉(zhuǎn)換如圖2-1所示。圖2-1glib事件循環(huán)狀態(tài)轉(zhuǎn)換圖下面簡(jiǎn)單介紹這幾個(gè)步驟:1)prepare:通過(guò)g_main_context_prepare()會(huì)調(diào)用事件對(duì)應(yīng)的prepare回調(diào)函數(shù),做一些準(zhǔn)備工作,如果事件已經(jīng)準(zhǔn)備好進(jìn)行監(jiān)聽(tīng)了,返回true。2)query:通過(guò)g_main_context_query()可以獲得實(shí)際需要調(diào)用poll的文件fd。3)check:當(dāng)query之后獲得了需要進(jìn)行監(jiān)聽(tīng)的fd,那么會(huì)調(diào)用poll對(duì)fd進(jìn)行監(jiān)聽(tīng),當(dāng)poll返回的時(shí)候,就會(huì)調(diào)用g_main_context_check()將poll的結(jié)果傳遞給主循環(huán),如果fd事件能夠被分派就會(huì)返回true。4)dispatch:通過(guò)g_main_context_dispatch()可以調(diào)用事件源對(duì)應(yīng)事件的處理函數(shù)。上面就是glib事件循環(huán)機(jī)制的處理流程,應(yīng)用程序需要做的就是把新的事件源加入到這個(gè)處理流程中,glib會(huì)負(fù)責(zé)處理事件源上注冊(cè)的各種事件。2.1.2QEMU中的事件循環(huán)機(jī)制QEMU的事件循環(huán)機(jī)制如圖2-2所示。QEMU在運(yùn)行過(guò)程中會(huì)注冊(cè)一些感興趣的事件,設(shè)置其對(duì)應(yīng)的處理函數(shù)。如對(duì)于VNC來(lái)說(shuō),會(huì)創(chuàng)建一個(gè)socket用于監(jiān)聽(tīng)來(lái)自用戶的連接,注冊(cè)其可讀事件為vnc_client_io,當(dāng)VNC有連接到來(lái)時(shí),glib的框架就會(huì)調(diào)用vnc_client_io函數(shù)。除了VNC,QEMU中還會(huì)注冊(cè)很多其他事件監(jiān)聽(tīng),如網(wǎng)卡設(shè)備的后端tap設(shè)備的收包,收到包之后QEMU調(diào)用tap_send將包路由到虛擬機(jī)網(wǎng)卡前端,若虛擬機(jī)使用qmp,那么在管理界面中,當(dāng)用戶發(fā)送qmp命令過(guò)來(lái)之后,glib會(huì)調(diào)用事先注冊(cè)的tcp_chr_accept來(lái)處理用戶的qmp命令。本節(jié)將分析QEMU的事件循環(huán)實(shí)現(xiàn)。關(guān)于QEMU的事件循環(huán)機(jī)制,F(xiàn)amZheng在KVMForum2015上有一個(gè)非常不錯(cuò)的演講,題為“ImprovingtheQEMUEventLoop”,讀者可以自行搜索學(xué)習(xí)。圖2-2QEMU事件循環(huán)機(jī)制可以通過(guò)如下命令啟動(dòng)虛擬機(jī)。在此命令行下啟動(dòng)的QEMU程序,其主循環(huán)事件總共包含了圖2-3所示的5個(gè)事件源,其中前面兩個(gè)qemu_aio_context和iohander_ctx都是類型為AioContext的自定義事件源,中間兩個(gè)VNC的事件源是glib標(biāo)準(zhǔn)事件源,最后一個(gè)不是QEMU通過(guò)調(diào)用g_source_attach添加的事件源,而是glib內(nèi)部庫(kù)自己使用的加入到事件循環(huán)的fd。qemu_aio_context和iohandler_ctx是兩個(gè)比較特殊的自定義的類型為AioContext的事件源,前者主要用于處理QEMU中塊設(shè)備相關(guān)的異步I/O請(qǐng)求通知,后者用于處理QEMU中各類事件通知,這些事件通知包括信號(hào)處理的fd、tap設(shè)備的fd以及VFIO設(shè)備對(duì)應(yīng)的中斷通知等。glib中事件源可以添加多個(gè)事件fd,對(duì)應(yīng)的AioContext表示為每一個(gè)fd在AioContext都有記錄,glib框架在執(zhí)行iohandler_ctx的分發(fā)函數(shù)時(shí),會(huì)遍歷其上所有的fd,如果某個(gè)fd上的數(shù)據(jù)準(zhǔn)備好了,就會(huì)調(diào)用相應(yīng)的回調(diào)函數(shù)。這里需要注意,每一個(gè)事件源本身都會(huì)有一個(gè)fd,當(dāng)添加一個(gè)fd到事件源時(shí),整個(gè)glib主循環(huán)都會(huì)監(jiān)聽(tīng)該fd。以前述命令為例,QEMU主循環(huán)總共會(huì)監(jiān)聽(tīng)6個(gè)fd,其中5個(gè)是事件源本身的fd,還有一個(gè)是通過(guò)系統(tǒng)調(diào)用SYS_signalfd創(chuàng)建的用來(lái)處理信號(hào)的fd,圖2-3中的tap設(shè)備fd只是作為一個(gè)例子,在上述命令行下并不會(huì)添加該fd。任何一個(gè)fd準(zhǔn)備好事件之后都可以喚醒主循環(huán)。本節(jié)末會(huì)對(duì)這6個(gè)fd的產(chǎn)生及其分析過(guò)程進(jìn)行介紹。QEMU主循環(huán)對(duì)應(yīng)的最重要的幾個(gè)函數(shù)如圖2-4所示。QEMU的main函數(shù)定義在vl.c中,在進(jìn)行好所有的初始化工作之后會(huì)調(diào)用函數(shù)main_loop來(lái)開(kāi)始主循環(huán)。圖2-3QEMU事件源實(shí)例圖2-4QEMU主循環(huán)對(duì)應(yīng)的函數(shù)main_loop及其調(diào)用的main_loop_wait的主要代碼如下。main_loop_wait函數(shù)調(diào)用了os_host_main_loop_wait函數(shù),在后者中可以找到對(duì)應(yīng)圖2-4的相關(guān)函數(shù),即每次main_loop循環(huán)的3個(gè)主要步驟。main_loop_wait在調(diào)用os_host_main_loop_wait前,會(huì)調(diào)用qemu_soonest_timeout函數(shù)先計(jì)算一個(gè)最小的timeout值,該值是從定時(shí)器列表中獲取的,表示監(jiān)聽(tīng)事件的時(shí)候最多讓主循環(huán)阻塞的事件,timeout使得QEMU能夠及時(shí)處理系統(tǒng)中的定時(shí)器到期事件。QEMU主循環(huán)的第一個(gè)函數(shù)是glib_pollfds_fill,下面的代碼顯示了該函數(shù)的工作流程。該函數(shù)的主要工作是獲取所有需要進(jìn)行監(jiān)聽(tīng)的fd,并且計(jì)算一個(gè)最小的超時(shí)時(shí)間。首先調(diào)用g_main_context_prepare開(kāi)始為主循環(huán)的監(jiān)聽(tīng)做準(zhǔn)備,接著在一個(gè)循環(huán)中調(diào)用g_main_context_query獲取需要監(jiān)聽(tīng)的fd,所有fd保存在全局變量gpollfds數(shù)組中,需要監(jiān)聽(tīng)的fd的數(shù)量保存在glib_n_poll_fds中,g_main_context_query還會(huì)返回fd時(shí)間最小的timeout,該值用來(lái)與傳過(guò)來(lái)的cur_timeout(定時(shí)器的timeout)進(jìn)行比較,選取較小的一個(gè),表示主循環(huán)最大阻塞的時(shí)間。os_host_main_loop_wait在調(diào)用glib_pollfds_fill之后就完成了圖2-4的第一步,現(xiàn)在已經(jīng)有了所有需要監(jiān)聽(tīng)的fd了,然后會(huì)調(diào)用qemu_mutex_unlock_iothread釋放QEMU大鎖(BigQemuLock,BQL),BQL會(huì)在本章第2節(jié)“QEMU線程模型”中介紹,這里略過(guò)。接著os_host_main_loop_wait函數(shù)會(huì)調(diào)用qemu_poll_ns,該函數(shù)代碼如下。它接收3個(gè)參數(shù),第一個(gè)是要監(jiān)聽(tīng)的fd數(shù)組,第二個(gè)是fds數(shù)組的長(zhǎng)度,第三個(gè)是一個(gè)timeout值,表示g_poll最多阻塞的時(shí)間。qemu_poll_ns在配置CONFIG_PPOLL時(shí)會(huì)調(diào)用ppoll,否則調(diào)用glib的函數(shù)g_poll,g_poll是一個(gè)跨平臺(tái)的poll函數(shù),用來(lái)監(jiān)聽(tīng)文件上發(fā)生的事件。qemu_poll_ns的調(diào)用會(huì)阻塞主線程,當(dāng)該函數(shù)返回之后,要么表示有文件fd上發(fā)生了事件,要么表示一個(gè)超時(shí),不管怎么樣,這都將進(jìn)入圖2-4的第三步,也就是調(diào)用glib_pollfds_poll函數(shù)進(jìn)行事件的分發(fā)處理,該函數(shù)的代碼如下。glib_pollfds_poll調(diào)用了glib框架的g_main_context_check檢測(cè)事件,然后調(diào)用g_main_context_dispatch進(jìn)行事件的分發(fā)。下面以虛擬機(jī)的VNC連接為例分析相應(yīng)的函數(shù)調(diào)用過(guò)程。VNC子模塊在初始化的過(guò)程中會(huì)在vnc_display_open中調(diào)用qio_channel_add_watch,設(shè)置其監(jiān)聽(tīng)的回調(diào)函數(shù)為vnc_listen_io,該過(guò)程最終會(huì)創(chuàng)建一個(gè)回調(diào)函數(shù)集合為qio_channel_fd_source_funcs的事件源,其中的dispatch函數(shù)為qio_channel_fd_source_dispatch,該函數(shù)會(huì)調(diào)用vnc_listen_io函數(shù)。以本小節(jié)最開(kāi)始的命令啟動(dòng)虛擬機(jī),然后在vnc_listen_io處下斷點(diǎn),使用VNC客戶端連接虛擬機(jī),QEMU進(jìn)程會(huì)中斷到調(diào)試器中,使用gdb的bt命令可以看到圖2-5所示的函數(shù)調(diào)用堆棧。上面是QEMU效仿glib實(shí)現(xiàn)的主循環(huán),但主循環(huán)存在一些缺陷,比如在主機(jī)使用多CPU的情況下伸縮性受到限制,同時(shí)主循環(huán)使用了QEMU全局互斥鎖,從而導(dǎo)致VCPU線程和主循環(huán)存在鎖競(jìng)爭(zhēng),使性能下降。為了解決這個(gè)問(wèn)題,QEMU引入了iothread事件循環(huán),把一些I/O操作分配給iothread,從而提高I/O性能。圖2-5vnc連接fd的事件處理函數(shù)堆棧2.1.3QEMU自定義事件源QEMU自定義了一個(gè)新的事件源AioContext,有兩種類型的AioContext,第一類用來(lái)監(jiān)聽(tīng)各種各樣的事件,比如iohandler_ctx,第二類是用來(lái)處理塊設(shè)備層的異步I/O請(qǐng)求,比如QEMU默認(rèn)的qemu_aio_context或者模塊自己創(chuàng)建的AioContext。這里只關(guān)注第一種情況,即事件相關(guān)的AioContext。下面的代碼列出了AioContext結(jié)構(gòu)中的主要成員。這里簡(jiǎn)單介紹一下AioContext中的幾個(gè)成員?!駍ource:glib中的GSource,每一個(gè)自定義的事件源第一個(gè)成員都是GSource結(jié)構(gòu)的成員?!駆ock:QEMU中的互斥鎖,用來(lái)保護(hù)多線程情況下對(duì)AioContext中成員的訪問(wèn)。●aio_handlers:一個(gè)鏈表頭,其鏈表中的數(shù)據(jù)類型為AioHandler,所有加入到AioContext事件源的文件fd的事件處理函數(shù)都掛到這個(gè)鏈表上?!駈otify_me和notified都與aio_notify相關(guān),主要用于在塊設(shè)備層的I/O同步時(shí)處理QEMU下半部(BottomHalvs,BH)?!駀irst_bh:QEMU下半部鏈表,用來(lái)連接掛到該事件源的下半部,QEMU的BH默認(rèn)掛在qemu_aio_context下。●notifier:事件通知對(duì)象,類型為EventNotifier,在塊設(shè)備進(jìn)行同步且需要調(diào)用BH的時(shí)候需要用到該成員。●tlg:管理掛到該事件源的定時(shí)器。剩下的結(jié)構(gòu)與塊設(shè)備層的I/O同步相關(guān),這里略過(guò)。AioContext拓展了glib中source的功能,不但支持fd的事件處理,還模擬內(nèi)核中的下半部機(jī)制,實(shí)現(xiàn)了QEMU中的下半部以及定時(shí)器的管理。接下來(lái)介紹AioContext的相關(guān)接口,這里只以文件fd的事件處理為主,涉及AioContext與塊設(shè)備層I/O同步的代碼會(huì)省略掉。首先是創(chuàng)建AioContext函數(shù)的aio_context_new,該函數(shù)的核心調(diào)用如下。aio_context_new函數(shù)首先創(chuàng)建分配了一個(gè)AioContext結(jié)構(gòu)ctx,然后初始化代表該事件源的事件通知對(duì)象ctx->notifier,接著調(diào)用了aio_set_event_notifier用來(lái)設(shè)置ctx->notifier對(duì)應(yīng)的事件通知函數(shù),初始化ctx中其他的成員。aio_set_event_notifier函數(shù)調(diào)用了aio_set_fd_handler函數(shù),后者是另一個(gè)重要的接口函數(shù),其作用是添加或者刪除事件源中的一個(gè)fd。如果作用是添加,則會(huì)設(shè)置fd對(duì)應(yīng)的讀寫函數(shù),aio_set_fd_handler即可用于從AioContext中刪除fd,也可以用于添加fd,下面的代碼去掉了刪除事件源中fd監(jiān)聽(tīng)處理的步驟,其代碼如下。aio_set_fd_handler的第一個(gè)參數(shù)ctx表示需要添加fd到哪個(gè)AioContext事件源;第二個(gè)參數(shù)fd表示添加的fd是需要在主循環(huán)中進(jìn)行監(jiān)聽(tīng)的;is_external用于塊設(shè)備層,對(duì)于事件監(jiān)聽(tīng)的fd都設(shè)置為false;io_read和io_write都是對(duì)應(yīng)fd的回調(diào)函數(shù),opaque會(huì)作為參數(shù)調(diào)用這些回調(diào)函數(shù)。aio_set_fd_handler函數(shù)首先調(diào)用find_aio_handler查找當(dāng)前事件源ctx中是否已經(jīng)有了fd,考慮新加入的情況,這里會(huì)創(chuàng)建一個(gè)名為node的AioHandler,使用fd初始化node->pfd.fd,并將其插入到ctx->aio_handlers鏈表上,調(diào)用glib接口g_source_add_poll將該fd插入到了事件源監(jiān)聽(tīng)fd列表中,設(shè)置node事件讀寫函數(shù)為io_read,io_write函數(shù),根據(jù)io_read和io_write的有無(wú)設(shè)置node->pfd.events,也就是要監(jiān)聽(tīng)的事件。aio_set_fd_handler調(diào)用之后,新的fd事件就加入到了事件源的aio_handlers鏈表上了,如圖2-6所示。圖2-6AioContext的aio_handlers鏈表aio_set_fd_handler函數(shù)一般被塊設(shè)備相關(guān)的操作直接調(diào)用,如果僅僅是添加一個(gè)普通的事件相關(guān)的fd到事件源,通常會(huì)調(diào)用其封裝函數(shù)qemu_set_fd_handler,該函數(shù)將事件fd添加到全部變量iohandler_ctx事件源中。glib中自定義的事件源需要實(shí)現(xiàn)glib循環(huán)過(guò)程中調(diào)用的幾個(gè)回調(diào)函數(shù),QEMU中為AioContext事件源定義了名為aio_source_funcs的GSourceFuns結(jié)構(gòu)。這幾個(gè)函數(shù)是自定義事件源需要實(shí)現(xiàn)的,這里介紹一下最重要的事件處理分派函數(shù)aio_ctx_dispatch。aio_ctx_dispatch代碼如下,其會(huì)調(diào)用aio_dispatch,aio_dispatch要完成3件事:第一是BH的處理,第二是處理文件fd列表中有事件的fd,第三是調(diào)用定時(shí)器到期的函數(shù)。這里分析一下文件fd的處理部分。aio_dispatch_handlers函數(shù)會(huì)遍歷aio_handlers,遍歷監(jiān)聽(tīng)fd上的事件是否發(fā)生了。fd發(fā)生的事件存在node->pfd.revents中,注冊(cè)時(shí)指定需要接受的事件存放在node->pfd.events中,revents變量保存了fd接收到的事件。對(duì)應(yīng)G_IO_IN可讀事件來(lái)說(shuō),會(huì)調(diào)用注冊(cè)的fd的io_read回調(diào),對(duì)G_IN_OUT可寫事件來(lái)說(shuō),會(huì)調(diào)用注冊(cè)的fd的io_write函數(shù)。當(dāng)然,如果當(dāng)前的fd已經(jīng)刪除了,則會(huì)刪除這個(gè)節(jié)點(diǎn)。2.1.4QEMU事件處理過(guò)程上一節(jié)介紹了QEMU的自定義事件源,本節(jié)以signalfd的處理為例介紹QEMU事件處理的過(guò)程。signalfd是Linux的一個(gè)系統(tǒng)調(diào)用,可以將特定的信號(hào)與一個(gè)fd綁定起來(lái),當(dāng)有信號(hào)到達(dá)的時(shí)候fd就會(huì)產(chǎn)生對(duì)應(yīng)的可讀事件。以如下命令啟動(dòng)虛擬機(jī)。在sigfd_handler函數(shù)下斷點(diǎn),在另一個(gè)終端向QEMU發(fā)送SIGALARM信號(hào),命令如下,其中2762是QEMU進(jìn)程號(hào)。在第一個(gè)命令行的中斷中可以看到QEMU進(jìn)程已經(jīng)在sigfd_handler函數(shù)被中斷下來(lái),圖2-7顯示了此時(shí)的函數(shù)調(diào)用情況,從中可以看到整個(gè)過(guò)程調(diào)用了glib的事件分發(fā)函數(shù)g_main_context_dispatch,然后調(diào)用了AioContext自定義事件源的回調(diào)函數(shù)aio_ctx_dispatch,最終調(diào)用到QEMU為信號(hào)注冊(cè)的可讀回調(diào)函數(shù)sigfd_handler。圖2-7sigfd_handler函數(shù)的棧回溯下面對(duì)這個(gè)過(guò)程進(jìn)行簡(jiǎn)單分析,首先分析signal事件源的初始化。vl.c中的main函數(shù)會(huì)調(diào)用qemu_init_main_loop進(jìn)行AioContext事件源的初始化,該函數(shù)代碼如下。qemu_init_main_loop函數(shù)調(diào)用qemu_signal_init將一個(gè)fd與一組信號(hào)關(guān)聯(lián)起來(lái),qemu_signal_init調(diào)用了之前提到的qemu_set_fd_handler函數(shù),設(shè)置該signalfd對(duì)應(yīng)的可讀回調(diào)函數(shù)為sigfd_handler。qemu_set_fd_handler在首次調(diào)用時(shí)會(huì)調(diào)用iohandler_init創(chuàng)建一個(gè)全局的iohandler_ctx事件源,這個(gè)事件源的作用是監(jiān)聽(tīng)QEMU中的各類事件。最終qemu_signal_init會(huì)在iohandlers_ctx的aio_handlers上掛一個(gè)AioHandler節(jié)點(diǎn),其fd為這里的signalfd,其io_read函數(shù)為這里的sigfd_handler。qemu_init_main_loop函數(shù)接著會(huì)調(diào)用aio_context_new創(chuàng)建一個(gè)全局的qemu_aio_context事件源,這個(gè)事件源主要用于處理BH和塊設(shè)備層的同步使用。最后,該函數(shù)調(diào)用aio_get_g_source和iohandler_get_g_source分別獲取qemu_aio_context和iohandler_ctx的GSource,以GSource為參數(shù)調(diào)用g_source_attach兩個(gè)AioContext加入到glib的主循環(huán)中去。將信號(hào)對(duì)應(yīng)的fd加入事件源以及將事件源加入到glib的主循環(huán)之后,QEMU就會(huì)按照2.1.2節(jié)所述,在一個(gè)while循環(huán)中進(jìn)行事件監(jiān)聽(tīng)。當(dāng)使用kill向QEMU進(jìn)程發(fā)送SIGALARM信號(hào)時(shí),signalfd就會(huì)有可讀信號(hào),從而導(dǎo)致glib的主循環(huán)返回調(diào)用g_main_context_dispatch進(jìn)行事件分發(fā),這會(huì)調(diào)用到aio_ctx_dispatch,最終會(huì)調(diào)用到qemu_signal_init注冊(cè)的可讀處理函數(shù)sigfd_handler。2.1.5QEMU主循環(huán)監(jiān)聽(tīng)的fd解析2.1.2節(jié)中介紹了QEMU的事件循環(huán)機(jī)制,并且在隨后的幾節(jié)中介紹了與事件循環(huán)機(jī)制相關(guān)的源碼,本節(jié)將實(shí)際分析QEMU主事件循環(huán)監(jiān)聽(tīng)的fd來(lái)源。首先以如下命令啟動(dòng)虛擬機(jī)。為了方便稍后的敘述,這里再把glib_pollfds_fill的代碼和行號(hào)列在圖2-8中:圖2-8glib_pollfds_fill函數(shù)源碼使用gdb在第200行下斷點(diǎn)。輸入“r”讓QEMU進(jìn)程運(yùn)行起來(lái)。gpollfds是一個(gè)數(shù)組,存著所有需要監(jiān)聽(tīng)的fd,其成員類型為pollfd,成員都存放在gpollfds.data中,所以這里可以判斷到底監(jiān)聽(tīng)了哪些fd。圖2-9顯示了所有監(jiān)聽(tīng)的fd,總共有6個(gè)fd,分別是4、6、8、9、e、f。圖2-9QEMU監(jiān)聽(tīng)的fd從圖2-9可以看出來(lái),第一個(gè)fd4是在monitor_init_globals初始化調(diào)用iohandler_init并創(chuàng)建iohander_ctx時(shí)調(diào)用的,其本身對(duì)應(yīng)iohander_ctx中的事件通知對(duì)象的fd。gdb繼續(xù)輸入“c”讓程序運(yùn)行起來(lái),在隨后的g_source_add_poll斷點(diǎn)中可以看到6、8、e、f這幾個(gè)fd的來(lái)源。6是調(diào)用qemu_signal_init創(chuàng)建signalfd對(duì)應(yīng)的fd,8是qemu_aio_context對(duì)應(yīng)的fd,e和f是vnc創(chuàng)建的fd。但是沒(méi)有fd9的信息。找到QEMU對(duì)應(yīng)的進(jìn)程id,查看/proc/目錄下該QEMU進(jìn)程對(duì)應(yīng)fd情況,如圖2-10所示。這里可以看到fd9是一個(gè)eventfd,其雖然在glib事件循環(huán)監(jiān)聽(tīng)中,但是其并沒(méi)有通過(guò)g_source_add_poll加入。圖2-10QEMU進(jìn)程fd分布在eventfd函數(shù)下斷點(diǎn),每次停下來(lái)之后在gdb中輸入finish命令完成當(dāng)前函數(shù)的執(zhí)行,然后查看rax寄存器的值,當(dāng)其是9的時(shí)候查看堆棧,結(jié)果如圖2-11所示。從中可以看出,fd9這個(gè)eventfd是由glib庫(kù)自己創(chuàng)建使用的。這樣,glib監(jiān)聽(tīng)的6個(gè)fd就搞清楚了。當(dāng)然,如果給QEMU提供不同的參數(shù),其監(jiān)聽(tīng)的fd也會(huì)隨著變化。圖2-11fd9注冊(cè)過(guò)程2.2QEMU線程模型2.2.1QEMU線程模型簡(jiǎn)介QEMU-KVM架構(gòu)中,一個(gè)QEMU進(jìn)程代表一個(gè)虛擬機(jī)。QEMU會(huì)有若干個(gè)線程,其中對(duì)于每個(gè)CPU會(huì)創(chuàng)建一個(gè)線程,還有其他的線程,如VNC線程、I/O線程、熱遷移線程,QEMU線程模型如圖2-12所示。圖2-12QEMU線程模型傳統(tǒng)上,QEMU主事件循環(huán)所在的線程由于會(huì)不斷監(jiān)聽(tīng)各種I/O事件,所以被稱為I/O線程?,F(xiàn)在的I/O線程通常是指塊設(shè)備層面的單獨(dú)用來(lái)處理I/O事件的線程。每一個(gè)CPU都會(huì)有一個(gè)線程,通常叫作VCPU線程,其主要的執(zhí)行函數(shù)是kvm_cpu_exec,比如圖2-12中有3個(gè)VCPU線程。QEMU為了完成其他功能還會(huì)有一些輔助線程,如熱遷移時(shí)候的migration線程、支持遠(yuǎn)程連接的VNC和SPICE線程等。線程模型通常使用QEMU大鎖進(jìn)行同步,獲取鎖的函數(shù)為qemu_mutex_lock_iothread,解鎖函數(shù)為qemu_mutex_unlock_iothread。實(shí)際上隨著演變,現(xiàn)在這兩個(gè)函數(shù)已經(jīng)變成宏了。很多場(chǎng)合都需要BQL,比如os_host_main_loop_wait在有fd返回事件時(shí),在進(jìn)行事件處理之前需要調(diào)用qemu_mutex_lock_iothread獲取BQL;VCPU線程在退出到QEMU進(jìn)行一些處理的時(shí)候也會(huì)獲取BQL。下面的代碼是main函數(shù)主循環(huán)中獲取BQL的過(guò)程。2.2.2QEMU線程介紹1.VCPU線程QEMU虛擬機(jī)的VCPU對(duì)應(yīng)于宿主機(jī)上的一個(gè)線程,通常叫作VCPU線程。在x86_cpu_realizefn函數(shù)中進(jìn)行CPU具現(xiàn)(CPU具現(xiàn)的概念會(huì)在2.4節(jié)中介紹)的時(shí)候會(huì)調(diào)用qemu_init_vcpu函數(shù)來(lái)創(chuàng)建VCPU線程。qemu_init_vcpu根據(jù)加速器的不同,會(huì)調(diào)用不同的函數(shù)來(lái)進(jìn)行VCPU的創(chuàng)建,對(duì)于KVM加速器來(lái)說(shuō),這個(gè)函數(shù)是qemu_kvm_start_vcpu,該函數(shù)的代碼如下。qemu_thread_create調(diào)用了pthread_create來(lái)創(chuàng)建VCPU線程。VCPU線程用來(lái)執(zhí)行虛擬機(jī)的代碼,其線程函數(shù)是qemu_kvm_cpu_thread_fn。2.VNC線程在main函數(shù)中,會(huì)調(diào)用vnc_init_func對(duì)VNC模塊進(jìn)行初始化,經(jīng)過(guò)vnc_display_init->vnc_start_worker_thread的調(diào)用最終創(chuàng)建VNC線程,VNC線程用來(lái)與VNC客戶端進(jìn)行交互。3.I/O線程設(shè)備模擬過(guò)程中可能會(huì)占用QEMU的大鎖,所以如果是用磁盤類設(shè)備進(jìn)行讀寫,會(huì)導(dǎo)致占用該鎖較長(zhǎng)時(shí)間。為了提高性能,會(huì)將這類操作單獨(dú)放到一個(gè)線程中去。QEMU抽象出了一個(gè)新的類型TYPE_IOTHREAD,可以用來(lái)進(jìn)行I/O線程的創(chuàng)建。比如virtio塊設(shè)備在其對(duì)象實(shí)例化函數(shù)中添加了一個(gè)link屬性,其對(duì)應(yīng)的連接對(duì)象為一個(gè)TYPE_IOTHREAD。當(dāng)進(jìn)行數(shù)據(jù)面的讀寫時(shí),就可以使用這個(gè)iothread進(jìn)行。當(dāng)然,QEMU還會(huì)有其他線程,比如說(shuō)熱遷移線程以及一些設(shè)備模擬自己創(chuàng)建的線程,這里就不一一介紹了。如同Linux內(nèi)核中的大鎖,BQL會(huì)對(duì)QEMU虛擬機(jī)的性能造成很大影響。早期的QEMU代碼在握有BQL時(shí)做的事情很多,QEMU多線程的主要?jiǎng)恿κ菧p少Q(mào)EMU主線程的運(yùn)行時(shí)間,QEMU在進(jìn)行一些設(shè)備模擬的時(shí)候,VCPU線程會(huì)退出到QEMU,搶占QEMU大鎖,如果這個(gè)時(shí)候有其他線程占據(jù)大鎖,再做長(zhǎng)時(shí)間的工作就會(huì)導(dǎo)致VCPU被掛起比較長(zhǎng)的時(shí)間,所以將一些沒(méi)有必要占據(jù)QEMU大鎖的任務(wù)放到單獨(dú)線程進(jìn)行處理就能夠增加VCPU的運(yùn)行時(shí)間,這也是QEMU社區(qū)在多線程方向的努力方向,即盡量將任務(wù)從QEMU大鎖中拿出來(lái)。2.3QEMU參數(shù)解析本節(jié)對(duì)QEMU參數(shù)解析進(jìn)行簡(jiǎn)要介紹,幫助讀者將QEMU命令行參數(shù)與其代碼中的實(shí)現(xiàn)聯(lián)系起來(lái),方便閱讀代碼,但是由于參數(shù)解析并非本書(shū)重點(diǎn),因此不會(huì)對(duì)具體的細(xì)節(jié)進(jìn)行深入講解。本節(jié)使用的QEMU命令如下。QEMU使用QEMUOption來(lái)表示QEMU程序的參數(shù)選項(xiàng),其定義如下。其中,name表示參數(shù)選項(xiàng)的名字;flags表示選項(xiàng)中一些參數(shù)選項(xiàng)的屬性,比如是否有子參數(shù);arch_mask表示參數(shù)支持的體系結(jié)構(gòu)。vl.c在全局范圍定義了一個(gè)qemu_options,存儲(chǔ)了所有的可用選項(xiàng),main函數(shù)中會(huì)調(diào)lookup_opt來(lái)解析QEMU命令行參數(shù),不在qemu_options中的參數(shù)是不合法的。qemu_options的生成使用QEMU_OPTIONS_GENERATE_OPTIONS編譯控制選項(xiàng)以及一個(gè)文件qemu-options-wrapper.h填充。在qemu-options-wrapper.h中,根據(jù)是否定義QEMU_OPTIONS_GENERATE_ENUM,QEMU_OPTIONS_GENERATE_HELP,QEMU_OPTIONS_GENERATE_OPTIONS以及qemu-options.def文件可以生成不同的內(nèi)容。qemu-options.def是在Makefile中利用scripts/hxtool腳本根據(jù)qemu-options.hx文件生成的。在這里只需要理解qemu_options中包括了所有可能的參數(shù)選項(xiàng),如上面的-enable-kvm、-smp、-realtime、-device等即可。圖2-13顯示了gdb中部分qemu_options的值。圖2-13QEMU參數(shù)選項(xiàng)QEMUOption提供了參數(shù)的基本信息情況。實(shí)際參數(shù)的保存是由3個(gè)數(shù)據(jù)結(jié)構(gòu)完成的。QEMU將所有參數(shù)分成了幾個(gè)大選項(xiàng),如-eanble-kvm和-kernel都屬于machine相關(guān)的,每一個(gè)大選項(xiàng)使用結(jié)構(gòu)體QemuOptsList表示,QEMU在qemu-config.c中定義了vm_config_groups。這表示可以支持48個(gè)大選項(xiàng)。在main函數(shù)中用qemu_add_opts將各個(gè)QemuOptsList添加到vm_config_groups中。每個(gè)QemuOptsList存儲(chǔ)了大選項(xiàng)支持的所有小選項(xiàng),如-realtime大選項(xiàng)定義如下。-realtime只支持一個(gè)值為bool的子選項(xiàng),即只能有-realtimemlock=on/off。但是像-device這種選項(xiàng)就沒(méi)有這么死板了,-device并沒(méi)有規(guī)定必需的選項(xiàng),因?yàn)樵O(shè)備有無(wú)數(shù)多種,不可能全部進(jìn)行規(guī)定,解析就是按照“,”或者“=”來(lái)進(jìn)行的。每個(gè)子選項(xiàng)由一個(gè)QemuOpt結(jié)構(gòu)表示,定義如下。name表示子選項(xiàng)的字符串表示;str表示對(duì)應(yīng)的值。QemuOptsList并不和QemuOpt直接聯(lián)系,中間還需要有一層QemuOpts,這是因?yàn)镼EMU命令行可以指定創(chuàng)建兩個(gè)相同的設(shè)備,此時(shí)這類設(shè)備都在QemuOptsList的鏈表上,這是兩個(gè)獨(dú)立QemuOpts,每個(gè)QemuOpts有自己的QemuOpt鏈表。QemuOpts結(jié)構(gòu)如下。head是QemuOpts下的QemuOpt鏈表頭;next用來(lái)連接相同QemuOptsList下同一種QemuOpts。QemuOptsList、QemuOpts與QemuOpt三者的關(guān)系如圖2-14所示。圖2-14QemuOptsList、QemuOpts與QemuOpt三者關(guān)系這里以-device參數(shù)項(xiàng)為例簡(jiǎn)單分析參數(shù)的處理過(guò)程,vl.c中的main函數(shù)中有一個(gè)很長(zhǎng)的for循環(huán)來(lái)解析參數(shù),當(dāng)解析到"-device"時(shí),下面是對(duì)QEMU_OPTION_device的分支處理。qemu_find_opts函數(shù)從全局變量vm_config_groups中找到剛才插入的deviceQemuOptsList并返回。qemu_opts_parse_noisily函數(shù)只是簡(jiǎn)單調(diào)用了opts_parse,后者解析出一個(gè)QemuOpts,每一個(gè)大類的參數(shù)(如-deviceedu)都會(huì)在相應(yīng)的QemuOptsList下面構(gòu)造處理一個(gè)Opts。opts_parse函數(shù)調(diào)用的最重要的兩個(gè)函數(shù)是qemu_opts_create和opts_do_parse,前者用來(lái)創(chuàng)建opts并且將它插入到QemuOptsList上,后者則開(kāi)始解析出一個(gè)一個(gè)的QemuOpt。opts_do_parse的作用就是解析參數(shù)的值,如本節(jié)開(kāi)始的命令行參數(shù)ivshmem,shm=ivshmem,size=1。QEMU的參數(shù)可以有多種情況,比如foo,bar中foo表示開(kāi)啟一個(gè)flag,也有可能類似于foo=bar,對(duì)此opts_do_parse需要處理各種情況,并對(duì)每一個(gè)值生成一個(gè)QemuOpt。關(guān)鍵代碼如下。該函數(shù)首先根據(jù)各種情況(foo,bar或者foo=bar,more)解析出option以及value,然后調(diào)用opt_set,在該函數(shù)中會(huì)分配一個(gè)QemuOpt結(jié)構(gòu),并且進(jìn)行初始化。例子中的ivshmem,shm=ivshmem,size=1會(huì)解析出3個(gè)QemuOpt,name=str分別是driver=ivshmem、shm=ivshmem、size=1。所以對(duì)于兩個(gè)device的參數(shù)解析會(huì)形成圖2-15所示的鏈表。圖2-15QEMU參數(shù)解析結(jié)果2.4QOM介紹QOM的全稱是QEMUObjectModel,顧名思義,這是QEMU中對(duì)象的一個(gè)抽象層。一般來(lái)講,對(duì)象是C++這類面向?qū)ο缶幊陶Z(yǔ)言中的概念。面向?qū)ο蟮乃枷氚ɡ^承、封裝與多態(tài),這些思想在大型項(xiàng)目中能夠更好地對(duì)程序進(jìn)行組織與設(shè)計(jì)。Linux內(nèi)核與QEMU雖然都是C語(yǔ)言的項(xiàng)目,但是都充滿了面向?qū)ο蟮乃枷?,QEMU中體現(xiàn)這一思想的就是QOM。QEMU的代碼中充滿了對(duì)象,特別是設(shè)備模擬,如網(wǎng)卡、串口、顯卡等都是通過(guò)對(duì)象來(lái)抽象的。QOM用C語(yǔ)言基本上實(shí)現(xiàn)了繼承、封裝、多態(tài)特點(diǎn)。如網(wǎng)卡是一個(gè)類,它的父類是一個(gè)PCI設(shè)備類,這個(gè)PCI設(shè)備類的父類是設(shè)備類,此即繼承。QEMU通過(guò)QOM可以對(duì)QEMU中的各種資源進(jìn)行抽象、管理(如設(shè)備模擬中的設(shè)備創(chuàng)建、配置、銷毀)。QOM還用于各種后端組件(如MemoryRegion,Machine等)的抽象,毫不夸張地說(shuō),QOM遍布于QEMU代碼。這一節(jié)會(huì)對(duì)QOM進(jìn)行詳細(xì)介紹,以幫助讀者理解QOM,進(jìn)而更加方便地閱讀QEMU代碼。要理解QOM,首先需要理解類型和對(duì)象的區(qū)別。類型表示種類,對(duì)象表示該種類中一個(gè)具體的對(duì)象。比如QEMU命令行中指定"-deviceedu,id=edu1,-deviceedu,id=edu2",edu本身是一個(gè)種類,創(chuàng)建了edu1和edu2兩個(gè)對(duì)象。QOM整個(gè)運(yùn)作包括3個(gè)部分,即類型的注冊(cè)、類型的初始化以及對(duì)象的初始化,3個(gè)部分涉及的函數(shù)如圖2-16所示。圖2-16QOM對(duì)象機(jī)制組成部分本章將對(duì)QOM涉及的各個(gè)方面進(jìn)行深入細(xì)致的分析。2.4.1類型的注冊(cè)在面向?qū)ο笏枷胫?,說(shuō)到對(duì)象時(shí)都會(huì)提到它所屬的類,QEMU也需要實(shí)現(xiàn)一個(gè)類型系統(tǒng)。以hw/misc/edu.c文件為例,這本身不是一個(gè)實(shí)際的設(shè)備,而是教學(xué)用的設(shè)備,它的結(jié)構(gòu)簡(jiǎn)單,比較清楚地展示了QEMU中的模擬設(shè)備。類型的注冊(cè)是通過(guò)type_init完成的。在include/qemu/module.h中可以看到,type_init是一個(gè)宏,并且除了type_init還有其他幾個(gè)init宏,比如block_init、opts_init、trace_init等,每個(gè)宏都表示一類module,均通過(guò)module_init按照不同的參數(shù)構(gòu)造出來(lái)。按照是否定義BUILD_DSO宏,module_init有不同的定義,這里假設(shè)不定義該宏,則module_init的定義如下。可以看到各個(gè)QOM類型最終通過(guò)函數(shù)register_module_init注冊(cè)到了系統(tǒng),其中function是每個(gè)類型都需要實(shí)現(xiàn)的初始化函數(shù),type表示是MODULE_INIT_QOM。這里的constructor是編譯器屬性,編譯器會(huì)把帶有這個(gè)屬性的函數(shù)do_qemu_init_##function放到特殊的段中,帶有這個(gè)屬性的函數(shù)會(huì)早于main函數(shù)執(zhí)行,也就是說(shuō)所有的QOM類型注冊(cè)在main執(zhí)行之前就已經(jīng)執(zhí)行了。register_module_init及相關(guān)函數(shù)代碼如下。register_module_init函數(shù)以類型的初始化函數(shù)以及所屬類型(對(duì)QOM類型來(lái)說(shuō)是MODULE_INIT_QOM)構(gòu)建出一個(gè)ModuleEntry,然后插入到對(duì)應(yīng)module所屬的鏈表中,所有module的鏈表存放在一個(gè)init_type_list數(shù)組中。圖2-17簡(jiǎn)單表示了init_type_list與各個(gè)module以及ModuleEntry之間的關(guān)系。圖2-17init_type_list結(jié)構(gòu)綜上可知,QEMU使用的各個(gè)類型在main函數(shù)執(zhí)行之前就統(tǒng)一注冊(cè)到了init_type_list[MODULE_INIT_QOM]這個(gè)鏈表中。進(jìn)入main函數(shù)后不久就以MODULE_INI
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 北京理工大學(xué)《植物生物學(xué)》2024 - 2025 學(xué)年第一學(xué)期期末試卷
- 軟件項(xiàng)目質(zhì)量管理
- 心理咨詢和輔導(dǎo)
- 2026年劇本殺運(yùn)營(yíng)公司市場(chǎng)費(fèi)用預(yù)算管理制度
- 2025年智能垃圾桶清潔十年技術(shù)報(bào)告
- 2026年文化娛樂(lè)產(chǎn)業(yè)虛擬現(xiàn)實(shí)報(bào)告
- 2026年及未來(lái)5年中國(guó)車廂底板市場(chǎng)運(yùn)行態(tài)勢(shì)及行業(yè)發(fā)展前景預(yù)測(cè)報(bào)告
- 小學(xué)道德與法治教學(xué)中生命教育的實(shí)施路徑課題報(bào)告教學(xué)研究課題報(bào)告
- 企業(yè)盤點(diǎn)和對(duì)賬制度
- 藝術(shù)研究院試題及答案
- 安全生產(chǎn)責(zé)任保險(xiǎn)培訓(xùn)課件
- 機(jī)械工程的奧秘之旅-揭秘機(jī)械工程的魅力與價(jià)值
- 《益生菌與藥食同源植物成分協(xié)同作用評(píng)價(jià)》-編制說(shuō)明 征求意見(jiàn)稿
- 送貨單回簽管理辦法
- 魯科版高中化學(xué)必修第一冊(cè)全冊(cè)教案
- 原發(fā)性高血壓患者糖代謝異常:現(xiàn)狀、關(guān)聯(lián)與防治探索
- 2025年存算一體芯片能效比:近內(nèi)存計(jì)算架構(gòu)突破與邊緣AI設(shè)備部署成本
- 國(guó)有企業(yè)服務(wù)采購(gòu)操作規(guī)范TCFLP 0054-2022
- 2025年獸醫(yī)公共衛(wèi)生學(xué)考試試題(附答案)
- 熱電材料研究進(jìn)展匯報(bào)
- 公安部保密管理辦法
評(píng)論
0/150
提交評(píng)論