欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

KVM+QEMU學(xué)習(xí)筆記,搜集整理合集

系統(tǒng) 2133 0
1 QEMU和KVM的關(guān)系:
? ? ?現(xiàn)在所說的虛擬化,一般都是指在CPU硬件支持基礎(chǔ)之上的虛擬化技術(shù)。KVM也同hyper-V、Xen一樣依賴此項技術(shù)。沒有CPU硬件虛擬化的支持,KVM是無法工作的。
? ? ?準(zhǔn)確來說,KVM是Linux的一個模塊。可以用modprobe去加載KVM模塊。加載了模塊后,才能進一步通過其他工具創(chuàng)建虛擬機。但僅有KVM模塊是 遠遠不夠的,因為用戶無法直接控制內(nèi)核模塊去作事情:還必須有一個用戶空間的工具才行。這個用戶空間的工具,開發(fā)者選擇了已經(jīng)成型的開源虛擬化軟件 QEMU。說起來QEMU也是一個虛擬化軟件。它的特點是可虛擬不同的CPU。比如說在x86的CPU上可虛擬一個Power的CPU,并可利用它編譯出 可運行在Power上的程序。KVM使用了QEMU的一部分,并稍加改造,就成了可控制KVM的用戶空間工具了。所以你會看到,官方提供的KVM下載有兩 大部分三個文件,分別是KVM模塊、QEMU工具以及二者的合集。也就是說,你可以只升級KVM模塊,也可以只升級QEMU工具。
?
2 QEMU基本介紹
? ? ?Qemu是一個完整的可以單獨運行的軟件,它可以用來模擬機器,非常靈活和可移植。它主要通過一個特殊的'重編譯器'將為特定處理器編寫二進制代碼轉(zhuǎn)換為另一種。(也就是,在PPCmac上面運行MIPS代碼,或者在X86 PC上運行ARM代碼)
?
3 KVM基本介紹
? ? ?KVM是一個基于Linux內(nèi)核的虛擬機,它屬于完全虛擬化范疇,從Linux-2.6.20開始被包含在Linux內(nèi)核中。KVM基于x86硬件虛擬化技術(shù),它的運行要求Intel VT-x或AMD SVM的支持。
? ? ?一般認為,虛擬機監(jiān)控的實現(xiàn)模型有兩類:監(jiān)控模型(Hypervisor)和宿主機模型(Host-based)。由于監(jiān)控模型需要進行處理器調(diào)度,還需要實現(xiàn)各種驅(qū)動程序,以支撐運行其上的虛擬機,因此實現(xiàn)難度上一般要大于宿主機模型。KVM的實現(xiàn)采用宿主機模型(Host-based),由于KVM是集成在Linux內(nèi)核中的,因此可以自然地使用Linux內(nèi)核提供的內(nèi)存管理、多處理器支持等功能,易于實現(xiàn),而且還可以隨著Linux內(nèi)核的發(fā)展而發(fā)展。另外,目前KVM的所有I/O虛擬化工作是借助Qemu完成的,也顯著地降低了實現(xiàn)的工作量。以上可以說是KVM的優(yōu)勢所在。
?
4 KVM架構(gòu)
kvm基本結(jié)構(gòu)有2個部分構(gòu)成:
* kvm 驅(qū)動:現(xiàn)在已經(jīng)是linux kernel的一個模塊了。其主要負責(zé)虛擬機的創(chuàng)建,虛擬內(nèi)存的分配,VCPU寄存器的讀寫以及VCPU的運行。
* Qemu:用于模擬虛擬機的用戶空間組件,提供I/O設(shè)備模型,訪問外設(shè)的途徑。
?
kvm基本結(jié)構(gòu)如上圖。kvm已經(jīng)是內(nèi)核模塊,被看作是一個標(biāo)準(zhǔn)的linux 字符集設(shè)備(/dev/kvm)。Qemu通過libkvm應(yīng)用程序接口,用fd通過ioctl向設(shè)備驅(qū)動來發(fā)送創(chuàng)建,運行虛擬機命令。設(shè)備驅(qū)動kvm就會來解析命令(kvm_dev_ioctl函數(shù)在kvm_main.c文件中),如下圖:
?
?
?
KVM模塊讓Linux主機成為一個虛擬機監(jiān)視器(VMM,Virtual Machine Monitor),并且在原有的Linux兩種執(zhí)行模式基礎(chǔ)上,新增加了客戶模式,客戶模式擁有自己的內(nèi)核模式和用戶模式。在虛擬機運行時,三種模式的工作各為:
?
客戶模式:執(zhí)行非I/O的客戶代碼,虛擬機運行在這個模式下。
用戶模式:代表用戶執(zhí)行I/O指令,qemu運行在這個模式下。
內(nèi)核模式:實現(xiàn)客戶模式的切換,處理因為I/O或者其他指令引起的從客戶模式退出(VM_EXIT)。kvm 模塊工作在這個模式下。
在kvm的模型中,每一個Gust OS都是作為一個標(biāo)準(zhǔn)的linux進程,都可以使用linux進程管理命令管理。
?
KVM三種類型的文件描述符
? ? ?首先是kvm設(shè)備本身。kvm內(nèi)核模塊本身是作為一個設(shè)備驅(qū)動程序安裝的,驅(qū)動的設(shè)備名稱是”/dev/kvm“。要使用kvm,需要先用open打開”/dev/kvm”設(shè)備,得到一個kvm設(shè)備文件描述符fd,然后利用此fd調(diào)用ioctl就可以向設(shè)備驅(qū)動發(fā)送命令了。kvm驅(qū)動解析此種請求的函數(shù)是kvm_dev_ioctl(kvm_main.c),如KVM_CREATE_VM。
?
? ? ?其次是具體的VM。通過KVM_CREATE_VM創(chuàng)建了一個VM后,用戶程序需要發(fā)送一些命令給VM,如KVM_CREATE_VCPU。這些命令當(dāng)然也是要通過ioctl來發(fā)送,所以VM也需要對應(yīng)一個文件描述符才行。用戶程序中用ioctl發(fā)送KVM_CREATE_VM得到的返回值就是新創(chuàng)建VM對應(yīng)的fd,之后利用此fd發(fā)送命令給此VM。kvm驅(qū)動解析此種請求的函數(shù)是kvm_vm_ioctl。此外,與OS線程類似,每個VM在kvm驅(qū)動中會對應(yīng)一個VM控制塊結(jié)構(gòu)struct kvm,每個對VM的內(nèi)核操作都基本要訪問這個結(jié)構(gòu),那么kvm驅(qū)動是如何找到請求這次命令的VM的控制塊的呢?回答這個問題首先要知道,linux內(nèi)核用一個struct file結(jié)構(gòu)來表示每個打開的文件,其中有一個void *private_data字段,kvm驅(qū)動將VM控制塊的地址保存到對應(yīng)struct file的private_data中。用戶程序發(fā)送ioctl時,指定具體的fd,內(nèi)核根據(jù)fd可以找到相應(yīng)的struct file,傳遞給kvm_vm_ioctl,再通過private_data就可以找到了。
?
? ? ?最后是具體的VCPU。原理基本跟VM情況差不多,kvm驅(qū)動解析此種請求的函數(shù)是kvm_vcpu_ioctl。VCPU控制塊結(jié)構(gòu)為struct kvm_vcpu。
?
5 KVM工作原理
? ? KVM的基本工作原理:用戶模式的Qemu利用接口libkvm通過ioctl系統(tǒng)調(diào)用進入內(nèi)核模式。KVM Driver為虛擬機創(chuàng)建虛擬內(nèi)存和虛擬CPU后執(zhí)行VMLAUCH指令進入客戶模式。裝載Guest OS執(zhí)行。如果Guest OS發(fā)生外部中斷或者影子頁表(shadow page)缺頁之類的事件,暫停Guest OS的執(zhí)行,退出客戶模式進行一些必要的處理。然后重新進入客戶模式,執(zhí)行客戶代碼。如果發(fā)生I/O事件或者信號隊列中有信號到達,就會進入用戶模式處理。KVM采用全虛擬化技術(shù)。客戶機不用修改就可以運行。
graphic
?
?
二、QEMU簡介
1 QEMU的框架
? ? QEMU屬于應(yīng)用層的仿真程序,它支持兩種操作模式:用戶模式仿真和系統(tǒng)模式仿真。用戶模式仿真 允許一個 CPU 構(gòu)建的進程在另一個 CPU 上執(zhí)行(執(zhí)行主機 CPU 指令的動態(tài)翻譯并相應(yīng)地轉(zhuǎn)換 Linux 系統(tǒng)調(diào)用)。系統(tǒng)模式仿真 允許對整個系統(tǒng)進行仿真,包括處理器和配套的外圍設(shè)備。每個模擬方式都有其安裝要求。對于系統(tǒng)模擬,安裝客戶操作系統(tǒng)就像安裝一臺單獨的計算機(下載并使用預(yù)先配置的磁盤鏡像是一個替代方法)。對于用戶空間模擬,不需要安裝整個系統(tǒng),但要考慮安裝使用軟件所需的支持庫。也許還需要配置跨編譯器以生成想要測試的二進制文件。
?
? ? 在本文中,我將主要介紹QEMU的系統(tǒng)模式下的工作方式。
?
? ? QEMU工作在操作系統(tǒng)的用戶態(tài)程序,它有一個如一般應(yīng)用程序的入口點函數(shù)——main函數(shù)(vl.c源碼文件)。這個main函數(shù)也是QEMU的系統(tǒng)模式的入口點函數(shù)。在這個main函數(shù)中,其主要的工作就是初始化一系列的參數(shù)、硬件設(shè)備仿真等以及命令行參數(shù)轉(zhuǎn)換,進而進入一個main_loop的循環(huán)中。
?
? ? 在QEMU中有很重要的一部分就是TCG(Tiny Code Generator),這就是一個Guest OS程序向Host OS程序轉(zhuǎn)換的內(nèi)置翻譯器,主要目的是為了在Host的硬件上面可以運行Guest的代碼。本文將主要集中在整體流程的介紹以及QEMU與KVM之間的交互,因此這部分將不會花過多精力涉及。
?
。由QEMU的開發(fā)者編寫,主要是為了讓開發(fā)者對QEMU有個清晰的認識,但是由于該文章比較古老,因此將根據(jù)現(xiàn)有的設(shè)計再做調(diào)整。
--------------------------------
? ? Guest OS的運行涉及到Guest OS代碼的執(zhí)行、timer的處理、IO處以及對monitor命令的響應(yīng)。
對于這種需要回應(yīng)從多重資源發(fā)來的事件的程序來說,現(xiàn)行有兩種比較流行的架構(gòu):
  1. Parallel architecture(平行架構(gòu))把那些可以同時執(zhí)行的工作分成多個進程或是線程。我叫他線程化的架構(gòu)(threaded architecture)。
  2. Event-driven architecture(事件驅(qū)動架構(gòu))通過執(zhí)行一個主循環(huán)來發(fā)送事件到handler以此對事件做反饋處理。這一方法通常通過使用select(2)或者poll(2)系列的系統(tǒng)調(diào)用等待多個文件描述符的方式來實現(xiàn)。
目前,QEMU使用一種混合架構(gòu),把事件驅(qū)動和線程組合在一起。這種做法之所以有效是因為只在單個線程上執(zhí)行的事件循環(huán)不能有效利用底層多核心的硬件。再則,有時候使用一個專用線程來減少特定工作的負擔(dān)要比把它整合在一個事件驅(qū)動的架構(gòu)中更簡單有效。雖然如此,QEMU的核心還是事件驅(qū)動的,大多數(shù)代碼都是在這樣一個環(huán)境中執(zhí)行的。
?
QEMU的事件驅(qū)動核心
一個事件驅(qū)動的架構(gòu)是以一個派發(fā)事件到處理函數(shù)的循環(huán)為核心的。
?
QEMU的主事件循環(huán)是main_loop_wait()(main-loop.c文件),它主要完成以下工作:
  1. 等待文件描述符變成可讀或可寫。文件描述符是一個關(guān)鍵角色,因為files、sockets、pipes以及其他各種各樣的資源都是文件描述符(file descriptors)。文件描述符的增加方式:qemu_set_fd_handler()。
  2. 處理到期的定時器(timer)。定時器的管理在qemu-timer.c文件中。
  3. 執(zhí)行bottom-halves(BHs),它和定時器類似會立即過期。BHs用來放置回調(diào)函數(shù)的重入和溢出。BHs的添加方式:qemu_bh_schedule()。
當(dāng)一個文件描述符準(zhǔn)備好了、一個定時器過期或者是一個BH被調(diào)度到時,事件循環(huán)就會調(diào)用一個回調(diào)函數(shù)來回應(yīng)這些事件。回調(diào)函數(shù)對于它們的環(huán)境有兩條規(guī)則:
  1. 沒有其他核心同時在執(zhí)行,所以不需要考慮同步問題。對于核心代碼來說,回調(diào)函數(shù)是線性和原子執(zhí)行的。在任意給定的時間里只有一個線程控制執(zhí)行核心代碼。
  2. 不應(yīng)該執(zhí)行可阻斷系統(tǒng)調(diào)用或是長運行計算(long-running computations)。由于事件循環(huán)在繼續(xù)其他事件時會等待當(dāng)前回調(diào)函數(shù)返回,所以如果違反這條規(guī)定會導(dǎo)致guest暫停并且使管理器無響應(yīng)。
第二條規(guī)定有時候很難遵守,在QEMU中會有代碼會被阻塞。事實上,qemu_aio_wait()里面還有嵌套循環(huán),它會等待那些頂層事件循環(huán)正在處理的事件的子集。慶幸的是,這些違反規(guī)則的部分會在未來重新架構(gòu)代碼時被移除。新代碼幾乎沒有合理的理由被阻塞,而解決方法之一就是使用專屬的工作線程來卸下(offload)這些長執(zhí)行或者會被阻塞的代碼。
?
卸下特殊的任務(wù)到工作線程
盡管很多I/O操作可以以一種非阻塞的形式執(zhí)行,但有些系統(tǒng)調(diào)用卻沒有非阻塞的替代方式。再者,長運行的計算單純的霸占著CPU并且很難被分割到回調(diào)函數(shù)中。在這種情況下專屬的工作線程就可以用來小心的將這些任務(wù)移出核心QEMU。
?
posix-aio-compat.c 中有一個工作線程的例子,一個異步的文件I/O實現(xiàn)。當(dāng)核心QEMU放出一個aio請求,這個請求被放到一個隊列總。工作線程從隊列中拿出這個請求,并在核心QEMU中執(zhí)行它。它們可能會有阻塞的動作,但因為它們在它們自己的線程中執(zhí)行所以并不會阻塞剩余的QEMU執(zhí)行。這個實現(xiàn)對于必要的同步以及工作線程和核心QEMU的通信有小心的處理。
?
另一個例子是 ui/vnc-jobs-async.c 中將計算密集型的鏡像解壓縮和解碼移到工作線程中。
?
因為核心QEMU的主要部分不是線程安全的,所以工作線程不能調(diào)用到核心QEMU的代碼。當(dāng)然簡單的使用類似qemu_malloc()的函數(shù)是線程安全的,這些是例外,而不在規(guī)則之內(nèi)。這也引發(fā)了工作線程如何將事件傳回核心QEMU的問題。
?
當(dāng)一個工作線程需要通知核心QEMU時,一個管道或者一個qemu_eventfd()文件描述符將被添加到事件循環(huán)中。工作線程可以向文件描述符中寫入,而當(dāng)文件描述符變成可讀時,事件循環(huán)會調(diào)用回調(diào)函數(shù)。另外,必須使用信號來確保事件循環(huán)可以在任何環(huán)境下執(zhí)行。這種方式在posix-aio-compat.c中被使用,而且在了解guest代碼如何被執(zhí)行之后變的更有意義。
?
執(zhí)行g(shù)uest代碼
目前為止我們已經(jīng)大概的看了一下QEMU中的事件循環(huán)和它的主要規(guī)則。其中執(zhí)行g(shù)uest代碼的能力是特別重要的,少了它,QEMU可以響應(yīng)事件但不會非常有用。
?
這里有兩種方式用來執(zhí)行g(shù)uest代碼:Tiny Code Generator(TCG)和KVM。TCG通過動態(tài)二進制轉(zhuǎn)化(dynamic binary translation)來模擬guest,它也以即時編譯(Just-in-Time compilation)被熟知。而KVM則是利用現(xiàn)有的現(xiàn)代intel和AMD CPU中硬件虛擬化擴展來直接安全的在host CPU上執(zhí)行g(shù)uest代碼。在這篇文章中,真正重要的并不是實際的技術(shù),不管是TCG還是KVM都允許我們跳轉(zhuǎn)到guest代碼中并且執(zhí)行它。
?
跳入guest代碼中會使我們失去對程序執(zhí)行的控制而把控制交給guest。而一個正在執(zhí)行g(shù)uest代碼的線程不能同時處在事件循環(huán)中,因為guest控制著CPU。一般情況下,花在guest代碼中的時間是有限的。因為對于被模擬設(shè)備的寄存器的讀寫和其他異常導(dǎo)致我們離開guest而把控制交還給QEMU。在極端的情況下一個guest可以花費無限制的時間而不放棄控制權(quán),而這會引起QEMU無響應(yīng)。
?
為了解決guest代碼霸占問題,QEMU線程使用信號來跳出guest。一個UNIX信號從當(dāng)前的執(zhí)行流程中抓取控制權(quán)并調(diào)用一個信號處理函數(shù)。這使得QEMU得以采取措施來離開guest代碼并返回它的主循環(huán),因而事件循環(huán)才有機會處理待解決的事件。
?
上述的結(jié)果是新事件可能第一時間被發(fā)覺如果QEMU當(dāng)前正在guest代碼中。事實上QEMU大多數(shù)時間規(guī)避處理事件,但因而產(chǎn)生的額外的延遲也成為的效能問題。因此,到核心QEMU的I/O結(jié)束和從工作線程來的通知使用信號來確保事件循環(huán)會被立即處理。
?
你可能會疑惑說到底事件循環(huán)和有多核心的SMP guest之間的架構(gòu)圖會是什么樣子的。而現(xiàn)在,線程模型和guest代碼都已經(jīng)提到了,現(xiàn)在我們來討論整體架構(gòu)。
?
IOTHREAD和NON-IOTHREAD線程架構(gòu)
傳統(tǒng)的架構(gòu)是單個QEMU線程來執(zhí)行g(shù)uest代碼和事件循環(huán)。這個模型就是所謂的non-iothread或者說!CONFIG_IOTHREAD,它是QEMU默認使用./configure && make的設(shè)置。QEMU線程執(zhí)行g(shù)uest代碼直到一個異常或者信號出現(xiàn)才回到控制器。然后它在select(2)不被阻塞的情況執(zhí)行一次事件循環(huán)的一次迭代。然后它又回到guest代碼中并重復(fù)上述過程直到QEMU被關(guān)閉。
?
如果guest使用,例如-smp 2,以啟動一個多vcpu啟動,也不會有多的QEMU線程被創(chuàng)建。取而代之的是在單個QEMU線程中多重執(zhí)行兩個vcpu和事件循環(huán)。因而non-iothread不能很好利用多核心的host硬件,而使得對SMP guest的模擬性能很差。
?
需要注意的是,雖然只有一個QEMU線程,但可能會有0或多個工作線程。這些線程可能是臨時的也可能是永久的。記住這些工作線程只執(zhí)行特殊的任務(wù)而不執(zhí)行g(shù)uest代碼也不處理事件。我之說以要強調(diào)這個是因為當(dāng)監(jiān)視host時很容易被工作線程迷惑而把他們當(dāng)做vcpu線程來中斷。記住,non-iothread只有一個QEMU線程。
?
一種更新的架構(gòu)是每個vcpu一個QEMU線程外加一個專用的事件循環(huán)線程。這個模型被定義為iothread或者CONFIG_IOTHREAD,它可以通過./configure --enable-io-thread在創(chuàng)建時開啟。每個vcpu線程可以平行的執(zhí)行g(shù)uest代碼,以此提供真正的SMP支持。而iothread執(zhí)行事件循環(huán)。核心QEMU代碼不能同時執(zhí)行的規(guī)則通過一個全局互斥來維護,并通過該互斥鎖同步vcpu和iothread間核心QEMU代碼。大多數(shù)時候vcpu線程在執(zhí)行g(shù)uest代碼而不需要獲取全局互斥鎖。大多數(shù)時間iothread被阻塞在select(2)因而也不需要獲取全局互斥鎖。
?
注意,TCG不是線程安全的,所以即使在在iothread模式下,它還是在一個QEMU線程中執(zhí)行多個vcpu。只有KVM可以真正利用每個vcpu一個線程的優(yōu)勢。
?
2 QEMU的線程
? ? HOST將qemu當(dāng)做一個普通的進程和其他進程統(tǒng)一調(diào)度,可以使用資源對qemu進行資源預(yù)留隔離(cpuset)和優(yōu)先級提升(chrt)。qemu進程包含多個線程,分配給GUEST的每個vcpu都對應(yīng)一個vcpu線程,另外qemu還有一個線程循環(huán)執(zhí)行select專門處理I/O事件。
QEMU的主要線程:
  • 主線程(main_loop),一個
  • vCPU線程,一個或者多個
  • I/O線程(aio),一個或者多個
  • worker thread(VNC/SPICE),一個
qemu里有個主線程處于無限循環(huán),會做如下操作
  • IO線程里有個select函數(shù),它阻塞在一個文件描述符(fd)集合上,等待其就緒。fd可以通過qemu_set_fd_handler()
  • 運行到期的定時器,定時器通過qemu_mod_timer添加
  • 運行BH(bottom-halves),BH通過qemu_bh_schedule添加
當(dāng)文件描述符就緒,定期器到期或者BH被調(diào)度,相應(yīng)的callback會被調(diào)用
?
qemu中還有一些worker threads。一些占用CPU較多的工作會明顯增大主IO線程的IO處理延遲,這些工作可以放在專用的線程里,例如posix-aio-compat.c中實現(xiàn)了異步文件I/O,當(dāng)有aio請求產(chǎn)生,該請求被置于隊列,工作線程可以在qemu主線程之外處理這些請求。VNC就是這樣一個例子,它用了一個專門的worker thread(ui/vnc-jobs.c)進行計算密集型的圖像壓縮和編碼工作。
?
3 QEMU的初始化流程
? ? 待續(xù)
?
4 QEMU虛擬網(wǎng)卡設(shè)備的創(chuàng)建流程
虛擬網(wǎng)卡類型為virtio-net-pci
virtio網(wǎng)卡設(shè)備對應(yīng)的命令行參數(shù)為?
-device virtio-net-pci,netdev=hostnet0,id=net0,mac=00:16:36:01:c4:86,bus=pci.0,addr=0x3
?
1) . 在parse命令行的時候,qemu把所有的-device選項parse后保存到qemu_device_opts中
2) . 調(diào)用module_call_init(MODULE_INIT_DEVICE); 往系統(tǒng)中添加所有支持的設(shè)備類型
? ?virtio-net-pci的設(shè)備類型信息如下(virtio-pci.c):
static PCIDeviceInfo virtio_info[] = {
? ? {
? ? ? ? .qdev.name ?= "virtio-net-pci",
? ? ? ? .qdev.size ?= sizeof(VirtIOPCIProxy),
? ? ? ? .init ? ? ? = virtio_net_init_pci,
? ? ? ? .exit ? ? ? = virtio_net_exit_pci,
? ? ? ? .romfile ? ?= "pxe-virtio.bin",
? ? ? ? .qdev.props = (Property[]) {
? ? ? ? ? ? DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags,
? ? ? ? ? ? ? ? ? ? ? ? ? ? VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, false),
? ? ? ? ? ? DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 3),
? ? ? ? ? ? DEFINE_VIRTIO_NET_FEATURES(VirtIOPCIProxy, host_features),
? ? ? ? ? ? DEFINE_NIC_PROPERTIES(VirtIOPCIProxy, nic),
? ? ? ? ? ? DEFINE_PROP_UINT32("x-txtimer", VirtIOPCIProxy,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?net.txtimer, TX_TIMER_INTERVAL),
? ? ? ? ? ? DEFINE_PROP_INT32("x-txburst", VirtIOPCIProxy,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? net.txburst, TX_BURST),
? ? ? ? ? ? DEFINE_PROP_STRING("tx", VirtIOPCIProxy, net.tx),
? ? ? ? ? ? DEFINE_PROP_END_OF_LIST(),
? ? ? ? },
? ? ? ? .qdev.reset = virtio_pci_reset,
? ? }
? ?};
?
3) . 調(diào)用qemu_opts_foreach(&qemu_device_opts, device_init_func, NULL, 1) 創(chuàng)建命令行上指定的設(shè)備
4) . device_init_func調(diào)用qdev_device_add(opts)
5) . qdev_device_add函數(shù)的流程如下:
? ?a) 調(diào)用qemu_opt_get(opts, "driver")獲取driver選項,這里應(yīng)該是virtio-net-pci
? ?b) 調(diào)用qdev_find_info(NULL, driver)來獲取注冊的DeviceInfo,這里應(yīng)該是上面virtio_info里面關(guān)于
? ? ? virtio-net-pci的結(jié)構(gòu)
? ?c) 調(diào)用qemu_opt_get(opts, "bus")獲取bus路徑,以/分隔各組件。這里是pci.0
? ?d) 如果bus路徑不為空,則調(diào)用qbus_find(path)來獲取bus實例(BusState結(jié)構(gòu))
? ? ? qbus_find函數(shù)的流程如下:
? ? ? d.1) 先找到路徑中的根bus,如果路徑以/開頭,則根bus為main_system_bus,否則,使用
? ? ? ? ? ?qbus_find_recursive(main_system_bus, elem, NULL)來查找。這里的elem = "pci.0"
? ? ? d.2) 如果整個路徑已經(jīng)完成,則返回當(dāng)前bus
? ? ? d.2) parse出下一個組件,調(diào)用qbus_find_dev查找對應(yīng)的設(shè)備
? ? ? d.3) parse出下一個組件,調(diào)用qbus_find_bus查找屬于上層設(shè)備的子bus
? ? ? d.4) 返回步驟2
? ? ? 由于這里的值是pci.0,因此其實只進行了一次qbus_find_recursive調(diào)用
? ?e) 如果bus路徑為空,則調(diào)用qbus_find_recursive(main_system_bus, NULL, info->bus_info)來獲取bus
? ? ? 實例。這里的info是driver("virtio-net-pci")所對應(yīng)的DeviceInfo,即最上面的結(jié)構(gòu)
? ? ? virtio-pci的初始化步驟是virtio_pci_register_devices -> pci_qdev_register_many ->?
? ? ? pci_qdev_register,在該函數(shù)中,會設(shè)置info->bus_info = &pci_bus_info,這樣就把PCIDeviceInfo
? ? ? 和pci的BusInfo聯(lián)系起來了
? ? ? qbus_find_recursive是一個遞歸函數(shù),其流程如下:
? ? ? e.1) 如果當(dāng)前bus的名稱和指定的名稱相同(指定名稱不為空的情況下),并且當(dāng)前bus指向的bus info和
? ? ? ? ? ?指定的bus info相同(指定bus info不為空的情況下),則返回當(dāng)前bus
? ? ? e.2) 這里是一個兩重循環(huán):
? ? ? ? ? ?對于當(dāng)前bus所有附屬的設(shè)備(bus->children為鏈表頭)
? ? ? ? ? ? ? ?對于當(dāng)前設(shè)備所有的附屬bus(dev->child_bus為鏈表頭)
? ? ? ? ? ? ? ? ? ?調(diào)用qbus_find_recursive函數(shù)
? ?f) 調(diào)用qdev_create_from_info(bus, info)來創(chuàng)建設(shè)備,返回的是DeviceState結(jié)構(gòu)。這里其實返回的是
? ? ? 一個VirtIOPCIProxy實例,因為create的時候是根據(jù)qdev.size來分配內(nèi)存大小的。
? ?g) 如果qemu_opts_id(opts)不為空,則設(shè)置qdev->id
? ?h) 調(diào)用qemu_opt_foreach(opts, set_property, qdev, 1)來設(shè)置設(shè)備的各種屬性
? ?i) 調(diào)用qdev_init來初始化設(shè)備。
? ?j) qdev_init會調(diào)用dev->info->init函數(shù)。這里實際調(diào)用的函數(shù)是virtio_net_init_pci
?
?
在這里也大致描述一下bus pci.0是如何生成的
1). 在main函數(shù)里面很前面的地方會調(diào)用module_call_init(MODULE_INIT_MACHINE);
2). module_call_init會調(diào)用所有已注冊QEMUMachine的init函數(shù)。該版本的qemu是注冊了
? ?pc_machine_rhel610, pc_machine_rhel600, pc_machine_rhel550, pc_machine_rhel544,
? ?pc_machine_rhel540這幾個 (pc.c)
3). 這些Machine的init函數(shù)(pc_init_rhel600, ...)都會調(diào)用到pc_init_pci函數(shù)
4). pc_init_pci會調(diào)用pc_init1,pc_init1在pci_enabled情況下會調(diào)用i440fx_init (piix_pci.c)
5). i440fx_init首先會調(diào)用qdev_create(NULL, "i440FX-pcihost")創(chuàng)建一個host device
6). 然后調(diào)用pci_bus_new在該設(shè)備下面創(chuàng)建一個附屬的pci bus。在調(diào)用該函數(shù)時,傳遞的name為NULL。
? ?下面再看看這個bus的名稱怎么會變成pci.0的
7). pci_bus_new調(diào)用pci_bus_new_inplace(bus, parent, name, devfn_min),其中bus指向剛分配的
? ?內(nèi)存,parent是前面創(chuàng)建的host device,name為NULL,devfn_min為0
8). pci_bus_new_inplace會調(diào)用qbus_create_inplace(&bus->qbus, &pci_bus_info, parent, name),
? ?注意這里的第二個參數(shù)是&pci_bus_info
9). qbus_create_inplace在開始的地方會為該bus生成一個名稱。因為傳遞進來的name為NULL,并且
? ?parent(那個host device)的id也為NULL,因此分支會跳到下面的代碼
? ? ? ??
? ? ? ? len = strlen(info->name) + 16;
? ? ? ? buf = qemu_malloc(len);
? ? ? ? len = snprintf(buf, len, "%s.%d", info->name,
? ? ? ? ? ? ? ? ? ? ? ?parent ? parent->num_child_bus : 0);
? ? ? ? for (i = 0; i < len; i++)
? ? ? ? ? ? buf[i] = qemu_tolower(buf[i]);
? ? ? ? bus->name = buf;
10). 在該段代碼中,info就是之前pci_bus_new_inplace調(diào)用時傳進來的&pci_bus_info,info->name是
? ? 字符串"PCI"。并且,因為這是在host device上創(chuàng)建的第一個bus,因此parent->num_child_bus = 0,
? ? 最后經(jīng)過小寫處理之后,該bus的名稱就成為了"pci.0"
?
? ? 這一段分析所對應(yīng)的bus/device layout如下
? ? main-system-bus ----> ?i440FX-pcihost ----> pci.0
?
與這段流程類似的有一張流程圖可以更加詳盡的介紹一下流程,但與上文介紹的內(nèi)容不是一一對應(yīng)的。
?
5 QEMU網(wǎng)卡的流程
?
?
?
6 QEMU中使用BIOS的流程分析
http://www.ibm.com/developerworks/cn/linux/1410_qiaoly_qemubios/
?
?
三、相關(guān)技術(shù)-處理器管理和硬件輔助虛擬化技術(shù)
? ? Intel 在2006年發(fā)布了硬件虛擬化技術(shù)。其中支持X86體系結(jié)構(gòu)的稱為Intel VT-x技術(shù)。ADM稱為SVM技術(shù)。
VT-x引入了一種新的處理器操作,叫做VMX(Virtual Machine Extension),提供了兩種處理器的工作環(huán)境。VMCS結(jié)構(gòu)實現(xiàn)兩種環(huán)境之間的切換。 VM Entry 使虛擬機進去客戶模式, VM Exit 使虛擬機退出客戶模式。
1 KVM中Guest OS的調(diào)度執(zhí)行
? ? VMM調(diào)度Guest OS執(zhí)行時,Qemu通過ioctl系統(tǒng)調(diào)用進入內(nèi)核模式,在KVM Driver中通過get_cpu獲得當(dāng)前物理CPU的引用。之后將Guest狀態(tài)從VMCS中讀出。并裝入物理CPU中。執(zhí)行VMLAUCH指令使得物理處理器進入非根操作環(huán)境,運行客戶代碼。
? ? 當(dāng)Guest OS執(zhí)行一些特權(quán)指令或者外部事件時,比如I/O訪問,對控制寄存器的操作,MSR的讀寫數(shù)據(jù)包到達等。都會導(dǎo)致物理CPU發(fā)生VMExit,停止運行Guest OS。將Guest OS保存到VMCS中,Host狀態(tài)裝入物理處理器中,處理器進入根操作環(huán)境,KVM取得控制權(quán),通過讀取VMCS中VM_EXIT_REASON字段得到引起VM Exit的原因。從而調(diào)用kvm_exit_handler處理函數(shù)。如果由于I/O獲得信號到達,則退出到用戶模式的Qemu處理。處理完畢后,重新進入客戶模式運行虛擬CPU。如果是因為外部中斷,則在Lib KVM中做一些必要的處理,重新進入客戶模式執(zhí)行客戶代碼。
2?KVM中內(nèi)存管理
? ? KVM使用影子頁表實現(xiàn)客戶物理地址到主機物理地址的轉(zhuǎn)換。初始為空,隨著虛擬機頁訪問實效的增加,影子頁表被逐漸建立起來,并隨著客戶機頁表的更新而更新。在KVM中提供了一個哈希列表和哈希函數(shù),以客戶機頁表項中的虛擬頁號和該頁表項所在頁表的級別作為鍵值,通過該鍵值查詢,如不為空,則表示該對應(yīng)的影子頁表項中的物理頁號已經(jīng)存在并且所指向的影子頁表已經(jīng)生成。如為空,則需新生成一張影子頁表,KVM將獲取指向該影子頁表的主機物理頁號填充到相應(yīng)的影子頁表項的內(nèi)容中,同時以客戶機頁表虛擬頁號和表所在的級別生成鍵值,在代表該鍵值的哈希桶中填入主機物理頁號,以備查詢。但是一旦Guest OS中出現(xiàn)進程切換,會把整個影子頁表全部刪除重建,而剛被刪掉的頁表可能很快又被客戶機使用,如果只更新相應(yīng)的影子頁表的表項,舊的影子頁表就可以重用。因此在KVM中采用將影子頁表中對應(yīng)主機物理頁的客戶虛擬頁寫保護并且維護一張影子頁表的逆向映射表,即從主機物理地址到客戶虛擬地址之間的轉(zhuǎn)換表,這樣VM對頁表或頁目錄的修改就可以觸發(fā)一個缺頁異常,從而被KVM捕獲,對客戶頁表或頁目錄項的修改就可以同樣作用于影子頁表,通過這種方式實現(xiàn)影子頁表與客戶機頁表保持同步。
3 KVM中設(shè)備管理
? ? 一個機器只有一套I/O地址和設(shè)備。設(shè)備的管理和訪問是操作系統(tǒng)中的突出問題、同樣也是虛擬機實現(xiàn)的難題,另外還要提供虛擬設(shè)備供各個VM使用。在KVM中通過移植Qemu中的設(shè)備模型(Device Model)進行設(shè)備的管理和訪問。操作系統(tǒng)中,軟件使用可編程I/O(PIO)和內(nèi)存映射I/O(MMIO)與硬件交互。而且硬件可以發(fā)出中斷請求,由操作系統(tǒng)處理。在有虛擬機的情況下,虛擬機必須要捕獲并且模擬PIO和MMIO的請求,模擬虛擬硬件中斷。
? ? 捕獲PIO:由硬件直接提供。當(dāng)VM發(fā)出PIO指令時,導(dǎo)致VM Exit然后硬件會將VM Exit原因及對應(yīng)的指令寫入VMCS控制結(jié)構(gòu)中,這樣KVM就會模擬PIO指令。MMIO捕獲:對MMIO頁的訪問導(dǎo)致缺頁異常,被KVM捕獲,通過X86模擬器模擬執(zhí)行MMIO指令。KVM中的I/O虛擬化都是用戶空間的Qemu實現(xiàn)的。所有PIO和MMIO的訪問都是被轉(zhuǎn)發(fā)到Qemu的。Qemu模擬硬件設(shè)備提供給虛擬機使用。KVM通過異步通知機制以及I/O指令的模擬來完成設(shè)備訪問,這些通知包括:虛擬中斷請求,信號驅(qū)動機制以及VM間的通信。
以虛擬機接收數(shù)據(jù)包來說明虛擬機和設(shè)備的交互。
graphic
(1)當(dāng)數(shù)據(jù)包到達主機的物理網(wǎng)卡后,調(diào)用物理網(wǎng)卡的驅(qū)動程序,在其中利用Linux內(nèi)核中的軟件網(wǎng)橋,實現(xiàn)數(shù)據(jù)的轉(zhuǎn)發(fā)。
(2)在軟件網(wǎng)撟這一層,會判斷數(shù)據(jù)包是發(fā)往那個設(shè)備的,同時調(diào)用網(wǎng)橋的發(fā)送函數(shù),向?qū)?yīng)的端口發(fā)送數(shù)據(jù)包。
(3)若數(shù)據(jù)包是發(fā)往虛擬機的,則要通過tap設(shè)備進行轉(zhuǎn)發(fā),tap設(shè)備由兩部分組成,網(wǎng)絡(luò)設(shè)備和字符設(shè)備。網(wǎng)絡(luò)設(shè)備負責(zé)接收和發(fā)送數(shù)據(jù)包,字符設(shè)備負責(zé)將數(shù)據(jù)包往內(nèi)核空間和用戶空間進行轉(zhuǎn)發(fā)。Tap網(wǎng)絡(luò)部分收到數(shù)據(jù)包后,將其設(shè)備文件符置位,同時向正在運行VM的進程發(fā)出I/O可用信號,引起VM Exit,停止VM運行,進入根操作狀態(tài)。KVM根據(jù)KVM_EXIT_REASON判斷原因,模擬I/O指令的執(zhí)行,將中斷注入到VM的中斷向量表中。
(4)返回用戶模式的Qemu中,執(zhí)行設(shè)備模型。返回到kvm_main_loop中,執(zhí)行kvm_main_loop_wait,之后進入main_loop_wait中,在這個函數(shù)里收集對應(yīng)設(shè)備的設(shè)備文件描述符的狀態(tài),此時tap設(shè)備文件描述符的狀態(tài)同樣被集到fd set。
(5)kvm_main_loop不停地循環(huán),通過select系統(tǒng)調(diào)用判斷哪個文件描述符的狀態(tài)發(fā)生變化,相應(yīng)的調(diào)用對應(yīng)的處理函數(shù)。對予tap來說,就會通過qemu_send_packet將數(shù)據(jù)發(fā)往rtl8139_do_receiver,在這個函數(shù)中完成相當(dāng)于硬件RTL8139網(wǎng)卡的邏輯操作。KVM通過模擬I/O指令操作虛擬RTL8139將數(shù)據(jù)拷貝到用戶地址空間,放在相應(yīng)的I/O地址。用戶模式處理完畢后返回內(nèi)核模式,而后進入客戶模式,VM被再次執(zhí)行,繼續(xù)收發(fā)數(shù)據(jù)包。
?
三、 源碼分析
1 源碼文件結(jié)構(gòu)
源碼文件主要是分為三部分:kvm核心代碼(平臺無關(guān))、kvm平臺相關(guān)代碼以及頭文件
kvm核心代碼目錄:virt/kvm,其中所包含文件:
? ? ?* ioapic.h
? ? ?* ioapic.c
? ? ?* iodev.h
? ? ?* kvm_main.c
?
kvm平臺相關(guān)源代碼文件。比如針對intel的HVM支持的vmx.c文件,以及針對AMD的HVM支持的svm.c文件。其所在目錄為: arch/x86/kvm,其中所包含的文件為:
? ? * Kconfig
? ? * Makefile
? ? * i8259.c
? ? * irq.c
? ? * irq.h
? ? * kvm_svm.h
? ? * lapic.c
? ? * lapic.h
* mmu.c
* mmu.h
* paging_tmpl.h
* segment_descriptor.h
* svm.c
* svm.h
* vmx.c
* vmx.h
* x86.c
* x86_emulate.c
?
?
頭文件分為兩種,根據(jù)平臺分為include/linux和include/asm-x86目錄。
include/linux目錄包含的是通用pc上linux的頭文件,其對應(yīng)文件為:
* kvm.h
* kvm_host.h
* kvm_para.h
* kvm_x86_emulate.h
?
include/asm-x86/
* kvm.h
* kvm_host.h
* kvm_para.h
* kvm_x86_emulate.h
?
2 KVM創(chuàng)建和運行虛擬機的流程
? ? KVM虛擬機創(chuàng)建和運行虛擬機分為用戶態(tài)和核心態(tài)兩個部分,用戶態(tài)主要提供應(yīng)用程序接口,為虛擬機創(chuàng)建虛擬機上下文環(huán)境,在libkvm中提供訪問內(nèi)核字符設(shè)備/dev/kvm的接口;內(nèi)核態(tài)為添加到內(nèi)核中的字符設(shè)備/dev/kvm,模塊加載進內(nèi)核后即可進行接口用戶空間調(diào)用創(chuàng)建虛擬機。在創(chuàng)建虛擬機過程中,kvm字符設(shè)備主要為客戶機創(chuàng)建kvm數(shù)據(jù)機構(gòu),創(chuàng)建該虛擬機的虛擬機文件描述符及其相應(yīng)的數(shù)據(jù)結(jié)構(gòu)以及創(chuàng)建虛擬處理器及其相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。Kvm創(chuàng)建虛擬機的流程如下圖所示。
?
graphic
? ? 首先申明一個kvm_context_t變量用以描述用戶態(tài)虛擬機上下文信息,然后調(diào)用kvm_init()函數(shù)初始化虛擬機上下文信息;函數(shù)kvm_create()創(chuàng)建虛擬機實例,該函數(shù)通過ioctl系統(tǒng)調(diào)用創(chuàng)建虛擬機相關(guān)的內(nèi)核數(shù)據(jù)結(jié)構(gòu)并且返回虛擬機文件描述符給用戶態(tài)kvm_context_t數(shù)據(jù)結(jié)構(gòu);創(chuàng)建完內(nèi)核虛擬機數(shù)據(jù)結(jié)構(gòu)后,再創(chuàng)建內(nèi)核pit以及mmio等基本外設(shè)模擬設(shè)備,然后調(diào)用kvm_create_vcpu()函數(shù)來創(chuàng)建虛擬處理器,kvm_create_vcpu()函數(shù)通過ioctl()系統(tǒng)調(diào)用向由vm_fd文件描述符指向的虛擬文件調(diào)用創(chuàng)建虛擬處理器,并將虛擬處理器的文件描述符返回給用戶態(tài)程序,用以以后的調(diào)度使用;創(chuàng)建完虛擬處理器后,由用戶態(tài)的QEMU程序申請客戶機用戶空間,用以加載和運行客戶機代碼;為了使得客戶虛擬機正確執(zhí)行,必須要在內(nèi)核中為客戶機建立正確的內(nèi)存映射關(guān)系,即影子頁表信息。因此,申請客戶機內(nèi)存地址空間后,調(diào)用函數(shù)kvm_create_phys_mem()創(chuàng)建客戶機內(nèi)存映射關(guān)系,該函數(shù)主要通過ioctl系統(tǒng)調(diào)用向vm_fd指向的虛擬文件調(diào)用設(shè)置內(nèi)核數(shù)據(jù)結(jié)構(gòu)中客戶機內(nèi)存域相關(guān)信息,主要建立影子頁表信息;當(dāng)創(chuàng)建好虛擬處理器和影子頁表后,即可讀取客戶機到指定分配的空間中,然后調(diào)度虛擬處理器運行。調(diào)度虛擬機的函數(shù)為kvm_run(),該函數(shù)通過ioctl系統(tǒng)調(diào)用調(diào)用由虛擬處理器文件描述符指向的虛擬文件調(diào)度處理函數(shù)kvm_run()調(diào)度虛擬處理器的執(zhí)行,該系統(tǒng)調(diào)用將虛擬處理器vcpu信息加載到物理處理器中,通過vm_entry執(zhí)行進入客戶機執(zhí)行。在客戶機正常運行期間kvm_run()函數(shù)不返回,只有發(fā)生以下兩種情況時,函數(shù)返回:1,發(fā)生了I/O事件,如客戶機發(fā)出讀寫I/O的指令;2,產(chǎn)生了客戶機和內(nèi)核KVM都無法處理的異常。I/O事件處理完畢后,通過重新調(diào)用kvm_run()函數(shù)繼續(xù)調(diào)度客戶機的執(zhí)行。
?
?
?
?
?
?
?
?
?
?
?
?
內(nèi)存相關(guān):http://www.linux-kvm.org/page/Memory
?
?
?

?

?

KVM+QEMU學(xué)習(xí)筆記,搜集整理合集


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产一二三四区中 | 欧美三级成人理伦 | 亚洲第一在线 | 亚洲三区在线观看 | 欧美日韩综合精品一区二区三区 | 亚洲欧洲精品在线 | 色老头永久免费视频 | 欧美日韩国产中文字幕 | 国产精品久久久久aaaa九色 | 成人日韩视频 | 欧美爽爽爽爽爽爽视频 | 久草青娱乐 | 色房四播 | 狠狠干夜夜草 | 91精品久久久久久久久久 | 九九在线精品视频 | 日韩在线观看视频一区 | 天干夜天天夜天干天 | 黄色网页免费 | 亚洲成色www久久网站 | 狠狠干天天干 | 欧美精品欧美精品系列 | 国产中文精品无码欧美综合小说 | 久久精品视频网站 | 污污的网站免费在线观看 | 香蕉福利久久福利久久香蕉 | 国产欧美曰韩一区二区三区 | 日韩a级片| 婷婷久久五月天 | www.99热.com| 日本字幕在线观看 | 清草在线视频精品 | 国产精品午夜电影 | 狠狠操91 | 国产欧美日韩免费 | 国产免费视频 | 亚洲精品a级 | 日韩视频不卡 | 古装三级在线观看 | 日本不卡在线播放 | 粉色视频高清大全免费观看1 |