天天看點

虛拟化基本知識及virtio-net初探

作者:達坦科技DateLord
虛拟化基本知識及virtio-net初探

QEMU/KVM是在Linux中被廣泛使用的虛拟化技術之一,而virtio作為一個半虛拟化I/O事實上的标準[1],是QEMU/KVM在I/O虛拟化部分的預設實作。virtio-net是virtio标準中的網卡裝置,被廣泛應用。本文将會沿着虛拟化,virtio半虛拟化I/O,virtio-net的基本情況這條路線逐漸深入。下面在第一節介紹虛拟化的基本知識和虛拟化IO的相關方法,在第二小節介紹引入virtio的必要性和其基本知識,在第三小節介紹virtio-net的相關内容。

虛拟化基本知識

虛拟機已經悄然滲透到我們的生活中,随處可見。比如,申請雲伺服器的時候,我們申請到的機器就是一台虛拟機;在編寫完服務端代碼,部署我們的服務時,經常采用docker,kata等容器技術;再比如,我們可能需要VMware來建立一個隔離的環境來運作某些惡意的服務;亦或是自制了一個作業系統,但是又不想用硬體調試,那我們就可以用qemu來進行測試;甚至在新能源的車機上,都可能通過虛拟化技術運作着多套作業系統。看到這裡,大家可能自然産生了一個疑問,這麼多虛拟化的方法有什麼異同,各自又有什麼特點和适用場景呢?本節将會帶領大家走進虛拟化,一窺虛拟化的基本知識,本小節内容和圖檔參考[2]、[3]、[8]。在深入之前,首先要确定什麼是虛拟化。虛拟化的定義是指抽象和模拟計算機的硬體和軟體的技術。計算機系統大概可以劃分為四個層次:硬體、作業系統、庫和使用者程式,如下圖所示:

虛拟化基本知識及virtio-net初探

而本節開始的描述的虛拟化場景中,都是抽象了這個結構中的某個層次,并為上層服務提供接口。

根據虛拟化層次的不同,我們可以将虛拟化技術劃分為不同的種類:

  • 直接對硬體資源進行虛拟化,也叫做系統級虛拟化。是通過模拟和抽象硬體資源,來提供虛拟化的ISA接口。我們常用的VMware,Xen,qemu都是屬于這個類型的。
  • 作業系統層次的虛拟化,也叫做輕量級虛拟化,程序虛拟化。作業系統本身就是對硬體的一種虛拟化,容器技術進一步通過namespace、Cgroups和overlayfs來提供對資源的隔離和限制,實作對作業系統的虛拟化。docker容器就是屬于這個類型的。
  • 應用層程式運作環境的虛拟化。對API調用進行虛拟化,無論是通過語言運作時還是攔截調用進行轉換,都可以實作對主控端裝置的中的API虛拟化的目标。JVM,Wine等技術就屬于這個類型。

本文主要讨論系統級虛拟化,下面介紹系統虛拟化的基本知識。

系統虛拟化

首先介紹一下系統級虛拟化知識中會出現的相關名詞

  • Guest OS:運作在虛拟機裡面的作業系統
  • Host OS:Type-2架構下支援虛拟機運作的作業系統
  • VMM/Hypervisor:表示虛拟機資料總管,是負責為虛拟機提供虛拟的硬體資源的,前者通常用于Type-2類型的虛拟機場景,後者通常用于Type-1類型的虛拟機場景
  • Type-1/Type-2:Type-1/Type-2是兩種虛拟化的架構,前者表示虛拟機資料總管直接和硬體互動,後者表示虛拟機資料總管通過Host OS和硬體互動。

    Type-1架構如下圖所示:

虛拟化基本知識及virtio-net初探

Type-2架構如下圖所示:

虛拟化基本知識及virtio-net初探

接着我們看一下在系統虛拟化的過程中有哪些需要實作的内容。系統虛拟化的任務一般要分為CPU虛拟化,記憶體虛拟化,I/O虛拟化。本文的virtio半虛拟化就是I/O虛拟化方法的一種,目前I/O虛拟化速度成為整個系統的瓶頸,是以越來越被人重視,而virtio半虛拟化就是解決這個問題的利器。CPU虛拟化是将CPU的能力抽象出來,可以給上層模拟出多個相同或者不同架構的CPU。根據指令執行所需要的權限不同,指令可以分為特權指令和非特權指令。同時根據指令執行是否通路實體資源,我們可以将指令分為敏感指令和非敏感指令。是以如果所有的敏感指令都是特權指令,我們就很容易實作CPU虛拟化,我們将這樣的指令集稱為可虛拟化架構,反之稱之為不可虛拟化架構。記憶體虛拟化需要保證虛拟機看到的是從0開始并且連續的,這是通過引入了一層新的位址空間GPA來解決的,Host OS直接接觸實體記憶體,是以會有HPA(Host Phyical Address),同時Host OS上面運作着Guest OS作為自己的程式,是以此時引入了GPA(Guest Phyical Address)和GVA(Guest Virtual Address)。當Guest OS想要通路記憶體時,首先從GVA通過頁表找到GPA,再通過頁表找到HPA,如下圖所示:

虛拟化基本知識及virtio-net初探

最後來到了I/O虛拟化部分。I/O虛拟化需要複用外設的接口資源,提供給客戶機使用。外設的接口資源通常由MMIO寄存器、PMIO寄存器、中斷子產品和DMA子產品四部分組成。對I/O的的虛拟化需要同時考慮這四種資源。I/O虛拟化有三種方式,分别為模拟、透傳、半虛拟化:

  • 模拟是通過對指令的模仿進行虛拟化,模拟不需要專用硬體的支援,但是性能比較弱。
  • 透傳是Guest OS直接通路I/O硬體,不經過Host OS,這樣的虛拟化有更高的性能;但是透傳在裝置共享,動态遷移場景下有不小的問題。
  • 半虛拟化的事實标準是Virtio。是通過一個Host OS和Guest OS共同認可的通信方式,加快通信的速度,在具有适用性的同時,大幅提升了速度。這也是本文接下來的主角,會在virtio基本知識小節介紹virtio的具體情況。

在介紹virtio的具體知識之前,再簡單介紹一下QEMU/KVM,本篇文章是以QEMU/KVM作為背景的。

QEMU/KVM

virtio運用在多個虛拟化的産品中,QEMU/KVM就是其中之一。QEMU是由Fabrice Bellard提出的一個通過仿真來虛拟化的項目。完整地實作了處理器虛拟化、記憶體虛拟化以及I/O裝置。但是由于采用了仿真方法,虛拟化的性能比較差。而KVM是由Avi Kivity提出的,一經提出便迅速進入Linux主線,它采用的是半虛拟化的方法。隻提供處理器虛拟化和記憶體虛拟化,不提供I/O虛拟化。既然QEMU功能全面而性能薄弱,而KVM性能強勁但是功能薄弱,一個很自然的想法是将兩者結合在一起。QEMU/KVM就是讓KVM處理處理器虛拟化和記憶體虛拟化,運作在核心态;讓QEMU處理I/O虛拟化,運作在使用者态。QEMU/KVM的架構如下圖所示:

虛拟化基本知識及virtio-net初探

其中,QEMU通過ioctl與/dev/kvm互動;1個QEMU程序可以啟動1個VM;1個VM裡面的vcpu是由一個線程負責的。

virtio基本知識

在了解了virtio在虛拟化整個架構的位置後,下面介紹一下virtio的基本知識及必要性,本小節内容和圖檔參考[4]。

virtio是一個半虛拟化的I/O方法,是由IBM的Rusty Russell在2007年提出的。通過仿真虛拟化I/O的方法适用性比較強,但是由于經曆了Guest OS、QEMU、Host OS多個階段,是以會産生多次VM Exit和VM Entry,性能比較弱,而I/O成為了整個系統的瓶頸,這也是迫切需要virtio的原因。

virtio具體架構如下所示:

虛拟化基本知識及virtio-net初探

主要的改進在于,不直接采用現有的硬體驅動,淘汰了客戶機驅動,裝置模拟層,VMM中的硬體驅動三層架構,而是采用一種更為進階别的抽象,減少了多層溝通的開銷。virtio相比于仿真,不再是全部通過中斷的方法通知讀取資料,而是通過更輕量化的事件機制異步通信;采用Vring的方法在前後端中共享記憶體;也不會再完整地模拟寄存器的行為來控制裝置和驅動,因為記憶體的性能無法和寄存器相比,失去了模拟的意義,直接使用記憶體中的一塊區域;也不需要每一次都進入到KVM module裡面,減少了VM-exit的次數,降低了開銷。

其中Virtio Device是QEMU模拟出來的裝置,是virtio的backend,負責操作實體裝置。Virtio Driver是Guest OS中的驅動,是virtio的frontend,例如Virtio-net,Virtio-blk,負責發送Guest OS的I/O請求。前端和後端裝置之間是通過Virtqueue來實作的,virtqueue是資料操作的接口,它具體是通過vring來實作的,但是隻會暴露給外界virtqueue的接口。前後端通信是通過Notification機制來進行的。

virtio網絡裝置是virtio架構中的一員,負責虛拟化網卡的工作。主要由前端驅動和後端裝置組成,随着後端裝置放置位置的不同,性能也會不同。本文介紹的是virtio-net,它的後端裝置放在qemu程序裡面。

虛拟化基本知識及virtio-net初探

virtio-net相關内容

下面具體解析virtio-net的相關内容,本小節内容和圖檔參考[5],[6],[7],[8],[9],[10],[11],[12]。virtio-net主要涉及内容有Virtio Device,Virtio Driver,Virtqueue,Notification,如下圖所示:

虛拟化基本知識及virtio-net初探

他們的作用已經在virtio基本知識小節介紹過了,下面說明各個部分的具體情況,來看一下virtio-net是如何起作用的.

Virtio Device

首先來看Virtio Device。virtio-net後端裝置負責和tap虛拟網卡通信,進而控制實際硬體。它是在qemu中利用面向對象的思想實作的。virtio裝置和相關總線關系如下圖所示:

虛拟化基本知識及virtio-net初探

而virtio裝置的挂載過程如下:首先建立一個virtio-net-PCI代理裝置,挂載到PCI總線上;再創立一個virtio總線,挂載到上面的代碼裝置上;然後在virtio總線上挂載virtio裝置,如virtio_net,virtio_blk等裝置。

Virtio Driver

接着是Virtio Driver前端驅動,主要實作代碼在linux_source_code/drivers/net/virtio_net.c中。

在講Virtio Driver之前,首先需要了解一下Linux裝置驅動的模型,這是整個driver實作的基礎。裝置模型是在Linux2.6提出來的,用Class,Bus,Device,和Device driver來抽象裝置。Bus上挂載有Device裝置和Device driver;Bus和Device、Device driver的拓撲關系可以用來描述一個裝置樹的依賴關系;熱插拔裝置的Driver提前注冊在Bus上,當一個裝置插入Bus時注冊Device,此時通過match函數來和Bus上的Driver來比對,來執行相關函數。

首先介紹一下virtio_bus,bus的結構如下圖所示:

虛拟化基本知識及virtio-net初探

virtio_bus是一個bus_type的執行個體,和pci_bus_type并列;virtio_net驅動其實是一分為二,它利用pci_driver和virtio_driver兩個driver實作,是以virtio_pci_driver和virtio_net_driver分别挂載在pci和virtio總線上;他們的device也是類似的結構,device之間通過一個virtio_pci_device連接配接。在初始化時,virtio裝置會先在pci bus上完成初始化,然後在virtio bus上完成初始化。

然後介紹virtio-net driver和device的初始化具體過程:

  • pci和virtio側的驅動是在系統初始化的時候都注冊好了;
  • virtio_net裝置是挂載在pci總線上的,在pci_scan_device枚舉裝置的過程中,發現裝置後會注冊一個pci_dev;
  • 然後會先引發pci側的driver和device的match,最後調用register_virtio_device,注冊virtio側device;
  • 注冊了virtio側的device後,會引發virtio側的match,進而調用driver的virtnet_probe函數,建立virtio-net;

Virtqueue

virtqueue是Virtio Device和Virtio Driver溝通的方式,外界使用這個接口進行通信,而内部則是采用vring的方法實作的;每一個virtio裝置可能有多個virtqueue,而在virtio-net中最少有兩條virtqueue,分别用來收發資訊;也可以配置多條收發隊列。

virtqueue具體的資料是vring來管理,vring用了三個資料結構用來管理記憶體:

  • Vring_desc是描述符表,用于記錄所有的記憶體區域,每一項描述符表指向一塊記憶體。指向的記憶體是通過scatterlist來組織的,然後會把記憶體位址放到描述符表的addr項中,長度用len表示。
  • Vring_avail的id是表示可用的描述區域,用于Guest向Host标志資料區域可用。
  • Vring_used的id表示已用的描述符區域,用于Host向Guest标志資料區域可用。

這裡解釋一下avail和used的意思,兩者是站在Host OS的角度來說的。avail就是說現在Host可以讀取,意味着Guest OS向内放入了資料而used是說Host使用完成,告知Guest現在可以讀取。

最後來看一下virtqueue的通信過程。Guest OS讀取資料的流程如下:

虛拟化基本知識及virtio-net初探

①表示virtioDriver從Used隊列中擷取一個Buffer的位置,并将Data拷貝到Buffer中,用于傳送(還可以從符号表配置設定一個Buffer),然後更新Avail隊列中的描述符索引值;②表示Driver通知Device來取資料;③表示Device從Avail隊列中擷取到描述符索引值,并将描述符索引對應的位址中的資料取出來;最後Device更新Used隊列中的描述符索引,為下一次driver擷取描述符表做準備;④表示Device通知Driver資料已經取完了。發送過程和接受過程原理相似,方向相反,這裡就不再贅述了。

Notification

Notification是前後端通信的機制。guest向host發送通知,采用的是ioeventfd,通知的這個過程叫kick;host向guest發送通知,采用的是irqfd。兩者都依賴于核心中的一個功能eventfd。KVM相當于一個代理裝置,在QEMU和Guest OS之間互相通知對方,示意圖如下:

虛拟化基本知識及virtio-net初探

參考文獻

[1] virtio: Towards a De-Facto Standard For Virtual I/O Devices

[2] 深入淺出系統虛拟化:原理與實踐

[3] 術道經緯知乎專欄虛拟化部分

[4] virtio網絡演化曆史

[5] LoyenWang公衆号虛拟化專欄

[6] 深度探索Linux系統虛拟化 原理與實作

[7] QEMU/KVM源碼解析與應用

[8] 系統虛拟化原理與實作

[9] 蝸窩科技統一裝置模型專欄

[10] Linux裝置驅動開發詳解

[11] Virtio----Vring Introduce

[12] virtio-net 實作機制

繼續閱讀