天天看點

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

kvm 虛拟化原理探究(5)— 網絡io虛拟化

标簽(空格分隔): kvm

前面的文章介紹了kvm的啟動過程,cpu虛拟化,記憶體虛拟化原理。作為一個完整的風諾依曼計算機系統,必然有輸入計算輸出這個步驟。傳統的io包括了網絡裝置io,塊裝置io,字元裝置io等等,在kvm虛拟化原理探究裡面,我們最主要介紹網絡裝置io和塊裝置io,其實他們的原理都很像,但是在虛拟化層又分化開了,這也是為什麼網絡裝置io虛拟化和塊裝置io虛拟化要分開講的原因。這一章介紹一下網絡裝置io虛拟化,下一章介紹塊裝置io虛拟化。

這裡的傳統并不是真的傳統,而是介紹一下在非虛拟化環境下的網絡裝置io流程。我們平常所使用的linux版本,比如debian或者centos等都是标準的linux tcp/ip協定棧,協定棧底層提供了driver抽象層來适配不同的網卡,在虛拟化中最重要的是裝置的虛拟化,但是了解整個網絡io流程後去看待虛拟化就會更加容易了解了。

在使用者層,我們通過socket與kernel做互動,包括建立端口,資料的接收發送等操作。

在kernel層,tcp/ip協定棧負責将我們的socket資料封裝到tcp或者udp包中,然後進入ip層,加入ip位址端口資訊等,進入資料鍊路層,加入mac位址等資訊後,通過驅動寫入到網卡,網卡再把資料發送出去。如下圖所示,比較主觀的圖。

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

在linux的tcp/ip協定棧中,每個資料包是有核心的skb_buff結構描述的,如下圖所示,socket發送資料包的時候後,進入核心,核心從skb_buff的池中配置設定一個skb_buff用來承載資料流量。

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

當資料到了鍊路層,鍊路層做好相應的鍊路層頭部封裝後,調用驅動層适配層的發送接口 dev_queue_xmit,最終調用到 net_start_xmit 接口。

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

發送資料和接收資料驅動層都采用dma模式,驅動加載時候會為網卡映射記憶體并設定描述狀态(寄存器中),也就是記憶體的起始位,長度,剩餘大小等等。發送時候将資料放到映射的記憶體中,然後設定網卡寄存器産生一個中斷,告訴網卡有資料,網卡收到中斷後處理對應的記憶體中的資料,處理完後向cpu産生一個中斷告訴cpu資料發送完成,cpu中斷處理過程中向上層driver通知資料發送完成,driver再依次向上層傳回。在這個過程中對于driver來說,發送是同步的。接收資料的流程和發送資料幾乎一緻,這裡就不細說了。dma的模式對後面的io虛拟化來說很重要。

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

準确來說,kvm隻提供了一些基本的cpu和記憶體的虛拟化方案,真正的io實作都由qemu-kvm來完成,隻不過我們在介紹kvm的文章裡都預設qemu-kvm和kvm為一個體系,就沒有分的那麼仔細了。實際上網絡io虛拟化都是由qemu-kvm來完成的。

還記得我們第一章節的demo裡面,我們的“鏡像”調用了 out 指令産生了一個io操作,然後因為此操作為敏感的裝置通路類型的操作,不能在vmx non-root 模式下執行,于是vm exits,模拟器接管了這個io操作。

虛拟機退出并得知原因為 kvm_exit_io,模拟器得知由于裝置産生了io操作并退出,于是擷取這個io操作并列印出資料。這裡其實我們就最小化的模拟了一個虛拟io的過程,由模拟器接管這個io。

在qemu-kvm全虛拟化的io過程中,其實原理也是一樣,kvm捕獲io中斷,由qemu-kvm接管這個io,由于采用了dma映射,qemu-kvm在啟動時候會注冊裝置的mmio資訊,以便能擷取到dma裝置的映射記憶體和控制資訊。

對于pci裝置來說,當裝置與cpu之間通過映射了一段連續的實體記憶體後,cpu對pci裝置的通路隻需要像通路記憶體一樣通路既可以。io裝置通常有兩種模式,一種是port模式,一種是mmio模式,前者就是我們demo裡面的in/out指令,後者就是pci裝置的dma通路方式,兩種方式的操作都能被kvm捕獲。

于是qemu-kvm将此操作代替guest完成後并執行相應的“回調”,也就是向vcpu産生中斷告訴io完成并傳回guest繼續執行。vcpu中斷和cpu中斷一樣,設定相應的寄存器後中斷便會觸發。

在全虛拟化環境下,guest中的io都由qemu-kvm接管,在guest中看到的一個網卡裝置并不是真正的一塊網卡,而是由實體機産生的一個tap裝置。知識在驅動注冊的時候将一些tap裝置所支援的特性加入到了guest的驅動注冊資訊裡面,是以在guest中看到有網絡裝置。

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

如上圖所示,qemu接管了來自guest的io操作,真實的場景肯定是需要将資料再次發送出去的,而不是像demo一樣列印出來,在guest中的資料包二層封裝的mac位址後,qemu層不需要對資料進行拆開再解析,而隻需要将資料寫入到tap裝置,tap裝置和bridge之間互動完成後,由bridge直接發送到網卡,bridge(其實nic綁定到了bridge)開啟了混雜模式,可以将所有請求都接收或者發送出去。

以下來自這篇文章的引用

當一個 tap 裝置被建立時,在 linux 裝置檔案目錄下将會生成一個對應 char 裝置,使用者程式可以像打開普通檔案一樣打開這個檔案進行讀寫。當執行 write()操作時,資料進入 tap 裝置,此時對于 linux 網絡層來說,相當于 tap 裝置收到了一包資料,請求核心接受它,如同普通的實體網卡從外界收到一包資料一樣,不同的是其實資料來自 linux 上的一個使用者程式。linux 收到此資料後将根據網絡配置進行後續處理,進而完成了使用者程式向 linux 核心網絡層注入資料的功能。當使用者程式執行 read()請求時,相當于向核心查詢 tap 裝置上是否有需要被發送出去的資料,有的話取出到使用者程式裡,完成 tap 裝置的發送資料功能。針對 tap 裝置的一個形象的比喻是:使用 tap 裝置的應用程式相當于另外一台計算機,tap 裝置是本機的一個網卡,他們之間互相連接配接。應用程式通過 read()/write()操作,和本機網絡核心進行通訊。

類似這樣的操作

bridge可能是一個linux bridge,也可能是一個ovs(open virtual switch),在涉及到網絡虛拟化的時候,通常需要利用到bridge提供的vlan tag功能。

以上就是kvm的網絡全虛拟化io流程了,我們也可以看到這個流程的不足,比如說當網絡流量很大的時候,會産生過多的vm的切換,同時産生過多的資料copy操作,我們知道copy是很浪費cpu時鐘周期的。于是qemu-kvm在發展的過程中,實作了virtio驅動。

基于 virtio 的虛拟化也叫作半虛拟化,因為要求在guest中加入virtio驅動,也就意味着guest知道了自己運作于虛拟環境了。

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

不同于全虛拟化的方式,virtio通過在guest的driver層引入了兩個隊列和相應的隊列就緒描述符與qemu-kvm層virtio backend進行通信,并用檔案描述符來替代之前的中斷。

virtio front-end與backend之間通過vring buffer互動,在qemu中,使用事件循環機制來描述buffer的狀态,這樣當buffer中有資料的時候,qemu-kvm會監聽到eventfd的事件就緒,于是就可以讀取資料後發送到tap裝置,當有資料從tap裝置過來的時候,qemu将資料寫入到buffer,并設定eventfd,這樣front-end監聽到事件就緒後從buffer中讀取資料。

可以看到virtio在全虛拟化的基礎上做了改動,降低了guest exit和entry的開銷,同時利用eventfd來建立控制替代硬體中斷面膜是,一定程度上改善了網絡io的性能。

不過從整體流程上來看,virtio還是存在過多的記憶體拷貝,比如qemu-kvm從vring buffer中拷貝資料後發送到tap裝置,這個過程需要經過使用者态到核心态的拷貝,加上一系列的系統調用,是以在流程上還可以繼續完善,于是出現了核心态的virtio,被稱作vhost-net。

我們用一張圖來對比一下virtio與vhost-net,圖檔來自redhat官網。

[原] KVM 虛拟化原理探究(5)— 網絡IO虛拟化

vhost-net 繞過了 qemu 直接在guest的front-end和backend之間通信,減少了資料的拷貝,特别是減少了使用者态到核心态的拷貝。性能得到大大加強,就吞吐量來說,vhost-net基本能夠跑滿一台實體機的帶寬。

vhost-net需要核心支援,redhat 6.1 後開始支援,預設狀态下是開啟的。

kvm的網絡裝置io虛拟化經過了全虛拟化->virtio->vhost-net的進化,性能越來越接近真實實體網卡,但是在小包處理方面任然存在差距,不過已經不是一個系統的瓶頸了,可以看到kvm在經過了這多年的發展後,性能也是越發的強勁,這也是他領先于其他虛拟化的重要原因之一。

在本章介紹了io虛拟化後,下一章介紹塊裝置的虛拟化,塊裝置虛拟化同樣利用了dma的特性。

文章屬原創,轉載請注明出處 

繼續閱讀