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

KVM 虛擬化原理探究(5)— 網絡IO虛擬化

標籤(空格分隔): KVMlinux


IO 虛擬化簡介

前面的文章介紹了KVM的啓動過程,CPU虛擬化,內存虛擬化原理。做爲一個完整的風諾依曼計算機系統,必然有輸入計算輸出這個步驟。傳統的IO包括了網絡設備IO,塊設備IO,字符設備IO等等,在KVM虛擬化原理探究裏面,咱們最主要介紹網絡設備IO和塊設備IO,其實他們的原理都很像,可是在虛擬化層又分化開了,這也是爲何網絡設備IO虛擬化和塊設備IO虛擬化要分開講的緣由。這一章介紹一下網絡設備IO虛擬化,下一章介紹塊設備IO虛擬化。網絡

傳統的網絡IO流程

這裏的傳統並非真的傳統,而是介紹一下在非虛擬化環境下的網絡設備IO流程。咱們日常所使用的Linux版本,好比Debian或者CentOS等都是標準的Linux TCP/IP協議棧,協議棧底層提供了driver抽象層來適配不一樣的網卡,在虛擬化中最重要的是設備的虛擬化,可是瞭解整個網絡IO流程後去看待虛擬化就會更加容易理解了。socket

標準的TCP/IP結構

在用戶層,咱們經過socket與Kernel作交互,包括建立端口,數據的接收發送等操做。
在Kernel層,TCP/IP協議棧負責將咱們的socket數據封裝到TCP或者UDP包中,而後進入IP層,加入IP地址端口信息等,進入數據鏈路層,加入Mac地址等信息後,經過驅動寫入到網卡,網卡再把數據發送出去。以下圖所示,比較主觀的圖。tcp

image_1aqgfh90g3ip1gtmclm180313o4m.png-95.8kB

在Linux的TCP/IP協議棧中,每一個數據包是有內核的skb_buff結構描述的,以下圖所示,socket發送數據包的時候後,進入內核,內核從skb_buff的池中分配一個skb_buff用來承載數據流量。性能

image_1aqgg26d7gof1jaanlbtblvq13.png-114.6kB
當數據到了鏈路層,鏈路層作好相應的鏈路層頭部封裝後,調用驅動層適配層的發送接口 dev_queue_xmit,最終調用到 net_start_xmit 接口。
image_1aqgn9e3ak2g183g1s2j150713581g.png-268.7kBcode

發送數據和接收數據驅動層都採用DMA模式,驅動加載時候會爲網卡映射內存並設置描述狀態(寄存器中),也就是內存的起始位,長度,剩餘大小等等。發送時候將數據放到映射的內存中,而後設置網卡寄存器產生一箇中斷,告訴網卡有數據,網卡收到中斷後處理對應的內存中的數據,處理完後向CPU產生一箇中斷告訴CPU數據發送完成,CPU中斷處理過程當中向上層driver通知數據發送完成,driver再依次向上層返回。在這個過程當中對於driver來講,發送是同步的。接收數據的流程和發送數據幾乎一致,這裏就不細說了。DMA的模式對後面的IO虛擬化來講很重要。blog

image_1aqger4b915nf19k11gjv1lc21atm9.png-46.6kB

KVM 網絡IO虛擬化

準確來講,KVM只提供了一些基本的CPU和內存的虛擬化方案,真正的IO實現都由qemu-kvm來完成,只不過咱們在介紹KVM的文章裏都默認qemu-kvm和KVM爲一個體系,就沒有分的那麼仔細了。實際上網絡IO虛擬化都是由qemu-kvm來完成的。接口

KVM 全虛擬化IO

還記得咱們第一章節的demo裏面,咱們的「鏡像」調用了 out 指令產生了一個IO操做,而後由於此操做爲敏感的設備訪問類型的操做,不能在VMX non-root 模式下執行,因而VM exits,模擬器接管了這個IO操做。隊列

switch (kvm->vcpus->kvm_run->exit_reason) {
        case KVM_EXIT_UNKNOWN:
            printf("KVM_EXIT_UNKNOWN\n");
            break;
        // 虛擬機執行了IO操做,虛擬機模式下的CPU會暫停虛擬機並
        // 把執行權交給emulator
        case KVM_EXIT_IO:
            printf("KVM_EXIT_IO\n");
            printf("out port: %d, data: %d\n", 
                kvm->vcpus->kvm_run->io.port,  
                *(int *)((char *)(kvm->vcpus->kvm_run) + kvm->vcpus->kvm_run->io.data_offset)
                );
            break;
        ...

虛擬機退出並得知緣由爲 KVM_EXIT_IO,模擬器得知因爲設備產生了IO操做並退出,因而獲取這個IO操做並打印出數據。這裏其實咱們就最小化的模擬了一個虛擬IO的過程,由模擬器接管這個IO。事件

在qemu-kvm全虛擬化的IO過程當中,其實原理也是同樣,KVM捕獲IO中斷,由qemu-kvm接管這個IO,因爲採用了DMA映射,qemu-kvm在啓動時候會註冊設備的mmio信息,以便能獲取到DMA設備的映射內存和控制信息。

static int pci_e1000_init(PCIDevice *pci_dev)
{
    e1000_mmio_setup(d); 
    // 爲PCI設備設置 mmio 空間
    pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio); 
    pci_register_bar(&d->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &d->io);
    d->nic = qemu_new_nic(&net_e1000_info, &d->conf, object_get_typename(OBJECT(d)), d->dev.qdev.id, d);   
    add_boot_device_path(d->conf.bootindex, &pci_dev->qdev, "/ethernet-phy@0"); 
}

對於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中看到有網絡設備。

image_1aqgs30fm15lj1pse1t6lm051751t.png-37.9kB

如上圖所示,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()操做,和本機網絡核心進行通信。

相似這樣的操做

fd = open("/dev/tap", XXX)
write(fd, buf, 1024);
read(fd, buf, 1024);

bridge多是一個Linux bridge,也多是一個OVS(Open virtual switch),在涉及到網絡虛擬化的時候,一般須要利用到bridge提供的VLAN tag功能。

以上就是KVM的網絡全虛擬化IO流程了,咱們也能夠看到這個流程的不足,好比說當網絡流量很大的時候,會產生過多的VM的切換,同時產生過多的數據copy操做,咱們知道copy是很浪費CPU時鐘週期的。因而qemu-kvm在發展的過程當中,實現了virtio驅動。

KVM Virtio 驅動

基於 Virtio 的虛擬化也叫做半虛擬化,由於要求在Guest中加入virtio驅動,也就意味着Guest知道了本身運行於虛擬環境了。
image_1aqgvbg7090kr1a1meq1tvilc62a.png-48.6kB

不一樣於全虛擬化的方式,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。

KVM Vhost-net

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

image_1aqh0s600fq41tj21b1r16331elm2n.png-143.8kB

vhost-net 繞過了 QEMU 直接在Guest的front-end和backend之間通訊,減小了數據的拷貝,特別是減小了用戶態到內核態的拷貝。性能獲得大大增強,就吞吐量來講,vhost-net基本可以跑滿一臺物理機的帶寬。
vhost-net須要內核支持,Redhat 6.1 後開始支持,默認狀態下是開啓的。

總結

KVM的網絡設備IO虛擬化通過了全虛擬化->virtio->vhost-net的進化,性能愈來愈接近真實物理網卡,可是在小包處理方面任然存在差距,不過已經不是一個系統的瓶頸了,能夠看到KVM在通過了這多年的發展後,性能也是愈加的強勁,這也是他領先於其餘虛擬化的重要緣由之一。 在本章介紹了IO虛擬化後,下一章介紹塊設備的虛擬化,塊設備虛擬化一樣利用了DMA的特性。

相關文章
相關標籤/搜索