1. 程式人生 > >KVM+QEMU學習筆記

KVM+QEMU學習筆記

1 QEMU和KVM的關係:

     現在所說的虛擬化,一般都是指在CPU硬體支援基礎之上的虛擬化技術。KVM也同hyper-V、Xen一樣依賴此項技術。沒有CPU硬體虛擬化的支援,KVM是無法工作的。

     準確來說,KVM是Linux的一個模組。可以用modprobe去載入KVM模組。載入了模組後,才能進一步通過其他工具建立虛擬機器。但僅有KVM模組是 遠遠不夠的,因為使用者無法直接控制核心模組去作事情:還必須有一個使用者空間的工具才行。這個使用者空間的工具,開發者選擇了已經成型的開源虛擬化軟體 QEMU。說起來QEMU也是一個虛擬化軟體。它的特點是可虛擬不同的CPU。比如說在x86的CPU上可虛擬一個Power的CPU,並可利用它編譯出 可執行在Power上的程式。KVM使用了QEMU的一部分,並稍加改造,就成了可控制KVM的使用者空間工具了。所以你會看到,官方提供的KVM下載有兩 大部分三個檔案,分別是KVM模組、QEMU工具以及二者的合集。也就是說,你可以只升級KVM模組,也可以只升級QEMU工具。

2 QEMU基本介紹

     Qemu是一個完整的可以單獨執行的軟體,它可以用來模擬機器,非常靈活和可移植。它主要通過一個特殊的'重編譯器'將為特定處理器編寫二進位制程式碼轉換為另一種。(也就是,在PPCmac上面執行MIPS程式碼,或者在X86 PC上執行ARM程式碼)

3 KVM基本介紹

     KVM是一個基於Linux核心的虛擬機器,它屬於完全虛擬化範疇,從Linux-2.6.20開始被包含在Linux核心中。KVM基於x86硬體虛擬化技術,它的執行要求Intel VT-x或AMD SVM的支援。

     一般認為,虛擬機器監控的實現模型有兩類:監控模型(Hypervisor)和宿主機模型(Host-based)。由於監控模型需要進行處理器排程,還需要實現各種驅動程式,以支撐執行其上的虛擬機器,因此實現難度上一般要大於宿主機模型。KVM的實現採用宿主機模型(Host-based),由於KVM是整合在Linux核心中的,因此可以自然地使用Linux核心提供的記憶體管理、多處理器支援等功能,易於實現,而且還可以隨著Linux核心的發展而發展。另外,目前KVM的所有I/O虛擬化工作是藉助Qemu完成的,也顯著地降低了實現的工作量。以上可以說是KVM的優勢所在。

4 KVM架構

kvm基本結構有2個部分構成:

* kvm 驅動:現在已經是linux kernel的一個模組了。其主要負責虛擬機器的建立,虛擬記憶體的分配,VCPU暫存器的讀寫以及VCPU的執行。

* Qemu:用於模擬虛擬機器的使用者空間元件,提供I/O裝置模型,訪問外設的途徑。

kvm基本結構如上圖。kvm已經是核心模組,被看作是一個標準的linux 字符集裝置(/dev/kvm)。Qemu通過libkvm應用程式介面,用fd通過ioctl向裝置驅動來發送建立,執行虛擬機器命令。裝置驅動kvm就會來解析命令(kvm_dev_ioctl函式在kvm_main.c檔案中),如下圖:

KVM模組讓Linux主機成為一個虛擬機器監視器(VMM,Virtual Machine Monitor),並且在原有的Linux兩種執行模式基礎上,新增加了客戶模式,客戶模式擁有自己的核心模式和使用者模式。在虛擬機器執行時,三種模式的工作各為:

客戶模式:執行非I/O的客戶程式碼,虛擬機器執行在這個模式下。

使用者模式:代表使用者執行I/O指令,qemu執行在這個模式下。

核心模式:實現客戶模式的切換,處理因為I/O或者其他指令引起的從客戶模式退出(VM_EXIT)。kvm 模組工作在這個模式下。

在kvm的模型中,每一個Gust OS都是作為一個標準的linux程序,都可以使用linux程序管理命令管理。

KVM三種類型的檔案描述符

     首先是kvm裝置本身。kvm核心模組本身是作為一個裝置驅動程式安裝的,驅動的裝置名稱是”/dev/kvm“。要使用kvm,需要先用open開啟”/dev/kvm”裝置,得到一個kvm裝置檔案描述符fd,然後利用此fd呼叫ioctl就可以向裝置驅動傳送命令了。kvm驅動解析此種請求的函式是kvm_dev_ioctl(kvm_main.c),如KVM_CREATE_VM。

     其次是具體的VM。通過KVM_CREATE_VM建立了一個VM後,使用者程式需要傳送一些命令給VM,如KVM_CREATE_VCPU。這些命令當然也是要通過ioctl來發送,所以VM也需要對應一個檔案描述符才行。使用者程式中用ioctl傳送KVM_CREATE_VM得到的返回值就是新建立VM對應的fd,之後利用此fd傳送命令給此VM。kvm驅動解析此種請求的函式是kvm_vm_ioctl。此外,與OS執行緒類似,每個VM在kvm驅動中會對應一個VM控制塊結構struct kvm,每個對VM的核心操作都基本要訪問這個結構,那麼kvm驅動是如何找到請求這次命令的VM的控制塊的呢?回答這個問題首先要知道,linux核心用一個struct file結構來表示每個開啟的檔案,其中有一個void *private_data欄位,kvm驅動將VM控制塊的地址儲存到對應struct file的private_data中。使用者程式傳送ioctl時,指定具體的fd,核心根據fd可以找到相應的struct file,傳遞給kvm_vm_ioctl,再通過private_data就可以找到了。

     最後是具體的VCPU。原理基本跟VM情況差不多,kvm驅動解析此種請求的函式是kvm_vcpu_ioctl。VCPU控制塊結構為struct kvm_vcpu。

5 KVM工作原理

    KVM的基本工作原理:使用者模式的Qemu利用介面libkvm通過ioctl系統呼叫進入核心模式。KVM Driver為虛擬機器建立虛擬記憶體和虛擬CPU後執行VMLAUCH指令進入客戶模式。裝載Guest OS執行。如果Guest OS發生外部中斷或者影子頁表(shadow page)缺頁之類的事件,暫停Guest OS的執行,退出客戶模式進行一些必要的處理。然後重新進入客戶模式,執行客戶程式碼。如果發生I/O事件或者訊號佇列中有訊號到達,就會進入使用者模式處理。KVM採用全虛擬化技術。客戶機不用修改就可以執行。

二、QEMU簡介

1 QEMU的框架

    QEMU屬於應用層的模擬程式,它支援兩種操作模式:使用者模式模擬和系統模式模擬。使用者模式模擬 允許一個 CPU 構建的程序在另一個 CPU 上執行(執行主機 CPU 指令的動態翻譯並相應地轉換 Linux 系統呼叫)。系統模式模擬 允許對整個系統進行模擬,包括處理器和配套的外圍裝置。每個模擬方式都有其安裝要求。對於系統模擬,安裝客戶作業系統就像安裝一臺單獨的計算機(下載並使用預先配置的磁碟映象是一個替代方法)。對於使用者空間模擬,不需要安裝整個系統,但要考慮安裝使用軟體所需的支援庫。也許還需要配置跨編譯器以生成想要測試的二進位制檔案。

    在本文中,我將主要介紹QEMU的系統模式下的工作方式。

    QEMU工作在作業系統的使用者態程式,它有一個如一般應用程式的入口點函式——main函式(vl.c原始碼檔案)。這個main函式也是QEMU的系統模式的入口點函式。在這個main函式中,其主要的工作就是初始化一系列的引數、硬體裝置模擬等以及命令列引數轉換,進而進入一個main_loop的迴圈中。

    在QEMU中有很重要的一部分就是TCG(Tiny Code Generator),這就是一個Guest OS程式向Host OS程式轉換的內建翻譯器,主要目的是為了在Host的硬體上面可以執行Guest的程式碼。本文將主要集中在整體流程的介紹以及QEMU與KVM之間的互動,因此這部分將不會花過多精力涉及。

。由QEMU的開發者編寫,主要是為了讓開發者對QEMU有個清晰的認識,但是由於該文章比較古老,因此將根據現有的設計再做調整。

--------------------------------

    Guest OS的執行涉及到Guest OS程式碼的執行、timer的處理、IO處以及對monitor命令的響應。

對於這種需要回應從多重資源發來的事件的程式來說,現行有兩種比較流行的架構:

  1. Parallel architecture(平行架構)把那些可以同時執行的工作分成多個程序或是執行緒。我叫他執行緒化的架構(threaded architecture)。
  2. Event-driven architecture(事件驅動架構)通過執行一個主迴圈來發送事件到handler以此對事件做反饋處理。這一方法通常通過使用select(2)或者poll(2)系列的系統呼叫等待多個檔案描述符的方式來實現。

目前,QEMU使用一種混合架構,把事件驅動和執行緒組合在一起。這種做法之所以有效是因為只在單個執行緒上執行的事件迴圈不能有效利用底層多核心的硬體。再則,有時候使用一個專用執行緒來減少特定工作的負擔要比把它整合在一個事件驅動的架構中更簡單有效。雖然如此,QEMU的核心還是事件驅動的,大多數程式碼都是在這樣一個環境中執行的。

QEMU的事件驅動核心

一個事件驅動的架構是以一個派發事件到處理函式的迴圈為核心的。

QEMU的主事件迴圈是main_loop_wait()(main-loop.c檔案),它主要完成以下工作:

  1. 等待檔案描述符變成可讀或可寫。檔案描述符是一個關鍵角色,因為files、sockets、pipes以及其他各種各樣的資源都是檔案描述符(file descriptors)。檔案描述符的增加方式:qemu_set_fd_handler()。
  2. 處理到期的定時器(timer)。定時器的管理在qemu-timer.c檔案中。
  3. 執行bottom-halves(BHs),它和定時器類似會立即過期。BHs用來放置回撥函式的重入和溢位。BHs的新增方式:qemu_bh_schedule()。

當一個檔案描述符準備好了、一個定時器過期或者是一個BH被排程到時,事件迴圈就會呼叫一個回撥函式來回應這些事件。回撥函式對於它們的環境有兩條規則:

  1. 沒有其他核心同時在執行,所以不需要考慮同步問題。對於核心程式碼來說,回撥函式是線性和原子執行的。在任意給定的時間裡只有一個執行緒控制執行核心程式碼。
  2. 不應該執行可阻斷系統呼叫或是長執行計算(long-running computations)。由於事件迴圈在繼續其他事件時會等待當前回撥函式返回,所以如果違反這條規定會導致guest暫停並且使管理器無響應。

第二條規定有時候很難遵守,在QEMU中會有程式碼會被阻塞。事實上,qemu_aio_wait()裡面還有巢狀迴圈,它會等待那些頂層事件迴圈正在處理的事件的子集。慶幸的是,這些違反規則的部分會在未來重新架構程式碼時被移除。新程式碼幾乎沒有合理的理由被阻塞,而解決方法之一就是使用專屬的工作執行緒來卸下(offload)這些長執行或者會被阻塞的程式碼。

卸下特殊的任務到工作執行緒

儘管很多I/O操作可以以一種非阻塞的形式執行,但有些系統呼叫卻沒有非阻塞的替代方式。再者,長執行的計算單純的霸佔著CPU並且很難被分割到回撥函式中。在這種情況下專屬的工作執行緒就可以用來小心的將這些任務移出核心QEMU。

posix-aio-compat.c中有一個工作執行緒的例子,一個非同步的檔案I/O實現。當核心QEMU放出一個aio請求,這個請求被放到一個佇列總。工作執行緒從佇列中拿出這個請求,並在核心QEMU中執行它。它們可能會有阻塞的動作,但因為它們在它們自己的執行緒中執行所以並不會阻塞剩餘的QEMU執行。這個實現對於必要的同步以及工作執行緒和核心QEMU的通訊有小心的處理。

另一個例子是ui/vnc-jobs-async.c中將計算密集型的映象解壓縮和解碼移到工作執行緒中。

因為核心QEMU的主要部分不是執行緒安全的,所以工作執行緒不能呼叫到核心QEMU的程式碼。當然簡單的使用類似qemu_malloc()的函式是執行緒安全的,這些是例外,而不在規則之內。這也引發了工作執行緒如何將事件傳回核心QEMU的問題。

當一個工作執行緒需要通知核心QEMU時,一個管道或者一個qemu_eventfd()檔案描述符將被新增到事件迴圈中。工作執行緒可以向檔案描述符中寫入,而當檔案描述符變成可讀時,事件迴圈會呼叫回撥函式。另外,必須使用訊號來確保事件迴圈可以在任何環境下執行。這種方式在posix-aio-compat.c中被使用,而且在瞭解guest程式碼如何被執行之後變的更有意義。

執行guest程式碼

目前為止我們已經大概的看了一下QEMU中的事件迴圈和它的主要規則。其中執行guest程式碼的能力是特別重要的,少了它,QEMU可以響應事件但不會非常有用。

這裡有兩種方式用來執行guest程式碼:Tiny Code Generator(TCG)和KVM。TCG通過動態二進位制轉化(dynamic binary translation)來模擬guest,它也以即時編譯(Just-in-Time compilation)被熟知。而KVM則是利用現有的現代intel和AMD CPU中硬體虛擬化擴充套件來直接安全的在host CPU上執行guest程式碼。在這篇文章中,真正重要的並不是實際的技術,不管是TCG還是KVM都允許我們跳轉到guest程式碼中並且執行它。

跳入guest程式碼中會使我們失去對程式執行的控制而把控制交給guest。而一個正在執行guest程式碼的執行緒不能同時處在事件迴圈中,因為guest控制著CPU。一般情況下,花在guest程式碼中的時間是有限的。因為對於被模擬裝置的暫存器的讀寫和其他異常導致我們離開guest而把控制交還給QEMU。在極端的情況下一個guest可以花費無限制的時間而不放棄控制權,而這會引起QEMU無響應。

為了解決guest程式碼霸佔問題,QEMU執行緒使用訊號來跳出guest。一個UNIX訊號從當前的執行流程中抓取控制權並呼叫一個訊號處理函式。這使得QEMU得以採取措施來離開guest程式碼並返回它的主迴圈,因而事件迴圈才有機會處理待解決的事件。

上述的結果是新事件可能第一時間被發覺如果QEMU當前正在guest程式碼中。事實上QEMU大多數時間規避處理事件,但因而產生的額外的延遲也成為的效能問題。因此,到核心QEMU的I/O結束和從工作執行緒來的通知使用訊號來確保事件迴圈會被立即處理。

你可能會疑惑說到底事件迴圈和有多核心的SMP guest之間的架構圖會是什麼樣子的。而現在,執行緒模型和guest程式碼都已經提到了,現在我們來討論整體架構。

IOTHREAD和NON-IOTHREAD執行緒架構

傳統的架構是單個QEMU執行緒來執行guest程式碼和事件迴圈。這個模型就是所謂的non-iothread或者說!CONFIG_IOTHREAD,它是QEMU預設使用./configure && make的設定。QEMU執行緒執行guest程式碼直到一個異常或者訊號出現才回到控制器。然後它在select(2)不被阻塞的情況執行一次事件迴圈的一次迭代。然後它又回到guest程式碼中並重覆上述過程直到QEMU被關閉。

如果guest使用,例如-smp 2,以啟動一個多vcpu啟動,也不會有多的QEMU執行緒被建立。取而代之的是在單個QEMU執行緒中多重執行兩個vcpu和事件迴圈。因而non-iothread不能很好利用多核心的host硬體,而使得對SMP guest的模擬效能很差。

需要注意的是,雖然只有一個QEMU執行緒,但可能會有0或多個工作執行緒。這些執行緒可能是臨時的也可能是永久的。記住這些工作執行緒只執行特殊的任務而不執行guest程式碼也不處理事件。我之說以要強調這個是因為當監視host時很容易被工作執行緒迷惑而把他們當做vcpu執行緒來中斷。記住,non-iothread只有一個QEMU執行緒。

一種更新的架構是每個vcpu一個QEMU執行緒外加一個專用的事件迴圈執行緒。這個模型被定義為iothread或者CONFIG_IOTHREAD,它可以通過./configure --enable-io-thread在建立時開啟。每個vcpu執行緒可以平行的執行guest程式碼,以此提供真正的SMP支援。而iothread執行事件迴圈。核心QEMU程式碼不能同時執行的規則通過一個全域性互斥來維護,並通過該互斥鎖同步vcpu和iothread間核心QEMU程式碼。大多數時候vcpu執行緒在執行guest程式碼而不需要獲取全域性互斥鎖。大多數時間iothread被阻塞在select(2)因而也不需要獲取全域性互斥鎖。

注意,TCG不是執行緒安全的,所以即使在在iothread模式下,它還是在一個QEMU執行緒中執行多個vcpu。只有KVM可以真正利用每個vcpu一個執行緒的優勢。

2 QEMU的執行緒

    HOST將qemu當做一個普通的程序和其他程序統一排程,可以使用資源對qemu進行資源預留隔離(cpuset)和優先順序提升(chrt)。qemu程序包含多個執行緒,分配給GUEST的每個vcpu都對應一個vcpu執行緒,另外qemu還有一個執行緒迴圈執行select專門處理I/O事件。

QEMU的主要執行緒:

  • 主執行緒(main_loop),一個
  • vCPU執行緒,一個或者多個
  • I/O執行緒(aio),一個或者多個
  • worker thread(VNC/SPICE),一個

qemu裡有個主執行緒處於無限迴圈,會做如下操作

  • IO執行緒裡有個select函式,它阻塞在一個檔案描述符(fd)集合上,等待其就緒。fd可以通過qemu_set_fd_handler()
  • 執行到期的定時器,定時器通過qemu_mod_timer新增
  • 執行BH(bottom-halves),BH通過qemu_bh_schedule新增

當檔案描述符就緒,定期器到期或者BH被排程,相應的callback會被呼叫

qemu中還有一些worker threads。一些佔用CPU較多的工作會明顯增大主IO執行緒的IO處理延遲,這些工作可以放在專用的執行緒裡,例如posix-aio-compat.c中實現了非同步檔案I/O,當有aio請求產生,該請求被置於佇列,工作執行緒可以在qemu主執行緒之外處理這些請求。VNC就是這樣一個例子,它用了一個專門的worker thread(ui/vnc-jobs.c)進行計算密集型的影象壓縮和編碼工作。

3 QEMU的初始化流程

    待續

4 QEMU虛擬網絡卡裝置的建立流程

虛擬網絡卡型別為virtio-net-pci

virtio網絡卡裝置對應的命令列引數為 

-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). 呼叫module_call_init(MODULE_INIT_DEVICE); 往系統中新增所有支援的裝置型別

   virtio-net-pci的裝置型別資訊如下(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). 呼叫qemu_opts_foreach(&qemu_device_opts, device_init_func, NULL, 1) 建立命令列上指定的裝置

4). device_init_func呼叫qdev_device_add(opts)

5). qdev_device_add函式的流程如下:

   a) 呼叫qemu_opt_get(opts, "driver")獲取driver選項,這裡應該是virtio-net-pci

   b) 呼叫qdev_find_info(NULL, driver)來獲取註冊的DeviceInfo,這裡應該是上面virtio_info裡面關於

      virtio-net-pci的結構

   c) 呼叫qemu_opt_get(opts, "bus")獲取bus路徑,以/分隔各元件。這裡是pci.0

   d) 如果bus路徑不為空,則呼叫qbus_find(path)來獲取bus例項(BusState結構)

      qbus_find函式的流程如下:

      d.1) 先找到路徑中的根bus,如果路徑以/開頭,則根bus為main_system_bus,否則,使用

           qbus_find_recursive(main_system_bus, elem, NULL)來查詢。這裡的elem = "pci.0"

      d.2) 如果整個路徑已經完成,則返回當前bus

      d.2) parse出下一個元件,呼叫qbus_find_dev查詢對應的裝置

      d.3) parse出下一個元件,呼叫qbus_find_bus查詢屬於上層裝置的子bus

      d.4) 返回步驟2

      由於這裡的值是pci.0,因此其實只進行了一次qbus_find_recursive呼叫

   e) 如果bus路徑為空,則呼叫qbus_find_recursive(main_system_bus, NULL, info->bus_info)來獲取bus

      例項。這裡的info是driver("virtio-net-pci")所對應的DeviceInfo,即最上面的結構

      virtio-pci的初始化步驟是virtio_pci_register_devices -> pci_qdev_register_many -> 

      pci_qdev_register,在該函式中,會設定info->bus_info = &pci_bus_info,這樣就把PCIDeviceInfo

      和pci的BusInfo聯絡起來了

      qbus_find_recursive是一個遞迴函式,其流程如下:

      e.1) 如果當前bus的名稱和指定的名稱相同(指定名稱不為空的情況下),並且當前bus指向的bus info和

           指定的bus info相同(指定bus info不為空的情況下),則返回當前bus

      e.2) 這裡是一個兩重迴圈:

           對於當前bus所有附屬的裝置(bus->children為連結串列頭)

               對於當前裝置所有的附屬bus(dev->child_bus為連結串列頭)

                   呼叫qbus_find_recursive函式

   f) 呼叫qdev_create_from_info(bus, info)來建立裝置,返回的是DeviceState結構。這裡其實返回的是

      一個VirtIOPCIProxy例項,因為create的時候是根據qdev.size來分配記憶體大小的。

   g) 如果qemu_opts_id(opts)不為空,則設定qdev->id

   h) 呼叫qemu_opt_foreach(opts, set_property, qdev, 1)來設定裝置的各種屬性

   i) 呼叫qdev_init來初始化裝置。

   j) qdev_init會呼叫dev->info->init函式。這裡實際呼叫的函式是virtio_net_init_pci

在這裡也大致描述一下bus pci.0是如何生成的

1). 在main函式裡面很前面的地方會呼叫module_call_init(MODULE_INIT_MACHINE);

2). module_call_init會呼叫所有已註冊QEMUMachine的init函式。該版本的qemu是註冊了

   pc_machine_rhel610, pc_machine_rhel600, pc_machine_rhel550, pc_machine_rhel544,

   pc_machine_rhel540這幾個 (pc.c)

3). 這些Machine的init函式(pc_init_rhel600, ...)都會呼叫到pc_init_pci函式

4). pc_init_pci會呼叫pc_init1,pc_init1在pci_enabled情況下會呼叫i440fx_init (piix_pci.c)

5). i440fx_init首先會呼叫qdev_create(NULL, "i440FX-pcihost")建立一個host device

6). 然後呼叫pci_bus_new在該裝置下面建立一個附屬的pci bus。在呼叫該函式時,傳遞的name為NULL。

   下面再看看這個bus的名稱怎麼會變成pci.0的

7). pci_bus_new呼叫pci_bus_new_inplace(bus, parent, name, devfn_min),其中bus指向剛分配的

   記憶體,parent是前面建立的host device,name為NULL,devfn_min為0

8). pci_bus_new_inplace會呼叫qbus_create_inplace(&bus->qbus, &pci_bus_info, parent, name),

   注意這裡的第二個引數是&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呼叫時傳進來的&pci_bus_info,info->name是

    字串"PCI"。並且,因為這是在host device上建立的第一個bus,因此parent->num_child_bus = 0,

    最後經過小寫處理之後,該bus的名稱就成為了"pci.0"

    這一段分析所對應的bus/device layout如下

    main-system-bus ---->  i440FX-pcihost ----> pci.0

與這段流程類似的有一張流程圖可以更加詳盡的介紹一下流程,但與上文介紹的內容不是一一對應的。

5 QEMU網絡卡的流程

6 QEMU中使用BIOS的流程分析

http://www.ibm.com/developerworks/cn/linux/1410_qiaoly_qemubios/

三、相關技術-處理器管理和硬體輔助虛擬化技術

    Intel 在2006年釋出了硬體虛擬化技術。其中支援X86體系結構的稱為Intel VT-x技術。ADM稱為SVM技術。

VT-x引入了一種新的處理器操作,叫做VMX(Virtual Machine Extension),提供了兩種處理器的工作環境。VMCS結構實現兩種環境之間的切換。VM Entry使虛擬機器進去客戶模式,VM Exit使虛擬機器退出客戶模式。

1 KVM中Guest OS的排程執行

    VMM排程Guest OS執行時,Qemu通過ioctl系統呼叫進入核心模式,在KVM Driver中通過get_cpu獲得當前物理CPU的引用。之後將Guest狀態從VMCS中讀出。並裝入物理CPU中。執行VMLAUCH指令使得物理處理器進入非根操作環境,執行客戶程式碼。

    當Guest OS執行一些特權指令或者外部事件時,比如I/O訪問,對控制暫存器的操作,MSR的讀寫資料包到達等。都會導致物理CPU發生VMExit,停止執行Guest OS。將Guest OS儲存到VMCS中,Host狀態裝入物理處理器中,處理器進入根操作環境,KVM取得控制權,通過讀取VMCS中VM_EXIT_REASON欄位得到引起VM Exit的原因。從而呼叫kvm_exit_handler處理函式。如果由於I/O獲得訊號到達,則退出到使用者模式的Qemu處理。處理完畢後,重新進入客戶模式執行虛擬CPU。如果是因為外部中斷,則在Lib KVM中做一些必要的處理,重新進入客戶模式執行客戶程式碼。

2 KVM中記憶體管理

    KVM使用影子頁表實現客戶實體地址到主機實體地址的轉換。初始為空,隨著虛擬機器頁訪問實效的增加,影子頁表被逐漸建立起來,並隨著客戶機頁表的更新而更新。在KVM中提供了一個雜湊列表和雜湊函式,以客戶機頁表項中的虛擬頁號和該頁表項所在頁表的級別作為鍵值,通過該鍵值查詢,如不為空,則表示該對應的影子頁表項中的物理頁號已經存在並且所指向的影子頁表已經生成。如為空,則需新生成一張影子頁表,KVM將獲取指向該影子頁表的主機物理頁號填充到相應的影子頁表項的內容中,同時以客戶機頁表虛擬頁號和表所在的級別生成鍵值,在代表該鍵值的雜湊桶中填入主機物理頁號,以備查詢。但是一旦Guest OS中出現程序切換,會把整個影子頁表全部刪除重建,而剛被刪掉的頁表可能很快又被客戶機使用,如果只更新相應的影子頁表的表項,舊的影子頁表就可以重用。因此在KVM中採用將影子頁表中對應主機物理頁的客戶虛擬頁防寫並且維護一張影子頁表的逆向對映表,即從主機實體地址到客戶虛擬地址之間的轉換表,這樣VM對頁表或頁目錄的修改就可以觸發一個缺頁異常,從而被KVM捕獲,對客戶頁表或頁目錄項的修改就可以同樣作用於影子頁表,通過這種方式實現影子頁表與客戶機頁表保持同步。

3 KVM中裝置管理

    一個機器只有一套I/O地址和裝置。裝置的管理和訪問是作業系統中的突出問題、同樣也是虛擬機器實現的難題,另外還要提供虛擬裝置供各個VM使用。在KVM中通過移植Qemu中的裝置模型(Device Model)進行裝置的管理和訪問。作業系統中,軟體使用可程式設計I/O(PIO)和記憶體對映I/O(MMIO)與硬體互動。而且硬體可以發出中斷請求,由作業系統處理。在有虛擬機器的情況下,虛擬機器必須要捕獲並且模擬PIO和MMIO的請求,模擬虛擬硬體中斷。

    捕獲PIO:由硬體直接提供。當VM發出PIO指令時,導致VM Exit然後硬體會將VM Exit原因及對應的指令寫入VMCS控制結構中,這樣KVM就會模擬PIO指令。MMIO捕獲:對MMIO頁的訪問導致缺頁異常,被KVM捕獲,通過X86模擬器模擬執行MMIO指令。KVM中的I/O虛擬化都是使用者空間的Qemu實現的。所有PIO和MMIO的訪問都是被轉發到Qemu的。Qemu模擬硬體裝置提供給虛擬機器使用。KVM通過非同步通知機制以及I/O指令的模擬來完成裝置訪問,這些通知包括:虛擬中斷請求,訊號驅動機制以及VM間的通訊。

以虛擬機器接收資料包來說明虛擬機器和裝置的互動。

(1)當資料包到達主機的物理網絡卡後,呼叫物理網絡卡的驅動程式,在其中利用Linux核心中的軟體網橋,實現資料的轉發。

(2)在軟體網撟這一層,會判斷資料包是發往那個裝置的,同時呼叫網橋的傳送函式,向對應的埠傳送資料包。

(3)若資料包是發往虛擬機器的,則要通過tap裝置進行轉發,tap裝置由兩部分組成,網路裝置和字元裝置。網路裝置負責接收和傳送資料包,字元裝置負責將資料包往核心空間和使用者空間進行轉發。Tap網路部分收到資料包後,將其裝置檔案符置位,同時向正在執行VM的程序發出I/O可用訊號,引起VM Exit,停止VM執行,進入根操作狀態。KVM根據KVM_EXIT_REASON判斷原因,模擬I/O指令的執行,將中斷注入到VM的中斷向量表中。

(4)返回使用者模式的Qemu中,執行裝置模型。返回到kvm_main_loop中,執行kvm_main_loop_wait,之後進入main_loop_wait中,在這個函式裡收集對應裝置的裝置檔案描述符的狀態,此時tap裝置檔案描述符的狀態同樣被集到fd set。

(5)kvm_main_loop不停地迴圈,通過select系統呼叫判斷哪個檔案描述符的狀態發生變化,相應的呼叫對應的處理函式。對予tap來說,就會通過qemu_send_packet將資料發往rtl8139_do_receiver,在這個函式中完成相當於硬體RTL8139網絡卡的邏輯操作。KVM通過模擬I/O指令操作虛擬RTL8139將資料拷貝到使用者地址空間,放在相應的I/O地址。使用者模式處理完畢後返回核心模式,而後進入客戶模式,VM被再次執行,繼續收發資料包。

三、 原始碼分析

1 原始碼檔案結構

原始碼檔案主要是分為三部分:kvm核心程式碼(平臺無關)、kvm平臺相關程式碼以及標頭檔案

kvm核心程式碼目錄:virt/kvm,其中所包含檔案:

     * ioapic.h

     * ioapic.c

     * iodev.h

     * kvm_main.c

kvm平臺相關原始碼檔案。比如針對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

標頭檔案分為兩種,根據平臺分為include/linux和include/asm-x86目錄。

include/linux目錄包含的是通用pc上linux的標頭檔案,其對應檔案為:

* 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建立和執行虛擬機器的流程

    KVM虛擬機器建立和執行虛擬機器分為使用者態和核心態兩個部分,使用者態主要提供應用程式介面,為虛擬機器建立虛擬機器上下文環境,在libkvm中提供訪問核心字元裝置/dev/kvm的介面;核心態為新增到核心中的字元裝置/dev/kvm,模組載入進核心後即可進行介面使用者空間呼叫建立虛擬機器。在建立虛擬機器過程中,kvm字元裝置主要為客戶機建立kvm資料機構,建立該虛擬機器的虛擬機器檔案描述符及其相應的資料結構以及建立虛擬處理器及其相應的資料結構。Kvm建立虛擬機器的流程如下圖所示。

    首先申明一個kvm_context_t變數用以描述使用者態虛擬機器上下文資訊,然後呼叫kvm_init()函式初始化虛擬機器上下文資訊;函式kvm_create()建立虛擬機器例項,該函式通過ioctl系統呼叫建立虛擬機器相關的核心資料結構並且返回虛擬機器檔案描述符給使用者態kvm_context_t資料結構;建立完核心虛擬機器資料結構後,再建立核心pit以及mmio等基本外設模擬裝置,然後呼叫kvm_create_vcpu()函式來建立虛擬處理器,kvm_create_vcpu()函式通過ioctl()系統呼叫向由vm_fd檔案描述符指向的虛擬檔案呼叫建立虛擬處理器,並將虛擬處理器的檔案描述符返回給使用者態程式,用以以後的排程使用;建立完虛擬處理器後,由使用者態的QEMU程式申請客戶機使用者空間,用以載入和執行客戶機程式碼;為了使得客戶虛擬機器正確執行,必須要在核心中為客戶機建立正確的記憶體對映關係,即影子頁表資訊。因此,申請客戶機記憶體地址空間後,呼叫函式kvm_create_phys_mem()建立客戶機記憶體對映關係,該函式主要通過ioctl系統呼叫向vm_fd指向的虛擬檔案呼叫設定核心資料結構中客戶機記憶體域相關資訊,主要建立影子頁表資訊;當建立好虛擬處理器和影子頁表後,即可讀取客戶機到指定分配的空間中,然後排程虛擬處理器執行。排程虛擬機器的函式為kvm_run(),該函式通過ioctl系統呼叫呼叫由虛擬處理器檔案描述符指向的虛擬檔案排程處理函式kvm_run()排程虛擬處理器的執行,該系統呼叫將虛擬處理器vcpu資訊載入到物理處理器中,通過vm_entry執行進入客戶機執行。在客戶機正常執行期間kvm_run()函式不返回,只有發生以下兩種情況時,函式返回:1,發生了I/O事件,如客戶機發出讀寫I/O的指令;2,產生了客戶機和核心KVM都無法處理的異常。I/O事件處理完畢後,通過重新呼叫kvm_run()函式繼續排程客戶機的執行。

相關推薦

KVM+QEMU學習筆記

1 QEMU和KVM的關係:      現在所說的虛擬化,一般都是指在CPU硬體支援基礎之上的虛擬化技術。KVM也同hyper-V、Xen一樣依賴此項技術。沒有CPU硬體虛擬化的支援,KVM是無法工作的。      準確來說,KVM是Linux的一個模組。可以用m

KVM&QEMU學習筆記(一)

1.安裝和配置 使用隨系統自帶的KVM 大部分的Linux發行版已經內建了KVM核心模組以及使用者空間工具,使用這些內建元件是最容易、推薦的方式:KVM核心模組現在是Linux核心的一部分,除非你使用的是精簡過的核心使用者空間元件,軟體包名稱一般是qemu-kvm或者k

KVM&QEMU學習筆記(二)

使用快照 快照(Snapshot)是Copy-on-write的一種應用。QEMU支援兩種快照: 內部快照(internal snapshot):在qcow2映象的snapshot table中維護的快照,所有快照都存放在一個映象檔案中 外部快照(external s

kvm虛擬化學習筆記(十四)之kvm虛擬機靜態遷移

虛擬主機 kvm 虛擬機遷移 kvm虛擬化 這裏提到的靜態遷移同是基於KVM虛擬主機之間的遷移,非異構虛擬化平臺的靜態遷移。1.靜態遷移就是虛擬機在關機狀態下,拷貝虛擬機虛擬磁盤文件與配置文件到目標虛擬主機中,實現的遷移。(1)虛擬主機各自使用本地存儲存放虛擬機磁盤文件本文實現基於本地磁盤存儲

kvm虛擬化學習筆記(十八)之ESXi到KVM之v2v遷移

虛擬化 kvm v2v kvm虛擬機遷移 1.ESXi到KVM之v2v情況說明(1).配置任務列表:1)VMwareESXi虛擬平臺下linux系統遷移到KVM虛擬平臺。2)VMwareESXi虛擬平臺下windows系統遷移到KVM虛擬平臺。提示:本文只介紹以上兩種遷移過程,KVM到ESXi

QEMU學習筆記-源碼分析01-module infrastracture

github reac strong free gis 執行 tor GNU C tab 前面的話 只是一個開始,希望自己能通過這樣的方法,歸納整理,方便自己和需要的人(當然只是自己的理解,歡迎指正和討論) 最開始搜QEMU的時候,就有很多人貼出了關於QEMU的源碼框架

kvm虛擬化學習筆記(四)之kvm虛擬機器日常管理與配置

KVM虛擬機器的管理主要是通過virsh命令對虛擬機器進行管理。 1. 檢視KVM虛擬機器配置檔案及執行狀態 (1) KVM虛擬機器預設配置檔案位置: /etc/libvirt/qemu/ autostart目錄是配置kvm虛擬機器開機自啟動目錄。 (2) virsh命令幫助 # virsh -help

KVM虛擬化學習筆記一:KVM概述

KVM虛擬機器簡介     KVM是kernel-based Virtual Machine的簡稱,目前已成為學術界的主流VMM之一。KVM的虛擬化需要硬體支援(如Intel VT技術或者AMD V技術),是基於硬體的完全虛擬化。KVM的安裝和使用相對於XEN來說十分的簡單和方便,並且功能強大,比較適用於高

學習筆記-KVM虛擬化

fault 執行 4.5 ado cow 名稱 也會 qemu-kvm inet6 虛擬化的分類   全虛擬化,半虛擬化 全虛擬化代表KVM,靠硬件來實現的 半虛擬化代表:XEN(它支持全虛擬化和半虛擬化) KVM是硬件虛擬化,準確的說就是一個Linux的模塊,模塊是內

虛擬化學習筆記-KVM虛擬化跨機遷移原理

線上遷移過程劃分為三個階段:準備階段、遷移階段和切換階段。遷移環境為虛擬化底層KVM+Qemu、虛擬化管理Libvirt、虛擬化網路Openvswitch。準備階段Step.1 選擇一臺具有足夠磁碟和記憶體資源的物理機DestHost,並在DestHost上建立VM對應的系統盤和資料盤

KVM學習筆記(二)--虛擬機器克隆

一、KVM建立虛擬機器 建立磁碟檔案及虛擬機器映象 [[email protected] images]# ll /var/lib/libvirt/images/ #kvm存放虛擬機器的路徑 [[email protected] image

Robot Operating System (ROS)學習筆記4---語音控制

sla 語音 出現 tput http 學習 process 輸入 ubun 搭建環境:XMWare Ubuntu14.04 ROS(indigo) 轉載自古月居 轉載連接:http://www.guyuehome.com/260 一、語音識別包 1、安裝

MySQL學習筆記(六)—— MySQL自連接

概念 cor 子查詢 ron 表操作 例子 質量 _id order by 有的時候我們需要對同一表中的數據進行多次檢索,這個時候我們可以使用之前學習過的子查詢,先查詢出需要的數據,再進行一次檢索。 例如:一張products表,有產品id,供應商id(vend_

jquery 深入學習筆記之中的一個 (事件綁定)

color 動態 name his pan mouseover this pre con 【jquery 事件綁定】 1、加入元素事件綁定 (1) 加入事件為當前元素 $(‘p‘).on(‘click‘,function(){ //code here ..

AngularJS入門學習筆記

rect directive 技術分享 attr 兩個 ava 內容 module 大括號 首先聲明: 本博客源自於學習:跟我學AngularJs:AngularJs入門及第一個實例。通過學習,我自己的一些學習筆記。 1.AngularJS的一些基本特性 (1)使用雙大括號

Python學習筆記-2017.5.4

列表 lin 覆蓋範圍 復習 處理 pytho 內部 global txt 本文章記錄學習過程中的細節和心得: 復習所學課程: 1、文件的操作:   打開文件,對文件的操作打開方式有兩種:   第一種:      f = open("test.txt", "r")#以只讀

SAS學習筆記之函數應用

不能 oracle 理解 資料 oracl 函數應用 特殊 put acl 今天在做數據需求的時候遇到一些問題,因為不能夠在數據庫裏面做,僅僅好在SAS裏面實現。這就遇到了一些麻煩,須要使用一些函數實現部分功能,如查找字段中某個特殊字符出現的次數,查找某個字符的位置等,

OpenCV2學習筆記(十五):利用Cmake高速查找OpenCV函數源代碼

one 生成 img log 分享 lan 學習筆記 全部 modules 在使用OpenCV時,在對一個函數的調用不是非常了解的情況下,通常希望查到該函數的官方聲明。而假設想進一步研究OpenCV的函數,則必須深入到源碼。在VS中我們能夠選中想要查

avalonjs 學習筆記1---checkbox

nod item ack lex server ini npm 學習 define 一、vscode 安裝使用 1.vs code+node.js下載安裝 2.在node.js command prompt 中運行 npm install -g live-server 3

Linux學習筆記(三):系統執行級與執行級的切換

查看 用戶操作 回車 water hat ntsysv tde 文件表 config 1.Linux系統與其它的操作系統不同,它設有執行級別。該執行級指定操作系統所處的狀態。Linux系統在不論什麽時候都執行於某個執行級上,且在不同的執行級上執行的程序和服務都不同,所要