從 VirtIO 到 vDPA:封包交給硬體之後,虛擬機如何熱遷移?
⚠️ 本文經由 AI 整理並撰寫。 假設你正在替一個虛擬化平台設計網路資料路徑。起初,讓 QEMU 處理虛擬網卡的 I/O 很直覺:裝置由軟體管理,狀態也容易掌握。但當封包率提高,Host CPU 花在描述符處理、通知與封包搬運上的成本,就會逐漸變得明顯。 把這些工作交給核心、專用的使用者空間程式,甚至網卡硬體,似乎是自然的下一步。然而,雲端平台不只需要「傳得快」,還需要在主機維護、資源調度時,把正在執行的虛擬機搬到另一台主機。當裝置可以繞過 QEMU,直接改寫 Guest 記憶體,QEMU 又該如何知道哪些資料必須重新傳送?到了交接那一刻,又由誰確認封包處理到哪裡? 這正是 VirtIO、vhost、vDPA 與 Shadow Virtqueue(SVQ)串在一起的原因:它們不只是不同的加速技術,更是在重新分配資料處理工作,以及對執行狀態的控制權。 本文以 Linux Host 上的 QEMU/KVM 與 virtio-net 為例,先從一個封包如何離開 Guest 談起,再沿著硬體卸載、髒頁追蹤與熱遷移,理解這些技術各自解決了什麼問題。具體功能與限制仍須對照實際的核心、QEMU、驅動及韌體版本。 一、先看一個封包:VirtIO 統一的是介面,不是後端 Guest 裡的應用程式送出封包後,作業系統最終會把工作交給網卡驅動。在 VirtIO 架構中,這個驅動不必理解某一家實體網卡的私有佇列格式,而是使用標準的 VirtIO 介面,透過 Virtqueue(VQ) 與裝置交換工作。 這裡容易產生第一個誤會:Virtqueue 並不是一個裝滿封包內容的環形儲存區。以 Split Virtqueue 為例,它主要包含三種結構: 結構 用途 Descriptor Table 描述 buffer 的位址、長度、存取方向與鏈結關係 Available Ring 告訴裝置有哪些描述符鏈可以處理 Used Ring 由裝置回報哪些描述符鏈已完成,以及完成長度 真正的封包內容稱為 payload,通常放在描述符指向的另一塊 Guest buffer。換句話說,Virtqueue 傳遞的是「去哪裡讀寫資料」以及「工作完成了沒有」,而不等同於資料本身。 具體而言,一個 Split descriptor 的欄位配置如下;這裡列的是欄位寬度,實際讀寫還必須遵守規範的位元組序與對齊要求: 欄位 大小 意義 addr 64 bits buffer 位址 len 32 bits buffer 長度 flags 16 bits 描述符方向、是否還有下一節,以及是否為間接描述符表 next 16 bits 描述符鏈中下一節的索引 四個欄位合計 16 bytes。VRING_DESC_F_WRITE 表示裝置可以寫入 buffer,而不是 Guest 可以寫入;TX payload 通常供裝置讀取,RX buffer 則供裝置寫入。VRING_DESC_F_NEXT 用於鏈結多個描述符,VRING_DESC_F_INDIRECT 則讓描述符指向另一張描述符表。一個封包可能跨多個 buffers,不能把「一個封包」等同於「一個 16 bytes 描述符」。 ...