KVM之Virtio介紹 (十五)

概而言之,virtio 是半虛擬化 hypervisor 中位於設備之上的抽象層。virtio 由 Rusty Russell 開發,他當時的目的是支持自己的虛擬化解決方案 lguest。本文在開篇時介紹半虛擬化和模擬設備,然後探索 virtio 的細節。本文的重點是來自 2.6.30 內核發行版的 virtio 框架。

Linux 是 hypervisor 展臺。如我的 剖析 Linux hypervisor 所述,Linux 提供各種 hypervisor 解決方案,這些解決方案都有自己的特點和優點。這些解決方案包括 Kernel-based Virtual Machine (KVM)、lguest 和 User-mode Linux 等。在 Linux 上配備這些不同的 hypervisor 解決方案會給操作系統帶來負擔,負擔的大小取決於各個解決方案的需求。其中的一項開銷爲設備的虛擬化。virtio 並沒有提供多種設備模擬機制(針對網絡、塊和其他驅動程序),而是爲這些設備模擬提供一個通用的前端,從而標準化接口和增加代碼的跨平臺重用。

完全虛擬化和半虛擬化

讓我們快速討論一下兩種類型完全不同的虛擬化模式:完全虛擬化和半虛擬化。在完全虛擬化 中,來賓操作系統運行在位於物理機器上的 hypervisor 之上。來賓操作系統並不知道它已被虛擬化,並且不需要任何更改就可以在該配置下工作。相反,在半虛擬化 中,來賓操作系統不僅知道它運行在 hypervisor 之上,還包含讓來賓操作系統更高效地過渡到 hypervisor 的代碼(見 圖 1)。

在完全虛擬化模式中,hypervisor 必須模擬設備硬件,它是在會話的最低級別進行模擬的(例如,網絡驅動程序)。儘管在該抽象中模擬很乾淨,但它同時也是最低效、最複雜的。在半虛擬化模式中,來賓操作系統和 hypervisor 能夠共同合作,讓模擬更加高效。半虛擬化方法的缺點是操作系統知道它被虛擬化,並且需要修改才能工作。

圖 1. 在完全虛擬化和半虛擬化環境下的設備模擬
在這裏插入圖片描述

硬件隨着虛擬化技術而不斷改變。新的處理器通過納入高級指令來讓來賓操作系統到 hypervisor 的過渡更加高效。此外,硬件也隨着輸入/輸出(I/O)虛擬化而不斷改變(參見 參考資料 瞭解 Peripheral Controller Interconnect [PCI] passthrough 和 single- and multi-root I/O 虛擬化)。

但是在傳統的完全虛擬化環境中,hypervisor 必須捕捉這些請求,然後模擬物理硬件的行爲。儘管這樣做提供很大的靈活性(即運行未更改的操作系統),但它的效率比較低(參見 圖 1 左邊)。圖 1 的右邊是半虛擬化示例。在這裏,來賓操作系統知道它運行在 hypervisor 之上,幷包含了充當前端的驅動程序。Hypervisor 爲特定的設備模擬實現後端驅動程序。通過在這些前端和後端驅動程序中的 virtio,爲開發模擬設備提供標準化接口,從而增加代碼的跨平臺重用率並提高效率。

針對 Linux 的抽象

從前面的小節可以看到,virtio 是對半虛擬化 hypervisor 中的一組通用模擬設備的抽象。該設置還允許 hypervisor 導出一組通用的模擬設備,並通過一個通用的應用編程接口(API)讓它們變得可用。圖 2 展示了爲什麼這很重要。有了半虛擬化 hypervisor 之後,來賓操作系統能夠實現一組通用的接口,在一組後端驅動程序之後採用特定的設備模擬。後端驅動程序不需要是通用的,因爲它們只實現前端所需的行爲。

圖 2. virtio 的驅動程序抽象
在這裏插入圖片描述

注意,在現實中(儘管不需要),設備模擬發生在使用 QEMU 的空間,因此後端驅動程序與 hypervisor 的用戶空間交互,以通過 QEMU 爲 I/O 提供便利。QEMU 是一個系統模擬器,它不僅提供來賓操作系統虛擬化平臺,還提供整個系統(PCI 主機控制器、磁盤、網絡、視頻硬件、USB 控制器和其他硬件元素)的模擬。

virtio API 依賴一個簡單的緩衝抽象來封裝來賓操作系統需要的命令和數據。讓我們查看 virtio API 的內部及其組件。

Virtio 架構

除了前端驅動程序(在來賓操作系統中實現)和後端驅動程序(在 hypervisor 中實現)之外,virtio 還定義了兩個層來支持來賓操作系統到 hypervisor 的通信。在頂級(稱爲 virtio)的是虛擬隊列接口,它在概念上將前端驅動程序附加到後端驅動程序。驅動程序可以使用 0 個或多個隊列,具體數量取決於需求。例如,virtio 網絡驅動程序使用兩個虛擬隊列(一個用於接收,另一個用於發送),而 virtio 塊驅動程序僅使用一個虛擬隊列。虛擬隊列實際上被實現爲跨越來賓操作系統和 hypervisor 的銜接點。但這可以通過任意方式實現,前提是來賓操作系統和 hypervisor 以相同的方式實現它。

圖 3. vital 框架的高級架構
在這裏插入圖片描述
如 圖 3 所示,分別爲塊設備(比如磁盤)、網絡設備、PCI 模擬和 balloon 驅動程序列出了 5 個前端驅動程序。每個前端驅動程序在 hypervisor 中有一個對應的後端驅動程序。

概念層次結構

從來賓操作系統的角度來看,對象層次結構 的定義如 圖 4 所示。在頂級的是 virtio_driver,它在來賓操作系統中表示前端驅動程序。與該驅動程序匹配的設備由 virtio_device(設備在來賓操作系統中的表示)封裝。這引用 virtio_config_ops 結構(它定義配置 virtio 設備的操作)。virtio_device 由 virtqueue 引用(它包含一個到它服務的 virtio_device 的引用)。最後,每個 virtqueue 對象引用 virtqueue_ops 對象,後者定義處理 hypervisor 的驅動程序的底層隊列操作。儘管隊列操作是 virtio API 的核心,我還是先簡單討論一下新的發現,然後再詳細探討 virtqueue_ops 操作。

圖 4. virtio 前端的對象層次結構
在這裏插入圖片描述
該流程以創建 virtio_driver 並通過 register_virtio_driver 進行註冊開始。virtio_driver 結構定義上層設備驅動程序、驅動程序支持的設備 ID 的列表、一個特性表單(取決於設備類型)和一個回調函數列表。當 hypervisor 識別到與設備列表中的設備 ID 相匹配的新設備時,將調用 probe 函數(由 virtio_driver 對象提供)來傳入 virtio_device 對象。將這個對象和設備的管理數據緩存起來(以獨立於驅動程序的方式緩存)。可能要調用 virtio_config_ops 函數來獲取或設置特定於設備的選項,例如,爲 virtio_blk 設備獲取磁盤的 Read/Write 狀態或設置塊設備的塊大小,具體情況取決於啓動器的類型。

注意,virtio_device 不包含到 virtqueue 的引用(但 virtqueue 確實引用了 virtio_device)。要識別與該 virtio_device 相關聯的 virtqueue,您需要結合使用 virtio_config_ops 對象和 find_vq 函數。該對象返回與這個 virtio_device 實例相關聯的虛擬隊列。find_vq 函數還允許爲 virtqueue 指定一個回調函數(查看 圖 4 中的 virtqueue 結構)。

virtqueue 是一個簡單的結構,它識別一個可選的回調函數(在 hypervisor 使用緩衝池時調用)、一個到 virtio_device 的引用、一個到 virtqueue 操作的引用,以及一個引用要使用的底層實現的特殊 priv 引用。雖然 callback 是可選的,但是它能夠動態地啓用或禁用回調。

該層次結構的核心是 virtqueue_ops,它定義在來賓操作系統和 hypervisor 之間移動命令和數據的方式。讓我們首先探索添加到或從 virtqueue 移除的對象。

Virtio 緩衝池

來賓操作系統(前端)驅動程序通過緩衝池與 hypervisor 交互。對於 I/O,來賓操作系統提供一個或多個表示請求的緩衝池。例如,您可以提供 3 個緩衝池,第一個表示 Read 請求,後面兩個表示響應數據。該配置在內部被表示爲一個散集列表(scatter-gather),列表中的每個條目表示一個地址和一個長度。

核心 API

通過 virtio_device 和 virtqueue(更常見)將來賓操作系統驅動程序與 hypervisor 的驅動程序鏈接起來。virtqueue 支持它自己的由 5 個函數組成的 API。您可以使用第一個函數 add_buf 來向 hypervisor 提供請求。如前面所述,該請求以散集列表的形式存在。對於 add_buf,來賓操作系統提供用於將請求添加到隊列的 virtqueue、散集列表(地址和長度數組)、用作輸出條目(目標是底層 hypervisor)的緩衝池數量,以及用作輸入條目(hypervisor 將爲它們儲存數據並返回到來賓操作系統)的緩衝池數量。當通過 add_buf 向 hypervisor 發出請求時,來賓操作系統能夠通過 kick 函數通知 hypervisor 新的請求。爲了獲得最佳的性能,來賓操作系統應該在通過 kick 發出通知之前將盡可能多的緩衝池裝載到 virtqueue。

通過 get_buf 函數觸發來自 hypervisor 的響應。來賓操作系統僅需調用該函數或通過提供的 virtqueue callback 函數等待通知就可以實現輪詢。當來賓操作系統知道緩衝區可用時,調用 get_buf 返回完成的緩衝區。

virtqueue API 的最後兩個函數是 enable_cb 和 disable_cb。您可以使用這兩個函數來啓用或禁用回調進程(通過在 virtqueue 中由 virtqueue 初始化的 callback 函數)。注意,該回調函數和 hypervisor 位於獨立的地址空間中,因此調用通過一個間接的 hypervisor 來觸發(比如 kvm_hypercall)。

緩衝區的格式、順序和內容僅對前端和後端驅動程序有意義。內部傳輸(當前實現中的連接點)僅移動緩衝區,並且不知道它們的內部表示。

示例 virtio 驅動程序

您可以在 Linux 內核的 ./drivers 子目錄內找到各種前端驅動程序的源代碼。可以在 ./drivers/net/virtio_net.c 中找到 virtio 網絡驅動程序,在 ./drivers/block/virtio_blk.c 中找到 virtio 塊驅動程序。子目錄 ./drivers/virtio 提供 virtio 接口的實現(virtio 設備、驅動程序、virtqueue 和連接點)。virtio 還應用在 High-Performance Computing (HPC) 研究中,以開發出通過共享內存傳遞的 inter-virtual machine (VM) 通信。尤其是,這是通過使用 virtio PCI 驅動程序的虛擬化 PCI 接口實現的。您可以在 參考資料 部分更多地瞭解這個知識點。

現在,您可以在 Linux 內核中實踐這個半虛擬化基礎架構。您所需的包括一個充當 hypervisor 的內核、一個來賓操作性內核和用於設備模擬的 QEMU。您可以使用 KVM(位於主機內核中的一個模塊)或 Rusty Russell 的 lguest(修改版的 Linux 來賓操作系統內核)。這兩個虛擬化解決方案都支持 virtio(以及用於系統模擬的 QEMU 和用於虛擬化管理的 libvirt)。

Rusty 的 lguest 是針對半虛擬化驅動程序和更快速地模擬虛擬設備的更簡潔代碼庫。但更重要的是,實踐證明 virtio 比現有的商業解決方案提供更出色的性能(網絡 I/O 能夠提升 2-3 倍)。性能的提升是需要付出代價的,但是如果您使用 Linux 作爲 hypervisor 和來賓操作系統,那麼這樣做是值得的。

結束語

也許您可能從來沒有爲 virtio 開發過前端或後端驅動程序,它實現了一個有趣的架構,值得您仔細去探索。virtio 爲提高半虛擬化 I/O 環境中的效率帶來了新的機會,同時能夠利用 Xen 以前的成果。Linux 不斷地證明它是一個產品 hypervisor,並且是新虛擬化技術研究平臺。virtio 這個例子展示了將 Linux 用作 hypervisor 的強大之處和開放性。

原文鏈接:https://www.ibm.com/developerworks/cn/linux/l-virtio/index.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章