VMBus¶
VMBus 是 Hyper-V 提供給客戶虛擬機器的一種軟體構造。 它由控制路徑和 Hyper-V 呈現給客戶虛擬機器的合成裝置使用的通用設施組成。 控制路徑用於向客戶虛擬機器提供合成裝置,並在某些情況下撤消這些裝置。 通用設施包括用於在客戶虛擬機器中的裝置驅動程式與 Hyper-V 的一部分的合成裝置實現之間進行通訊的軟體通道,以及允許 Hyper-V 和客戶機相互中斷的信令原語。
VMBus 在 Linux 中被建模為匯流排,期望在執行的 Linux 客戶機中存在 /sys/bus/vmbus 條目。 VMBus 驅動程式 (drivers/hv/vmbus_drv.c) 建立與 Hyper-V 主機的 VMBus 控制路徑,然後將自身註冊為 Linux 匯流排驅動程式。 它實現標準匯流排功能,用於將裝置新增到匯流排/從匯流排中刪除裝置。
Hyper-V 提供的大多數合成裝置都有相應的 Linux 裝置驅動程式。 這些裝置包括
SCSI 控制器
網絡卡
圖形幀緩衝區
鍵盤
滑鼠
PCI 裝置直通
心跳
時間同步
關機
記憶體氣球
與 Hyper-V 的鍵/值對 (KVP) 交換
Hyper-V 線上備份(又名 VSS)
客戶虛擬機器可以具有合成 SCSI 控制器、合成網絡卡和 PCI 直通裝置的多個例項。 其他合成裝置每個虛擬機器限制為單個例項。 上面未列出的是 Hyper-V 提供的一小部分合成裝置,這些裝置僅供 Windows 客戶機使用,Linux 沒有這些裝置的驅動程式。
Hyper-V 在描述合成裝置時使用術語“VSP”和“VSC”。 “VSP”是指實現特定合成裝置的 Hyper-V 程式碼,而“VSC”是指客戶虛擬機器中裝置的驅動程式。 例如,合成網絡卡的 Linux 驅動程式被稱為“netvsc”,合成 SCSI 控制器的 Linux 驅動程式被稱為“storvsc”。 這些驅動程式包含名稱如“storvsc_connect_to_vsp”的函式。
VMBus 通道¶
合成裝置的例項使用 VMBus 通道在 VSP 和 VSC 之間進行通訊。 通道是雙向的,用於傳遞訊息。 大多數合成裝置使用單個通道,但合成 SCSI 控制器和合成網絡卡可以使用多個通道來實現更高的效能和更大的並行性。
每個通道由兩個環形緩衝區組成。 這些是大學資料結構教科書中的經典環形緩衝區。 如果讀寫指標相等,則認為環形緩衝區為空,因此完整的環形緩衝區始終至少有一個位元組未使用。 “in”環形緩衝區用於從 Hyper-V 主機到客戶機的訊息,“out”環形緩衝區用於從客戶機到 Hyper-V 主機的訊息。 在 Linux 中,“in”和“out”指定是由客戶機端檢視的。 環形緩衝區是在客戶機和主機之間共享的記憶體,它們遵循標準範例,其中記憶體由客戶機分配,構成環形緩衝區的 GPA 列表被傳達給主機。 每個環形緩衝區由一個帶有讀寫索引和一些控制標誌的標頭頁(4 KB)組成,後面是實際環的記憶體。 環的大小由客戶機中的 VSC 確定,並且特定於每個合成裝置。 構成環的 GPA 列表透過 VMBus 控制路徑作為 GPA 描述符列表 (GPADL) 傳達給 Hyper-V 主機。 請參閱函式 vmbus_establish_gpadl()。
每個環形緩衝區被對映到三個部分的連續 Linux 核心虛擬空間中:1) 4 KB 標頭頁,2) 構成環本身的記憶體,3) 構成環本身的記憶體的第二次對映。 因為 (2) 和 (3) 在核心虛擬空間中是連續的,所以將資料複製到環形緩衝區和從環形緩衝區複製資料的程式碼不需要擔心環形緩衝區迴繞。 一旦複製操作完成,可能需要重置讀寫索引以指向第一次對映,但實際的資料複製不需要分成兩個部分。 這種方法還允許輕鬆地直接在環中訪問複雜的資料結構,而無需處理迴繞。
在頁面大小 > 4 KB 的 arm64 上,標頭頁仍然必須作為 4 KB 區域傳遞給 Hyper-V。 但是實際環的記憶體必須與 PAGE_SIZE 對齊,並且大小必須是 PAGE_SIZE 的倍數,以便可以進行重複對映技巧。 因此,標頭頁的一部分未使用,也不會傳達給 Hyper-V。 這種情況由 vmbus_establish_gpadl() 處理。
Hyper-V 對可以透過 GPADL 與主機共享的客戶機記憶體總量強制執行限制。 此限制確保惡意客戶機無法強制消耗過多的主機資源。 對於 Windows Server 2019 及更高版本,此限制約為 1280 MB。 對於 Windows Server 2019 之前的版本,該限制約為 384 MB。
VMBus 通道訊息¶
在 VMBus 通道中傳送的所有訊息都有一個標準標頭,其中包括訊息長度、訊息有效負載的偏移量、一些標誌和一個 transactionID。 標頭之後的訊息部分對於每個 VSP/VSC 對都是唯一的。
訊息遵循以下兩種模式之一
單向:任何一方傳送訊息,不期望響應訊息
請求/響應:一方(通常是客戶機)傳送訊息,並期望響應
transactionID(又名“requestID”)用於匹配請求和響應。 一些合成裝置允許同時有多個請求在進行中,因此客戶機在傳送請求時指定一個 transactionID。 Hyper-V 在匹配的響應中發回相同的 transactionID。
在 VSP 和 VSC 之間傳遞的訊息是控制訊息。 例如,從 storvsc 驅動程式傳送的訊息可能是“執行此 SCSI 命令”。 如果訊息還意味著客戶機和 Hyper-V 主機之間的一些資料傳輸,則要傳輸的實際資料可以嵌入在控制訊息中,也可以指定為單獨的資料緩衝區,Hyper-V 主機將作為 DMA 操作訪問該緩衝區。 前一種情況在資料大小較小且將資料複製到環形緩衝區和從環形緩衝區複製資料的成本最小的情況下使用。 例如,從 Hyper-V 主機到客戶機的時間同步訊息包含實際時間值。 當資料較大時,使用單獨的資料緩衝區。 在這種情況下,控制訊息包含描述資料緩衝區的 GPA 列表。 例如,storvsc 驅動程式使用此方法來指定要執行磁碟 I/O 的資料緩衝區。
存在三個函式用於傳送 VMBus 通道訊息
vmbus_sendpacket():僅控制訊息和帶有嵌入資料的訊息 -- 無 GPA
vmbus_sendpacket_pagebuffer():帶有 GPA 列表的訊息,用於標識要傳輸的資料。 每個 GPA 都有一個偏移量和長度,以便可以定位客戶機記憶體的多個不連續區域。
vmbus_sendpacket_mpb_desc():帶有 GPA 列表的訊息,用於標識要傳輸的資料。 單個偏移量和長度與 GPA 列表相關聯。 GPA 必須描述要定位的客戶機記憶體的單個邏輯區域。
歷史上,Linux 客戶機信任 Hyper-V 傳送格式良好且有效的訊息,並且合成裝置的 Linux 驅動程式沒有完全驗證訊息。 隨著完全加密客戶機記憶體並允許客戶機不信任虛擬機器監控程式(AMD SEV-SNP、Intel TDX)的處理器技術的引入,信任 Hyper-V 主機不再是有效的假設。 VMBus 合成裝置的驅動程式正在更新,以完全驗證從與 Hyper-V 共享的記憶體中讀取的任何值,其中包括來自 VMBus 裝置的訊息。 為了方便這種驗證,客戶機從“in”環形緩衝區讀取的訊息被複制到不與 Hyper-V 共享的臨時緩衝區。 驗證是在此臨時緩衝區中執行的,而沒有 Hyper-V 在訊息驗證後但在使用前惡意修改訊息的風險。
合成中斷控制器 (synic)¶
Hyper-V 為每個客戶機 CPU 提供一個合成中斷控制器,VMBus 使用它來進行主機-客戶機通訊。 雖然每個 synic 定義了 16 個合成中斷 (SINT),但 Linux 僅使用 16 箇中的一個 (VMBUS_MESSAGE_SINT)。 與 Hyper-V 主機和客戶機 CPU 之間的通訊相關的所有中斷都使用該 SINT。
SINT 被對映到單個每個 CPU 的架構中斷(即,一個 8 位 x86/x64 中斷向量,或一個 arm64 PPI INTID)。 因為客戶機中的每個 CPU 都有一個 synic 並且可能接收 VMBus 中斷,所以它們在 Linux 中最好被建模為每個 CPU 的中斷。 這種模型在 arm64 上執行良好,其中為 VMBUS_MESSAGE_SINT 分配了單個每個 CPU 的 Linux IRQ。 此 IRQ 在 /proc/interrupts 中顯示為一個標記為“Hyper-V VMbus”的 IRQ。 由於 x86/x64 缺乏對每個 CPU IRQ 的支援,因此在所有 CPU 上靜態分配一個 x86 中斷向量 (HYPERVISOR_CALLBACK_VECTOR),並顯式編碼為呼叫 vmbus_isr()。 在這種情況下,沒有 Linux IRQ,並且中斷在 /proc/interrupts 中的“HYP”行上以聚合方式可見。
synic 提供了將架構中斷多路分解為一個或多個邏輯中斷並將邏輯中斷路由到 Linux 中的正確 VMBus 處理程式的方法。 此多路分解由 vmbus_isr() 和訪問 synic 資料結構的相關函式完成。
synic 未在 Linux 中建模為 irq 晶片或 irq 域,並且多路分解的邏輯中斷不是 Linux IRQ。 因此,它們不會出現在 /proc/interrupts 或 /proc/irq 中。 其中一個邏輯中斷的 CPU 親和性透過 /sys/bus/vmbus 下的條目進行控制,如下所述。
VMBus 中斷¶
VMBus 提供了一種機制,當客戶機在環形緩衝區中排隊新訊息時,客戶機可以中斷主機。 主機期望客戶機僅在“out”環形緩衝區從空轉換為非空時才傳送中斷。 如果客戶機在其他時間傳送中斷,則主機認為此類中斷是不必要的。 如果客戶機發送過多不必要的中斷,主機可能會透過暫停其執行幾秒鐘來限制該客戶機,以防止拒絕服務攻擊。
類似地,當主機在 VMBus 控制路徑上傳送新訊息時,或者當由於主機插入新 VMBus 通道訊息而導致 VMBus 通道“in”環形緩衝區從空轉換為非空時,主機將透過 synic 中斷客戶機。 控制訊息流和每個 VMBus 通道“in”環形緩衝區是單獨的邏輯中斷,由 vmbus_isr() 多路分解。 它首先透過呼叫 vmbus_chan_sched() 來檢查通道中斷來進行多路分解,vmbus_chan_sched() 檢視 synic 點陣圖以確定哪些通道在此 CPU 上有掛起的中斷。 如果多個通道在此 CPU 上有掛起的中斷,則按順序處理這些通道。 處理完所有通道中斷後,vmbus_isr() 檢查並處理在 VMBus 控制路徑上接收到的任何訊息。
VMBus 通道將中斷的客戶機 CPU 由客戶機在建立通道時選擇,並告知主機該選擇。 VMBus 裝置大致分為兩類
“慢速”裝置,只需要一個 VMBus 通道。 裝置(如鍵盤、滑鼠、心跳和 timesync)生成的中斷相對較少。 它們的 VMBus 通道都分配為中斷 VMBUS_CONNECT_CPU,它始終是 CPU 0。
“高速”裝置,可以使用多個 VMBus 通道來實現更高的並行性和效能。 這些裝置包括合成 SCSI 控制器和合成網絡卡。 它們的 VMBus 通道中斷被分配給在 VM 中可用 CPU 中分散的 CPU,以便可以並行處理多個通道上的中斷。
VMBus 通道中斷到 CPU 的分配在函式 init_vp_index() 中完成。 此分配在正常的 Linux 中斷親和性機制之外完成,因此中斷既不是“非託管”中斷也不是“託管”中斷。
可以在 /sys/bus/vmbus/devices/<deviceGUID>/channels/<channelRelID>/cpu 中看到 VMBus 通道將中斷的 CPU。 在更高版本的 Hyper-V 上執行時,可以透過將新值寫入此 sysfs 條目來更改 CPU。 由於 VMBus 通道中斷不是 Linux IRQ,因此 /proc/interrupts 或 /proc/irq 中沒有與單個 VMBus 通道中斷對應的條目。
如果 Linux 客戶機中的線上 CPU 有分配給它的 VMBus 通道中斷,則不能將其離線。 從核心 v6.15 開始,任何此類中斷都會在離線時自動重新分配給其他 CPU。 “其他”CPU 由實現選擇,並且不進行負載平衡或以其他方式智慧確定。 如果 CPU 再次聯機,則先前分配給它的通道中斷不會移回。 因此,在多個 CPU 離線(可能再次聯機)之後,中斷到 CPU 的對映可能會被打亂且不是最佳的。 在這種情況下,必須手動重新建立最佳分配。 對於核心 v6.14 及更早版本,必須首先如上所述手動將任何衝突的通道中斷重新分配給另一個 CPU。 然後,當沒有通道中斷分配給 CPU 時,可以將其離線。
即使在分配給通道的 CPU 以外的 CPU 上收到中斷,VMBus 通道中斷處理程式碼也被設計為可以正常工作。 具體來說,該程式碼不使用基於 CPU 的互斥來實現正確性。 在正常操作中,Hyper-V 將中斷分配的 CPU。 但是,當透過 sysfs 更改分配給通道的 CPU 時,客戶機並不知道 Hyper-V 何時會進行轉換。 即使在 Hyper-V 開始中斷新 CPU 之前存在時間延遲,該程式碼也必須正常工作。 請參閱 target_cpu_store() 中的註釋。
VMBus 裝置建立/刪除¶
Hyper-V 和 Linux 客戶機有一個單獨的訊息傳遞路徑,用於合成裝置建立和刪除。 此路徑不使用 VMBus 通道。 請參閱 vmbus_post_msg() 和 vmbus_on_msg_dpc()。
第一步是客戶機連線到通用的 Hyper-V VMBus 機制。 作為建立此連線的一部分,客戶機和 Hyper-V 就他們將使用的 VMBus 協議版本達成一致。 此協商允許較新的 Linux 核心在較舊的 Hyper-V 版本上執行,反之亦然。
然後,客戶機告訴 Hyper-V“傳送提供”。 Hyper-V 為 VM 配置的每個合成裝置向客戶機發送提供訊息。 每個 VMBus 裝置型別都有一個稱為“類 ID”的固定 GUID,每個 VMBus 裝置例項也由一個 GUID 標識。 來自 Hyper-V 的提供訊息包含兩個 GUID,以(在 VM 中)唯一地標識裝置。 每個裝置例項都有一個提供訊息,因此具有兩個合成網絡卡的 VM 將收到兩個具有網絡卡類 ID 的提供訊息。 提供訊息的順序可能因啟動而異,並且不得假定在 Linux 程式碼中是一致的。 提供訊息也可能在 Linux 最初啟動後很久才到達,因為 Hyper-V 支援將裝置(如合成網絡卡)新增到正在執行的 VM。 vmbus_process_offer() 處理新的提供訊息,vmbus_process_offer() 間接呼叫 vmbus_add_channel_work()。
收到提供訊息後,客戶機根據類 ID 識別裝置型別,並呼叫正確的驅動程式來設定裝置。 驅動程式/裝置匹配使用標準的 Linux 機制執行。
裝置驅動程式探測函式開啟到相應 VSP 的主 VMBus 通道。 它為通道環形緩衝區分配客戶機記憶體,並透過向主機提供環形緩衝區記憶體的 GPA 列表來與 Hyper-V 主機共享環形緩衝區。 請參閱 vmbus_establish_gpadl()。
設定好環形緩衝區後,裝置驅動程式和 VSP 透過主通道交換設定訊息。 這些訊息可能包括協商要在 Linux VSC 和 Hyper-V 主機上的 VSP 之間使用的裝置協議版本。 設定訊息還可能包括建立額外的 VMBus 通道,這些通道被錯誤地命名為“子通道”,因為一旦建立它們,它們在功能上等同於主通道。
最後,裝置驅動程式可以像任何裝置驅動程式一樣在 /dev 中建立條目。
Hyper-V 主機可以向客戶機發送“撤消”訊息以刪除先前提供的裝置。 Linux 驅動程式必須隨時處理此類撤消訊息。 撤消裝置會呼叫裝置驅動程式“刪除”函式來乾淨地關閉裝置並將其刪除。 撤消合成裝置後,Hyper-V 和 Linux 都不保留關於其先前存在的任何狀態。 以後可能會重新新增此類裝置,在這種情況下,它將被視為一個全新的裝置。 請參閱 vmbus_onoffer_rescind()。
對於某些裝置(如 KVP 裝置),當主通道關閉時,Hyper-V 會自動傳送撤消訊息,這可能是由於將裝置從其驅動程式中解除繫結造成的。 撤消會導致 Linux 刪除裝置。 但是,Hyper-V 會立即重新向客戶機提供該裝置,導致在 Linux 中建立一個新的裝置例項。 對於其他裝置(如合成 SCSI 和網絡卡裝置),關閉主通道_不_會導致 Hyper-V 傳送撤消訊息。 裝置繼續存在於 Linux 的 VMBus 上,但沒有驅動程式繫結到它。 相同的驅動程式或新驅動程式隨後可以繫結到裝置的現有例項。