virtio 是一種 I/O 半虛擬化解決方案,ovs是一個虛擬交換機,利用軟件的方式實現交換功能。本文將對virtio+ovs的轉發原理進行介紹和並對其性能展開分析。
1、 virtio和ovs介紹
傳統數據中心的硬件服務器上運行着linux,linux使用硬件網卡收發包,硬件網卡連接的硬件交換機進行包轉發實現服務器之間的互通。硬件網卡有broadcom、mellanox和intel等各種品牌,硬件交換機有H3C、cisco等品牌。在雲計算環境下,對計算資源進行了切分,服務器上運行的是一個個虛擬機,虛擬機也要有網卡實現互連互通,但虛擬機的網卡不是物理的,它通過虛擬的網卡連接到虛擬的交換機上,虛擬的交換機對同一個服務器上的虛擬機之間流量進行轉發,如果虛擬交換機再連接到服務器的硬件網卡,那么虛擬機就可以和服務器外面通信了。
硬件網卡收包時,CPU先分配內存,然后告訴硬件網卡內存的地址,報文從硬件交換機出來后進入硬件網卡的隊列,硬件網卡通過DMA功能把包從物理網卡搬運到內存中。然后中斷CPU說報文收上來了,CPU處理中斷,軟中斷執行內核協議棧處理,最后通知應用程序收包。
虛擬網卡是CPU模擬出來的,它的隊列也是模擬出來的,就是服務器上的一塊內存。要模擬DMA功能就得進行一次特殊的內存拷貝,從服務器上拷貝到虛擬機里,虛擬機運行在服務器上,用的也是服務器上的物理內存,相當於服務器上物理內存之間的拷貝,只是地址轉換比較復雜。
應用程序調用發包時,內核協議棧模塊分配一塊內存,把用戶態要發的內容拷貝到內核。然后經過復雜的協議處理,最后地址告訴硬件網卡說我要發包,你發完了告訴我一聲,硬件網卡的DMA就把要發的數據從內存中搬運到物理網卡的隊列中,然后告訴CPU說發完了,你可以回收內存了。
虛擬網卡發包和物理網卡發包類似,包從虛擬機中搬運到物理服務器內存中,然后經過軟件交換機,最后從物理網卡出去。虛擬網卡有e1000、virtio等,為什么雲計算環境最終選擇了virtio?首先virtio提供了一種虛擬機和物理服務器數據交換的通用機制,虛擬網卡、虛擬硬盤、虛擬顯卡和虛擬串口等都可以用。其次virtio還分frontend和backend,frontend運行在虛擬機中,backend運行在物理服務器上,frontend和backend配合實現具體的網絡功能,frontend和backend之間用virtio通用機制交換數據,設計真是十分巧妙。因此virtio得到了大多數hypervisor的支持,成了虛擬化事實上的標准。
硬件交換機是通過學習MAC進行二層轉發的,使用STP協議防止環路出現,為了減小arp這樣的廣播風暴又增加了vlan隔離。軟件交換機有linux bridge、vpp和ovs等。為什么雲計算環境中大多使用ovs?因為雲計算是最自然的SDN應用場景,不再需要復雜的控制面協議,如STP。另外雲計算要求靈活,vpp pipeline和linux bridge設計時考慮更多的是二三層轉發原理和轉發性能,要加入新功能異常艱難,而ovs采用了openflow pipeline,多table和多group靈活跳轉。pipeline設計考慮更多的是功能實現,性能方面由datapath支撐,並且datapath upstream到kernel中順手就可使用,所以選擇ovs。
2、vhost-net
這張圖來自於redhat的博客,博客地址在參考文獻中,非常完美的一張圖,就沒必要重復造輪子了。操作硬件網卡就是讀寫硬件網卡的寄存器,現在的外設都是PCIE外設,有配置空間和BAR空間。配置空間是PCIE標准定義,BAR空間硬件自己定義,讀寫寄存器就會觸發硬件相應的操作,比如發送包,會構造一個ring,每個ring指向skb,然后把ring的頭寫到一個寄存器,再寫一下觸發發包的寄存器,硬件就會讀取ring,把ring指向的skb都從內存搬運到網卡上去。
虛擬機也有模擬的主板芯片和PCIE總線,virtio-net是PCIE總線上的一個外設。在這張圖中frontend就是guest kernel中的virtio-net driver,而backend是qemu模擬的virtio-net device。virtio-net driver讀寫寄存器,qemu就要把原來硬件干的活模擬一遍。因為虛擬網卡寄存器是不存在的,kvm就把一塊內存做特殊標志當作寄存器,virtio-net driver一寫這塊內存,cpu就從guest中exit出來,停止執行guest,開始執行qemu代碼模擬guest觸發的動作。qemu把外設模擬分為兩種:控制面模擬和數據面模擬。控制面模擬有feature協商,vring地址交換等;數據面模擬有數據搬運和消息傳遞,其中消息傳遞就是guest通知vhost-net數據准備好了,我要發送,vhost-net發送完后告訴guest我幫你發送完了。在這張圖中qemu把數據面模擬的工作交給了內核的vhost-net模擬來完成。
上面說虛擬網卡模擬DMA是內存拷貝,既然是內存拷貝那干脆共享內存就不用拷貝了。vhost-net就是這么干的,不管是內存拷貝還是內存共享都要進行內存地址換算。guest中的virtio-net driver設置的都是guest physical address,而vhost-net只知道host virtual address。但別忘記guest的內存是qemu分配的,qemu知道換算關系,可以通過vhost protoco告訴vhost-net模塊。剩下就是消息傳遞了,kvm給qemu提供了api,qemu創建出兩個eventfd傳遞給kvm,並通知guest,寫一個fd,kvm讀這個fd,再把中斷注入guest中,這個方向的通知叫做call。guest通知qemu,並寫寄存器,kvm攔截后寫另一個fd,qemu讀這個fd,這個方向的通知叫做kick。qemu干脆一狠心,把這兩個fd的讀寫都交給vhost-net來負責。
-
guest發包流程
guest在內核中分配skb,把地址寫到vring中,kick kvm,kvm再通知vhost。vhost是內核線程,通過地址換算拿到skb並復制,通知guest發送完成。guest回收sbk,vhost繼續給skb找netdev,加入到一個cpu的backlog,觸發softirq。這個cpu的softirq發現netdev綁定在ovs橋上,查ovs流表找到出接口,調用物理網卡的驅動發送出去,物理網卡發送完成,把skb釋放。
-
guest收包流程
guest中的virtio-net driver分配skb,設置到vring上,物理網卡驅動分配skb設置給網卡,網卡DMA,中斷觸發,在softirq發現物理網卡綁在橋上,查ovs流表找到虛擬機的tap口,把skb放入tap的隊列中,叫醒vhost worker,vhost worker醒來一看skb到了tap隊列中,把skb拷貝到guest分配的skb上,通知kvm包來了(不需要qemu),kvm再中斷guest。
3、vhost-user
這張圖也來自於redhat的博客,原圖guest中運行的是virtio-net-pmd和vIOMMU,考慮到guest中的難度,我把圖做了一點修改。用戶在guest中搞hugepage和dpdk pmd難度很大,而且目前沒有配套的成熟用戶態協議棧。假設guest是最普通的guest,與vhost-net相同。這種模式ovs不再用內核的datapath,物理網卡綁定了DPDK,直接把包DMA到用戶態ovs,ovs進程和qemu進程共享內存把包傳遞到qemu進程中,qemu進程地址換算一下包就到了guest。virtio-net控制面模擬還在qemu中,原來給vhost-net下配置的vhost protocol變成了vhost-user protocol把配置交給了ovs-dpdk進程,qemu和ovs-dpdk之間建立了unix domain socket。這個socket的神奇之處在於不僅能傳遞virtio配置信息,還能傳遞qemu和kvm之間通信的kick/call fd。qemu把virtio-net數據面模擬交給了ovs-dpdk進程,消息通道還是靠kvm。guest中一直poll不現實,但ovs-dpdk從guest中拿包時就可以一直poll。
virtio full offload
這張圖也來自於redhat博客,根據我自己的理解修改一把,保持guest不變,用戶在物理機上怎么部署業務,在虛擬機中也怎么部署業務,不能讓用戶感覺到不習慣或者不舒服。整體原理就是把virtio backend都由硬件實現了,然后用passthrough功能,和普通的物理網卡passthrough一模一樣,只是這塊卡實現了virtio標准。passthrough和dpdk都用了vfio-pci,原理一樣,把物理網卡的pcie配置空間映射到qemu進程或者ovs-dpdk進程空間中,ovs-dpdk就直接讀寫,但qemu還得再地址轉換一下給guest,guest中的virtio-net driver就可以直接讀寫了,這樣virtio kick就好搞了,但call還是經過vfio-pci來通知kvm,kvm再中斷注入guest中。數據搬運就是硬件直接DMA到qemu進程或者ovs-dpdk進程中,進程在虛擬地址空間中分配內存,交給硬件的地址都是進程的虛擬地址。然后vfio收到進程給的地址信息,把進程虛擬地址轉換成物理地址給IOMMU。IOMMU在外設給進程虛擬地址搬運數據時把地址轉換成這個物理地址。qemu相比ovs-dpdk多了一道手續,guest driver給硬件配置的地址是自己的物理地址,qemu和vfio得把guest的物理地址轉換成host的物理地址。
passthrough的問題是虛擬機不能熱遷移,熱遷移要把網卡pcie空間的數據遷移走,而且還要知道硬件DMA修改了那些guest的內存,要把修改的內存也遷移過去,但硬件都沒有提供這樣的接口,提供了接口qemu還得有辦法獲取。既然有其它物理網卡能passthrough了,就無需實現virtio物理網卡來passthrough。
5、vdpa
這張圖也來自於redhat博客,圖太多可能博客作者搞混了圖,原圖是不對的。當時可能想把vpda往vfio/mdev方面靠,首先mdev並不要求必須遵循virtio標准,vdpa實現了數據面的virtio標准,並且把datapath offload到了硬件,控制面沒有offload繼續由qemu模擬,硬件設備虛擬出來的vdpa未必實現了virtio標准要求的pcie功能,顯然用vfio不行。沒有vfio就不能利用其控制iommu的代碼,需要vdpa自己開發,內核中沒有upstream,控制面利用vhost協議,vhost再調用vhost-vdpa,vhost-vdpa調用硬件廠商提供的接口把控制信息下發到硬件中。
這種方法硬件網卡直接把包DMA到guest中了,問題是kick和call還是很麻煩,kick到了kvm,kvm通知vhost-vdpa調用硬件驅動來通知硬件。硬件call時中斷給了硬件驅動,硬件驅動通知vhsot-vdpa,vhost-vdpa再通知kvm,最后由kvm把中斷注入guest。如果guest用dpdk poll mode driver,kick和call就不成問題了。
搞了這么復雜,第一是為了讓硬件網卡實現起來容易,硬件網卡廠商基於SRIOV和Scable IOV實現資源分割,再模擬vring操作就可以包裝出vdpa。第二是為了實現熱遷移,寄希望於硬件廠商的driver能提供接口獲取DMA寫了哪些guest內存。第三guest和host用vdpa統一用virtio-net驅動,qemu只增加一個vhost-vdpa模塊,硬件的不同讓硬件廠商的驅動屏蔽,廠商同時提供硬件和驅動,其它模塊都不動就能適配不同廠商。
6性能分析
guest保持不變,用的都是virtio-net驅動,中斷和協議棧有開銷,這是避免不了的。定性分析一下三種轉發模型的利弊,忽略virtio full offload。首先上限受制於PCIE插槽的物理能力,當然真實場景下很難達到物理上限,只有dpdk l2fwd這些簡單模型下才能達到。包處理就是一條流水線,達到無縫配合才能實現性能最高,並行幾條流水線處理性能會更好。
簡單總結一下影響性能的幾個因素,分析每種因素在三種模塊中的影響。
-
中斷收包有上下文切換,有切換就會有性能開銷。
vhost-net在物理網卡收發包時使用了中斷,vhost-user使用dpdk pmd,因而沒有中斷開銷。
-
zero-copy,當然是拷貝越少越好了,最好零拷貝。vdpa就是zero-copy,vhost-net 和vhost-user的rx無法zero-copy。假設rx要實現zero copy,guest分配skb,backend把skb發給硬件網卡驅動,驅動設置給硬件網卡,硬件並不知道來的包發送給哪個虛擬機。假如硬件網卡一個隊列對應一個虛擬機,那guest不提供skb,硬件網卡就丟包了,rx skb只能由硬件網卡驅動分配,分配時可以從guest內存中分配嗎?好像也不行,因為沒法和guest同步,只能從內核或者ovs空間中分配skb,分配的skb也沒法共享給guest,必須拷貝。vhost-net和vhsot-user tx都能實現zero-copy,guest 分配skb,backend直到等物理網卡驅動DMA走再通知guest發送完了,你可以回收skb了。過程有些復雜,第一guest可能需要等很久才能回收,第二等硬件網卡發送完了通知哪個guest回收難實現。所以vhost-net和vhost-user zero-copy都不現實。
-
hugepage,能減少TBL miss。
vhost-user模型中qemu和ovs-dpdk都用到了hugepage,壞處是內存不能超賣。
-
多隊列
參與轉發的CPU越多,從串行變成並行,當然能提高性能,但CPU資源是有限的,到底分幾個隊列呢。物理網卡有多隊列,virtio-net也有多隊列,按流分隊列還是按包分隊列。不管怎樣,進哪個隊列由物理網卡來實現,軟件實現不划算,物理網卡計算一個hash值寫到skb中,再根據這個值入virtio-net隊列。發送最簡單的配置就是一個cpu一個隊列。
-
無鎖
減少同步的開銷,DPDK中有無鎖隊列,CPU之間傳遞skb用無鎖隊列開銷就小。另一方面就是查流表和寫流表實現無鎖,用urcu等等。
-
接力干還是一口氣干到底
pipeline設計問題,接力干就是一個cpu處理一段時間skb后把它放入隊列交由下一個cpu處理,下一個cpu poll或者由上個cpu通知它開始處理skb。沒有數據包時poll就會白白浪費cpu,而通知機制有開銷和時延。一口氣干到底就是就是一個cpu從收包、處理包到最后發送包,都由自己處理,配合多隊列多個cpu同時進行。這兩種模式的不同涉及到cpu cache/prefech等技術的利用,接力干對cache/prefetch友好。vhost-net是接力干的,vhost-user兩個模式都能用,用戶態修改代碼和調試都容易一點。
整體來看vdpa是性能最高的,還省cpu,但是價錢貴;其次是vhost-user,有點費cpu;vhost性能最差。
7總結
vhost-net用的最多,配套完善,穩定成熟。據我所知vhost-user在電信級別雲中很常用,電信級別雲只追求性能,不考慮超賣,vcpu強綁定,不跨numa,網元數據面passthrough,數據面和控制面通過vhost-user通信。數據面如果用了vhost-user能熱遷移能橫向擴展,這樣更符合NFV的理念。但普通openstack環境vhost-user就不能使用內核的QOS、netfilter、connection tracking、三層轉發和arp等功能,這意味着openstack安全組,QOS和DVR都無法使用,只能用高性能三層專用網關。vpda個人沒用過,沒有這樣的硬件,可能很多公司已經開始試用了,恐怕是一張很貴的卡,不僅要處理virtio還要處理vxlan,虛擬機能用,裸機也能用。
沒有完美的開源技術,只有最佳實踐經驗,最佳適用場景,用起來,修改起來,回饋開源社區。
8參考文獻
-
https://www.redhat.com/en/blog/deep-dive-virtio-networking-and-vhost-net
-
https://www.redhat.com/en/blog/journey-vhost-users-realm
-
https://www.redhat.com/en/blog/how-deep-does-vdpa-rabbit-hole-go
-
https://zhuanlan.zhihu.com/p/336616452
-
https://zhuanlan.zhihu.com/p/308114104