1. 程式人生 > >【原創】Linux虛擬化KVM-Qemu分析(十一)之virtqueue

【原創】Linux虛擬化KVM-Qemu分析(十一)之virtqueue

# 背景 - `Read the fucking source code!` --By 魯迅 - `A picture is worth a thousand words.` --By 高爾基 說明: 1. KVM版本:5.9.1 2. QEMU版本:5.0.0 3. 工具:Source Insight 3.5, Visio 4. 文章同步在部落格園:`https://www.cnblogs.com/LoyenWang/` # 1. 概述 汪汪汪,最近忙成狗了,一下子把我更新的節奏打亂了,草率的道個歉。 ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173659872-275187796.png) - 前邊系列將Virtio Device和Virtio Driver都已經講完,本文將分析virtqueue; - virtqueue用於前後端之間的資料交換,一看到這種資料佇列,首先想到的就是ring-buffer,實際的實現會是怎麼樣的呢? # 2. 資料結構 先看一下核心的資料結構: ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173709126-884834927.png) - 通常Virtio裝置操作Virtqueue時,都是通過`struct virtqueue`結構體,這個可以理解成對外的一個介面,而`Virtqueue`機制的實現依賴於`struct vring_virtqueue`結構體; - `Virtqueue`有三個核心的資料結構,由`struct vring`負責組織: 1. `struct vring_desc`:描述符表,每一項描述符指向一片記憶體,記憶體型別可以分為out型別和in型別,分別代表輸出和輸入,而記憶體的管理都由驅動來負責。該結構體中的next欄位,可用於將多個描述符構成一個描述符鏈,而flag欄位用於描述屬性,比如只讀只寫等; 2. `struct vring_avail`:可用描述符區域,用於記錄裝置可用的描述符ID,它的主體是陣列ring,實際就是一個環形緩衝區; 3. `struct vring_used`:已用描述符區域,用於記錄裝置已經處理完的描述符ID,同樣,它的ring陣列也是環形緩衝區,與`struct vring_avail`不同的是,它還記錄了裝置寫回的資料長度; 這麼看,當然是有點不太直觀,所以,下圖來了: ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173718528-1016668684.png) - 簡單來說,驅動會分配好記憶體(`scatterlist`),並通過`virtqueue_add`新增到描述表中,這樣描述符表中的條目就都能對應到具體的實體地址了,其實可以把它理解成一個資源池子; - 驅動可以將可用的資源更新到`struct vring_avail`中,也就是將可用的描述符ID新增到ring陣列中,熟悉環形緩衝區的同學應該清楚它的機制,通過維護頭尾兩個指標來進行管理,Driver負責更新頭指標(idx),Device負責更新尾指標(Qemu中的Device負責維護一個last_avail_idx),頭尾指標,你追我趕,生生不息; - 當裝置使用完了後,將已用的描述符ID更新到`struct vring_used`中,`vring_virtqueue`自身維護了last_used_idx,機制與`struct vring_avail`一致; # 3. 流程分析 ## 3.1 傳送 ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173735876-1164554235.png) 當驅動需要把資料傳送給裝置時,流程如上圖所示: 1. ①A表示分配一個Buffer並新增到Virtqueue中,①B表示從Used佇列中獲取一個Buffer,這兩種中選擇一種方式; 2. ②表示將Data拷貝到Buffer中,用於傳送; 3. ③表示更新Avail佇列中的描述符索引值,注意,驅動中需要執行memory barrier操作,確保Device能看到正確的值; 4. ④與⑤表示Driver通知Device來取資料; 5. ⑥表示Device從Avail佇列中獲取到描述符索引值; 6. ⑦表示將描述符索引對應的地址中的資料取出來; 7. ⑧表示Device更新Used佇列中的描述符索引; 7. ⑨與⑩表示Device通知Driver資料已經取完了; ## 3.2 接收 ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173744024-718252802.png) 當驅動從裝置接收資料時,流程如上圖所示: 1. ①表示Device從Avail佇列中獲取可用描述符索引值; 2. ②表示將資料拷貝至描述符索引對應的地址上; 3. ③表示更新Used佇列中的描述符索引值; 4. ④與⑤表示Device通知Driver來取資料; 5. ⑥表示Driver從Used佇列中獲取已用描述符索引值; 6. ⑦表示將描述符索引對應地址中的資料取出來; 7. ⑧表示將Avail佇列中的描述符索引值進行更新; 8. ⑨與⑩表示Driver通知Device有新的可用描述符; ## 3.3 程式碼分析 程式碼的分析將圍繞下邊這個圖來展開(`Virtio-Net`),偷個懶,只分析單向資料傳送了: ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173757944-2086503669.png) ### 3.3.1 virtqueue建立 ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173804452-1571678857.png) - 之前的系列文章分析過virtio裝置和驅動,Virtio-Net是PCI網絡卡裝置驅動,分別會在`virtnet-probe`和`virtio_pci_probe`中完成所有的初始化; - `virtnet_probe`函式入口中,通過`init_vqs`完成Virtqueue的初始化,這個逐級呼叫關係如圖所示,最終會呼叫到`vring_create_virtqueue`來建立Virtqueue; - 這個建立的過程中,有些細節是忽略的,比如通過PCI去讀取裝置的配置空間,獲取建立Virtqueue所需要的資訊等; - 最終就是圍繞`vring_virtqueue`資料結構的初始化展開,其中vring資料結構的記憶體分配也都是在驅動中完成,整個結構體都由驅動來管理與維護; ### 3.3.2 virtio-net驅動傳送 ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173812553-1572434645.png) - 網路資料的傳輸在驅動中通過`start_xmit`函式來實現; - `xmit_skb`函式中,`sg_init_table`初始化sg列表,`sg_set_buf`將sg指向特定的buffer,`skb_to_sgvec`將socket buffer中的資料填充sg; - 通過`virtqueue_add_outbuf`將sg新增到Virtqueue中,並更新Avail佇列中描述符的索引值; - `virtqueue_notify`通知Device,可以過來取資料了; ### 3.3.3 Qemu virtio-net裝置接收 ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173819450-917504788.png) - Guest驅動寫暫存器操作時,陷入到KVM中,最終Qemu會捕獲到進行處理,入口函式為`kvm_handle_io`; - Qemu中會針對IO記憶體區域設定讀寫的操作函式,當Guest進行IO操作時,最終觸發操作函式的呼叫,針對Virtio-Net,由於它是PCI裝置,操作函式為`virtio_pci_config_write`; - `virtio_pci_config_write`函式中,對Guest的寫操作進行判斷並處理,比如在`VIRTIO_PCI_QUEUE_NOTIFY`時,呼叫`virtio_queue_notify`,用於處理Guest驅動的通知,並最終回撥`handle_output`函式; - 針對Virtio-Net裝置,傳送的回撥函式為`virtio_net_handle_tx_bh`,並在`virtio_net_flush_tx`中完成操作; - 通用的操作模型:通過`virtqueue_pop`從Avail佇列中獲取地址,將資料進行處理,通過`virtqueue_push`將處理完後的描述符索引更新到Used佇列中,通過`virtio_notify`通知Guest驅動; Virtqueue這種設計思想比較巧妙,不僅用在virtio中,在AMP系統中處理器之間的通訊也能看到它的身影。 草草收場了,下回見。 # 參考 `https://www.redhat.com/en/blog/virtqueues-and-virtio-ring-how-data-travels` `Virtual I/O Device Version 1.1` 歡迎關注個人公眾號,不定期更新技術文章。 ![](https://img2020.cnblogs.com/blog/1771657/202103/1771657-20210328173930969-7149102