一文分析Linux虛擬化KVM-Qemu之virtqueue
說明:
KVM版本:5.9.1
QEMU版本:5.0.0
工具:Source Insight 3.5, Visio
1. 概述

前邊系列將Virtio Device和Virtio Driver都已經(jīng)講完,本文將分析virtqueue;
virtqueue用于前后端之間的數(shù)據(jù)交換,一看到這種數(shù)據(jù)隊(duì)列,首先想到的就是ring-buffer,實(shí)際的實(shí)現(xiàn)會(huì)是怎么樣的呢?
2. 數(shù)據(jù)結(jié)構(gòu)
先看一下核心的數(shù)據(jù)結(jié)構(gòu):

通常Virtio設(shè)備操作Virtqueue時(shí),都是通過
struct virtqueue
結(jié)構(gòu)體,這個(gè)可以理解成對外的一個(gè)接口,而Virtqueue
機(jī)制的實(shí)現(xiàn)依賴于struct vring_virtqueue
結(jié)構(gòu)體;Virtqueue
有三個(gè)核心的數(shù)據(jù)結(jié)構(gòu),由struct vring
負(fù)責(zé)組織:struct vring_desc
:描述符表,每一項(xiàng)描述符指向一片內(nèi)存,內(nèi)存類型可以分為out類型和in類型,分別代表輸出和輸入,而內(nèi)存的管理都由驅(qū)動(dòng)來負(fù)責(zé)。該結(jié)構(gòu)體中的next字段,可用于將多個(gè)描述符構(gòu)成一個(gè)描述符鏈,而flag字段用于描述屬性,比如只讀只寫等;struct vring_avail
:可用描述符區(qū)域,用于記錄設(shè)備可用的描述符ID,它的主體是數(shù)組ring,實(shí)際就是一個(gè)環(huán)形緩沖區(qū);struct vring_used
:已用描述符區(qū)域,用于記錄設(shè)備已經(jīng)處理完的描述符ID,同樣,它的ring數(shù)組也是環(huán)形緩沖區(qū),與struct vring_avail
不同的是,它還記錄了設(shè)備寫回的數(shù)據(jù)長度;
這么看,當(dāng)然是有點(diǎn)不太直觀,所以,下圖來了:

簡單來說,驅(qū)動(dòng)會(huì)分配好內(nèi)存(
scatterlist
),并通過virtqueue_add
添加到描述表中,這樣描述符表中的條目就都能對應(yīng)到具體的物理地址了,其實(shí)可以把它理解成一個(gè)資源池子;驅(qū)動(dòng)可以將可用的資源更新到
struct vring_avail
中,也就是將可用的描述符ID添加到ring數(shù)組中,熟悉環(huán)形緩沖區(qū)的同學(xué)應(yīng)該清楚它的機(jī)制,通過維護(hù)頭尾兩個(gè)指針來進(jìn)行管理,Driver負(fù)責(zé)更新頭指針(idx),Device負(fù)責(zé)更新尾指針(Qemu中的Device負(fù)責(zé)維護(hù)一個(gè)last_avail_idx),頭尾指針,你追我趕,生生不息;當(dāng)設(shè)備使用完了后,將已用的描述符ID更新到
struct vring_used
中,vring_virtqueue
自身維護(hù)了last_used_idx,機(jī)制與struct vring_avail
一致;
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)??


3. 流程分析
3.1 發(fā)送

當(dāng)驅(qū)動(dòng)需要把數(shù)據(jù)發(fā)送給設(shè)備時(shí),流程如上圖所示:
①A表示分配一個(gè)Buffer并添加到Virtqueue中,①B表示從Used隊(duì)列中獲取一個(gè)Buffer,這兩種中選擇一種方式;
②表示將Data拷貝到Buffer中,用于傳送;
③表示更新Avail隊(duì)列中的描述符索引值,注意,驅(qū)動(dòng)中需要執(zhí)行memory barrier操作,確保Device能看到正確的值;
④與⑤表示Driver通知Device來取數(shù)據(jù);
⑥表示Device從Avail隊(duì)列中獲取到描述符索引值;
⑦表示將描述符索引對應(yīng)的地址中的數(shù)據(jù)取出來;
⑧表示Device更新Used隊(duì)列中的描述符索引;
⑨與⑩表示Device通知Driver數(shù)據(jù)已經(jīng)取完了;
3.2 接收

當(dāng)驅(qū)動(dòng)從設(shè)備接收數(shù)據(jù)時(shí),流程如上圖所示:
①表示Device從Avail隊(duì)列中獲取可用描述符索引值;
②表示將數(shù)據(jù)拷貝至描述符索引對應(yīng)的地址上;
③表示更新Used隊(duì)列中的描述符索引值;
④與⑤表示Device通知Driver來取數(shù)據(jù);
⑥表示Driver從Used隊(duì)列中獲取已用描述符索引值;
⑦表示將描述符索引對應(yīng)地址中的數(shù)據(jù)取出來;
⑧表示將Avail隊(duì)列中的描述符索引值進(jìn)行更新;
⑨與⑩表示Driver通知Device有新的可用描述符;
3.3 代碼分析
代碼的分析將圍繞下邊這個(gè)圖來展開(Virtio-Net
),偷個(gè)懶,只分析單向數(shù)據(jù)發(fā)送了:

3.3.1 virtqueue創(chuàng)建

之前的系列文章分析過virtio設(shè)備和驅(qū)動(dòng),Virtio-Net是PCI網(wǎng)卡設(shè)備驅(qū)動(dòng),分別會(huì)在
virtnet-probe
和virtio_pci_probe
中完成所有的初始化;virtnet_probe
函數(shù)入口中,通過init_vqs
完成Virtqueue的初始化,這個(gè)逐級調(diào)用關(guān)系如圖所示,最終會(huì)調(diào)用到vring_create_virtqueue
來創(chuàng)建Virtqueue;這個(gè)創(chuàng)建的過程中,有些細(xì)節(jié)是忽略的,比如通過PCI去讀取設(shè)備的配置空間,獲取創(chuàng)建Virtqueue所需要的信息等;
最終就是圍繞
vring_virtqueue
數(shù)據(jù)結(jié)構(gòu)的初始化展開,其中vring數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配也都是在驅(qū)動(dòng)中完成,整個(gè)結(jié)構(gòu)體都由驅(qū)動(dòng)來管理與維護(hù);
3.3.2 virtio-net驅(qū)動(dòng)發(fā)送

網(wǎng)絡(luò)數(shù)據(jù)的傳輸在驅(qū)動(dòng)中通過
start_xmit
函數(shù)來實(shí)現(xiàn);xmit_skb
函數(shù)中,sg_init_table
初始化sg列表,sg_set_buf
將sg指向特定的buffer,skb_to_sgvec
將socket buffer中的數(shù)據(jù)填充sg;通過
virtqueue_add_outbuf
將sg添加到Virtqueue中,并更新Avail隊(duì)列中描述符的索引值;virtqueue_notify
通知Device,可以過來取數(shù)據(jù)了;
3.3.3 Qemu virtio-net設(shè)備接收

Guest驅(qū)動(dòng)寫寄存器操作時(shí),陷入到KVM中,最終Qemu會(huì)捕獲到進(jìn)行處理,入口函數(shù)為
kvm_handle_io
;Qemu中會(huì)針對IO內(nèi)存區(qū)域設(shè)置讀寫的操作函數(shù),當(dāng)Guest進(jìn)行IO操作時(shí),最終觸發(fā)操作函數(shù)的調(diào)用,針對Virtio-Net,由于它是PCI設(shè)備,操作函數(shù)為
virtio_pci_config_write
;virtio_pci_config_write
函數(shù)中,對Guest的寫操作進(jìn)行判斷并處理,比如在VIRTIO_PCI_QUEUE_NOTIFY
時(shí),調(diào)用virtio_queue_notify
,用于處理Guest驅(qū)動(dòng)的通知,并最終回調(diào)handle_output
函數(shù);針對Virtio-Net設(shè)備,發(fā)送的回調(diào)函數(shù)為
virtio_net_handle_tx_bh
,并在virtio_net_flush_tx
中完成操作;通用的操作模型:通過
virtqueue_pop
從Avail隊(duì)列中獲取地址,將數(shù)據(jù)進(jìn)行處理,通過virtqueue_push
將處理完后的描述符索引更新到Used隊(duì)列中,通過virtio_notify
通知Guest驅(qū)動(dòng);
Virtqueue這種設(shè)計(jì)思想比較巧妙,不僅用在virtio中,在AMP系統(tǒng)中處理器之間的通信也能看到它的身影。
原文作者:LoyenWang
