深入解析Linux虛擬化KVM-Qemu分析之virtio設備
說明:
KVM版本:5.9.1
QEMU版本:5.0.0
工具:Source Insight 3.5, Visio
1. 概述
先來張圖:

圖中羅列了四個關鍵模塊:
Virtio Device
、Virtio Driver
、Virtqueue
、Notification(eventfd/irqfd)
;Virtio Driver
:前端部分,處理用戶請求,并將I/O請求轉(zhuǎn)移到后端;Virtio Device
:后端部分,由Qemu來實現(xiàn),接收前端的I/O請求,并通過物理設備進行I/O操作;Virtqueue
:中間層部分,用于數(shù)據(jù)的傳輸;Notification
:交互方式,用于異步事件的通知;
本文先從Qemu側的virtio device入手,我會選擇從一個實際的設備來闡述,沒錯,還是上篇文章中提到的網(wǎng)絡設備。
2. 流程分析
在Qemu的網(wǎng)卡虛擬化時,通常會創(chuàng)建一個虛擬網(wǎng)卡前端和虛擬網(wǎng)卡后端,如下圖:

在虛擬機創(chuàng)建的時候指定參數(shù):
-netdev tap, id = tap0, -device virtio-net-pci, netdev=tap0
;創(chuàng)建一個
Tap
網(wǎng)卡后端設備;創(chuàng)建一個
Virtio-Net
網(wǎng)卡前端設備;網(wǎng)卡前端設備和后端設備進行交互,最終與Host的驅(qū)動完成數(shù)據(jù)的收發(fā);
全文圍繞著Tap
設備的創(chuàng)建和Virtio-Net
設備的創(chuàng)建展開。
入口流程如下:

Qemu的代碼閱讀起來還是比較費勁的,各種盤根錯節(jié),里邊充斥著面向?qū)ο蟮乃枷?,先給自己挖個坑,后續(xù)會專題研究的,
this is for you, you have my words.
;圖中與本文相關的有三個模塊:1)模塊初始化;2)網(wǎng)絡設備初始化;3)設備初始化;
Qemu中設備模擬通過
type_init
先編譯進系統(tǒng),在module_call_init
時進行回調(diào),比如圖中的xxx_register_types
,在這些函數(shù)中都是根據(jù)TypeInfo
類型信息來創(chuàng)建具體的實現(xiàn);net_init_client
用來創(chuàng)建網(wǎng)絡設備,比如Tap
設備;device_init_func
根據(jù)Qemu命令的傳入?yún)?shù)創(chuàng)建虛擬設備,比如Virtio-Net
;
下邊進入細節(jié),the devil is in the details
。
【文章福利】小編推薦自己的Linux內(nèi)核技術交流群:【749907784】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)??


3. tap創(chuàng)建
從上文中,我們知道,Tap
與Virtio-Net
屬于前后端的關系,最終是通過結構體分別指向?qū)Ψ?,如下圖:

NetClientState
是網(wǎng)卡模擬的核心結構,表示網(wǎng)絡設備中的幾個端點,兩個端點通過peer
指向?qū)Ψ剑?/p>
創(chuàng)建Tap設備的主要工作就是創(chuàng)建一個NetClientState
結構,并添加到net_clients
鏈表中:

函數(shù)的調(diào)用細節(jié)如下圖:

處理流程只關注了核心的處理流程,整個過程有很多關于傳入?yún)?shù)的處理,選擇性忽略了;
net_tap_init
:與Host的tun
驅(qū)動進行交互,其實質(zhì)就是打開該設備文件,并進行相應的配置等;net_tap_fd_init
:根據(jù)net_tap_info
結構,創(chuàng)建NetClientState
,并進行相關設置,這里邊net_tap_info
結構體中的接收函數(shù)指針用于實際的數(shù)據(jù)傳輸處理;tap_read_poll
用于將fd添加到Qemu的AioContext中,用于異步響應,當有數(shù)據(jù)來臨時,捕獲事件并進行處理;
以上就是Tap后端的創(chuàng)建過程,下文將針對前端創(chuàng)建了。
4. virtio-net創(chuàng)建
這是一個復雜的流程。
4.1 數(shù)據(jù)結構
Qemu中用C語言實現(xiàn)了面向?qū)ο蟮哪P?,用于對設備進行抽象,精妙!
針對Virtio-Net設備,結構體及拓撲組織關系如下圖:

DeviceState
作為所有設備的父類,其中派生了VirtIODevice
和PCIDevice
,而本文研究的Virtio-Net
派生自VirtIODevice
;Qemu中會虛擬一個PCI總線,同時創(chuàng)建
virtio-net-pci
,virtio-balloon-pci
,virtio-scsi-pci
等PCI代理設備,這些代理設備掛載在PCI總線上,同時會創(chuàng)建Virtio總線,用于掛載最終的設備,比如VirtIONet
;PCI代理設備就是一個紐帶;
4.2 流程分析
與設備創(chuàng)建相關的三個函數(shù),可以從device_init_func
入口跟蹤得知:

當Qemu命令通過
-device
傳入?yún)?shù)時,device_init_func
會根據(jù)參數(shù)去查找設備,并最終調(diào)用到該設備對應的類初始化函數(shù)、對象初始化函數(shù)、以及realize函數(shù);所以,我們的分析就是這三個入口;
4.2.1 class_init

在網(wǎng)卡虛擬化過程中,參數(shù)只需要指定PCI代理設備即可,也就是
-device virtio-net-pci, netdev=tap0
,從而會調(diào)用到virtio_net_pci_class_init
函數(shù);由于實現(xiàn)了類的繼承關系,在子類初始化之前,需要先調(diào)用父類的實現(xiàn),圖中也表明了繼承關系以及調(diào)用函數(shù)順序;
C語言實現(xiàn)繼承,也就是將父對象放置在自己結構體的開始位置,圖中的顏色能看出來;
4.2.2 instance_init
類初始化結束后,開始對象的創(chuàng)建:

針對
Virtio-Net-PCI
的實例化比較簡單,作為代理,負責將它的后繼對象初始化,也就是本文的前端設備Virtio-Net
;
4.2.3 realize

realize
的調(diào)用,比較繞,簡單來說,它的類繼承關系中存在多個realize
的函數(shù)指針,最終會從父類開始執(zhí)行,一直調(diào)用到子類,而這些函數(shù)指針的初始化在什么時候做的呢?沒錯,就是在class_init類初始化的時候,進行了賦值,細節(jié)不表,結論可靠;最終的調(diào)用關系就如圖了;
到目前為止,我們似乎都還沒有看到Virtio-Net
設備的相關操作,不用著急,已經(jīng)很接近真相了:

virtio_net_pci_realize
函數(shù),會觸發(fā)virtio_device_realize
的調(diào)用,該函數(shù)是一個通用的virtio設備實現(xiàn)函數(shù),所有的virtio設備都會調(diào)用,而我們的前端設備Virtio-Net
也是virtio設備;virtio_net_device_realize
就到了我們的主角了,它進行了virtio通用的設置(后續(xù)在數(shù)據(jù)通信中再分析),還創(chuàng)建了一個NetClientState
端點,與Tap
設備對應,分別指向了對方,惺惺相惜,各自安好;virtio_bus_device_plugged
表示設備插入總線時的處理,完成的工作就是按照PCI總線規(guī)劃,配置各類信息,以便與Guest OS中的virtio驅(qū)動交互,后續(xù)的文章再分析了;
原文作者:LoyenWang
