====================================
目錄
1 硬件虛擬化技術背景
2 KVM的內部實現概述
2.1 KVM的抽象對象
2.2 KVM的vcpu
2.3 KVM的IO虛擬化
2.3.1 IO的虛擬化
2.3.2 VirtIO
3 KVM-IO可能優化地方
3.1 Virt-IO的硬盤優化
3.2 普通設備的直接分配(Direct Assign)
3.3 普通設備的複用
===================================
1 硬件虛擬化技術背景
硬件虛擬化技術通過虛擬化指令集、MMU(Memory Map Unit)以及IO來運行不加修改的操作系統。
傳統的處理器通過選擇不同的運行(Ring 特權)模式,來選擇指令集的範圍,內存的尋址方式,中斷髮生方式等操作。在原有的Ring特權等級的基礎上,處理器的硬件虛擬化技術帶來了一個新的運行模式:Guest模式[1],來實現指令集的虛擬化。當切換到Guest模式時,處理器提供了先前完整的特權等級,讓Guest操作系統可以不加修改的運行在物理的處理器上。Guest與Host模式的處理器上下文完全由硬件進行保存與切換。此時,虛擬機監視器(Virtual Machine Monitor)通過一個位於內存的數據結構(Intel稱爲VMCS, AMD稱爲VMCB)來控制Guest系統同Host系統的交互,以完成整個平臺的虛擬化。
傳統的操作系統通過硬件MMU完成虛擬地址到物理地址的映射。在虛擬化環境中,Guest的虛擬地址需要更多一層的轉換,才能放到地址總線上:
Guest虛擬地址 -> Guest物理地址 -> Host物理地址 ^ ^ | | MMU1 MMU2
其中MMU1可以由軟件模擬(Shadow paging中的vTLB)或者硬件實現(Intel EPT、AMD NPT)。MMU2由硬件提供。
系統的IO虛擬化技術,通常是VMM捕捉Guest的IO請求,通過軟件模擬的傳統設備將其請求傳遞給物理設備。一些新的支持虛擬化技術的設備,通過硬件技術(如Intel VT-d),可以將其直接分配給Guest操作系統,避免軟件開銷。
[1]X86處理器的生產廠商有自己的稱謂,比如英特爾將Guest模式稱爲non-root operation,與之相對的是root operation,本文稱爲host模式。
2 KVM的內部實現概述
KVM是Linux內核的一個模塊,基於硬件虛擬化技術實現VMM的功能。該模塊的工作主要是通過操作與處理器共享的數據結構來實現指令集以及MMU的虛擬化,捕捉Guest的IO指令(包括Port IO和mmap IO)以及實現中斷虛擬化。至於IO設備的軟件模擬,是通過用戶程序QEMU來實現的。QEMU負責解釋IO指令流,並將其請求換成系統調用或者庫函數傳給Host操作系統,讓Host上的驅動去完成真正的IO操作。她們之間的關係如下圖所示:
+--------------+ +--------+ | Qemu | | | | | | | | +---+ +----+| | Guest | | |vHD| |vNIC||<-----+ | | | +---+ +----+| | | | +--------------+ | +--------+ ^ | ^ | syscall |IO stream | | via FDs | | +----|----------------|------------|--------+ | | | v | | v | +----------+ | | +--------+ +------>| | | | |drivers |<--+ | kvm.ko | | | +--------+ | +----------+ | | ^ | Host kernel | +----|----------|---------------------------+ v v +--------+ +---+ | HDD | |NIC| +--------+ +---+ 圖 1
從Host操作系統的角度來看,KVM Guest操作系統相當於一個進程運行在系統上,普通的命令如kill、top、taskset等可以作用於該Guest。該進程的用戶虛擬空間就是Guest的物理空間,該進程的線程對應着Guest的處理器。
從Qemu的角度來看,KVM模塊抽象出了三個對象,她們分別代表KVM自己,Guest的虛擬空間以(VM)及運行虛擬處理器(VCPU)。這三個對象分別對應着三個文件描述符,Qemu通過文件描述符用系統調用IOCTL來操作這三個對象,同KVM交互。此時,Qemu主要只模擬設備,她以前的CPU和MMU的模擬邏輯都被kvm.ko取代了。
2.1 KVM的抽象對象
KVM同應用程序(Qemu)的交互接口爲/dev/kvm,通過open以及ioctl系統調用可以獲取並操作KVM抽象出來的三個對象,Guest的虛擬處理器(fd_vcpu[N]), Guest的地址空間(fd_vm), KVM本身(fd_kvm)。其中每一個Guest可以含有多個vcpu,每一個vcpu對應着Host系統上的一個線程。
Qemu啓動Guest系統時,通過/dev/kvm獲取fd_kvm和fd_vm,然後通過fd_vm將Guest的“物理空間”mmap到Qemu進程的虛擬空間,並根據配置信息創建vcpu[N]線程,返回fd_vcpu[N]。然後Qemu將操作fd_vcpu在其自己的進程空間mmap一塊KVM的數據結構區域。該數據結構(下圖中的shared)用於同kvm.ko交互,包含Guest的IO信息,如端口號,讀寫方向,內存地址等。Qemu通過這些信息,調用虛擬設備註冊的回調函數來模擬設備的行爲,並將Guest IO請求換成系統請求發送給Host系統。由於Guest的地址空間已經映射到Qemu的進程空間裏面,Qemu的虛擬設備邏輯可以很方便的存取Guest地址空間裏面的數據。三個對象之間的關係如下圖所示:
+----------+ | +--------+ | Qemu | Host user | | | | | | | | | | | | Guest | | +------+| | | user | | |shared|| | | | | +------+| | | | | ^ | | | | +-------|--+ | | | | | | | | fds| | | | | ------|---|---------------| |--------| | | | | | v v Host kernel | | Guest | +---------+ | | kernel | | | | | | | kvm.ko |----+ | | | | | |fd_kvm | | | +---------+ | | +--------+ v ^ +----+ fd_vm | |vmcs|----+-------------- +------+ +----+ | +------+ | host | | | Guest| | mode | |fd_vcpu | mode | +------+ | +------+ ^ v ^ | +-------+ | | vm exit | Phy | vm entry| +-------------| CPU |---------+ +-------+ 圖 2
圖中vm-exit代表處理器進入host模式,執行kvm和Qemu的邏輯。vm-entry代表處理器進入Guest模式,執行整個Guest系統的邏輯。如圖所示,Qemu通過三個文件描述符同kvm.ko交互,然後kvm.ko通過vmcs這個數據結構同處理器交互,最終達到控制Guest系統的效果。其中fd_kvm主要用於Qemu同KVM本身的交互,比如獲取KVM的版本號,創建地址空間、vcpu等。fd_vcpu主要用於控制處理器的模式切換,設置進入Guest mode前的處理器狀態等等(內存尋址模式,段寄存器、控制寄存器、指令指針等),同時Qemu需要通過fd_vcpu來mmap一塊KVM的數據結構區域。fd_vm主要用於Qemu控制Guest的地址空間,向Guest注入虛擬中斷等。
2.2 KVM的vcpu
如前文所述,KVM的vcpu對應着host系統上的一個線程。從Qemu的角度來看,她運行在一個loop中:
for (;;) { kvm_run(vcpu); switch (shared_data->exit_reason) { ... case KVM_IO: handle_io(vcpu); break; case KVM_MMIO: handle_mmio(vcpu); break; ... } }
該線程同Guest的vcpu緊密相連。如果我們把線程的執行看作Guest vcpu的一部分,那麼從Host的角度來看,該vcpu在三種不同的上下文中運行:Host user/Host kernel/Guest,將運行於一個更大的循環當中。該vcpu的運行邏輯如下圖:
Host user | Host kernel | Guest mode | | | | | | | +->kvm_run(vcpu)-------+ | | | | v | | | | +->vm entry----------+ | | | | | v | | | | | Execute | | | | | Natively | | | | | | | | | | vm exit<----------+ | | | | | | | | | | | | | | Yes | | v | | | +----------------I/O ? | | | | | | | No | | | | | | | | | | | | | v | | | v Yes | | Signal | | +--Handle IO<---------Pending? | | | | | No | | | +----+ | | 圖 3
實際上,在host上通過ps命令看到的關於vcpu這個線程的運行時間正是這三種上下文的總和。
2.3 KVM的IO虛擬化
2.3.1 IO的虛擬化
傳統系統中,設備都直接或間接的掛在PCI總線上。PCI設備通過PCI配置空間以及設備地址空間接收操作系統的驅動請求和命令,通過中斷機制通知反饋操作系統。配置空間和設備地址空間都將映射到處理器Port空間或者操作系統內存空間中,所以設備的軟件模擬需要VMM將相關的Guest PIO和MMIO請求截獲,通過硬件虛擬化提供的機制將其傳送給軟件。模擬軟件處理完後再通過VMM提供的虛擬中斷機制反饋Guest。如下圖所示:
+-----------------------------------+ | +--------------+ | | | PCI config | +----------+ | | +--------------+<--->| driver | | | +--------------+<--->| | | | | Device memory| +----------+ | | +--------------+ ^ | | ^ | | +-------|--------------------|------+ | | vINTR via VMM PIO/MMIO via VMM| +----------+ v | +------------------------+ | +--------+ +--------+ | | | PCI | | Device | | | | config | | memory | | Virtual Device | +--------+ +--------+ | +------------------------+ | v +------------+ |host driver | +------------+ 圖 4
虛擬設備的軟件邏輯放在用戶層也可以放在內核中。完全的虛擬設備模擬,可以處理在Guest中不加修改的驅動請求。通常這將消耗大量的處理器cycle去模擬設備。如果可以修改或者重寫Guest的驅動代碼,那麼虛擬設備和驅動之間的IO接口可以根據虛擬化的特性重新定義爲更高層更加高效的接口,如下圖所示:
+----------------+ | | | +-----------+ | | |para-driver| | | +-----------+ | +-------^--------+ | | new I/O interface via VMM v +---------+ |Virtual | |device | +---------+ | v +------------+ |host driver | +------------+ 圖 5
KVM的virtio正是通過這種方式提供了高速IO通道。
除了軟件模擬,現有的硬件虛擬化技術還可以將一些支持虛擬化技術的新興硬件直接分配給Guest。除了需要支持虛擬化技術的硬件(可以發起remmappable的MSI中斷請求),設備的直接分配一般還需要主板上的芯片以及CPU支持,比如英特爾的VT-d技術。支持虛擬化技術的硬件平臺主要做兩件事,一個是DMA Remapping,將DMA請求中的Guest的物理地址映射到Host的物理地址,另一個是中斷Remapping,將能remappable的中斷請求根據由VMM設置,位於內存的IRT(Interrupt Remapping Table)發送到指定的vcpu上。
PC平臺上,通常北橋(或者類似結構的root-complex)連接着CPU、內存以及外設。用於DMA Remapping和中斷Remapping的硬件邏輯位於北橋中。如下所示:
+-------------+ |cpu0, cpu1...| +-------------+ ^ | <-- System Bus | | v v +---------------------+ | North Bridge | | | +--------+ | +--------+ |<----->| Memory | | | vt-d | | +--------+ | +--------+ | +---------------------+ ^ ^ | | v v +--------+ +--------+ | PCI-e | | South |<-----> PCI legacy devices... | device | | Bridge | +--------+ +--------+ 圖 6
目前,只有支持MSI的PCI/PCI-e設備才能直接分配給Guest。其中PCI-e設備可以直接與北橋相連或者橋連,然後單獨分配給一個Guest。在一個橋後的所有的橋連PCI設備只能作爲一個整體分配給一個Guest。KVM在硬件虛擬化的平臺上支持PCI-e/PCI設備的直接分配。
2.3.2 VirtIO
VirtIO爲Guest和Qemu提供了高速的IO通道。Guest的磁盤和網絡都是通過VirtIO來實現數據傳輸的。由於Guest的地址空間mmap到Qemu的進程空間中,VirtIO以共享內存的數據傳輸方式以及半虛擬化(para-virtualized)接口爲Guest提供了高效的硬盤以及網絡IO性能。其中,KVM爲VirtIO設備與Guest的VirtIO驅動提供消息通知機制,如下圖所示:
+---------------+ | Qemu | | +--------+ | +-------------------+ | | VirtIO | | | +---------+ | | | Device | | | | VirtIO | Guest | | +--------+ | | | Driver | | +------|--^-----+ | +---------+ | | | +---|---^-----------+ irqfd | | PIO | | fd_vm | |ioeventfd | |vInterrupt ---------|--|------------------|---|------------ v | v | +----------+ +--------------+ Host | eventfd |<------->| KVM.ko | kernel | core | | | +----------+ +--------------+ 圖 7
如圖所示,Guest VirtIO驅動通過訪問port空間向Qemu的VirtIO設備發送IO發起消息。而設備通過讀寫irqfd或者IOCTL fd_vm通知Guest驅動IO完成情況。irqfd和ioeventfd是KVM爲用戶程序基於內核eventfd機制提供的通知機制,以實現異步的IO處理(這樣發起IO請求的vcpu將不會阻塞)。之所以使用PIO而不是MMIO,是因爲
KVM處理PIO的速度快於MMIO。
3 KVM-IO可能優化地方
3.1 Virt-IO的硬盤優化
從圖1中可以看到,Guest的IO請求需要經過Qemu處理後通過系統調用纔會轉換成Host的IO請求發送給Host的驅動。雖然共享內存以及半虛擬化接口的通信協議減輕了IO虛擬化的開銷,但是Qemu與內核之間的系統模式切換帶來的開銷是避免不了的。
目前Linux內核社區中的vhost就是將用戶態的Virt-IO網絡設備放在了內核中,避免系統模式切換以及簡化算法邏輯最終達到IO減少延遲以及增大吞吐量的目的。如下圖所示:
+-------------------+ | +---------+ | | | VirtIO | Guest | | | Driver | | | +-----+---+ | +---|---^-----------+ PIO | | | | vInterrupt ------------------------------|---|-------------- v | +----------+ +--------------+ Host | Vhost |<------->| KVM.ko | kernel | net | | | +----^-----+ +--------------+ | | +---v----+ | NIC | | Driver | +--------+ 圖 8
目前KVM的磁盤虛擬化還是在用戶層通過Qemu模擬設備。我們可以通過vhost框架將磁盤的設備模擬放到內核中達到優化的效果。
3.2 普通設備的直接分配(Direct Assign)
如前文所述,目前只有特殊的PCI設備才能直接分配給相應的Guest,即VMM-bypass,避免額外的軟件開銷。我們可以在KVM中軟實現DMA以及中斷的remapping功能,然後將現有的普通設備直接分配給Guest。如下圖所示:
+----------------+ | Guest | | +---------+ | +-------->| | Driver | | | | +---------+ | | +------------^---+ D | | | M | DMA Req.| | vINTR A | | | | +-------|-------|----------+ O | | v KVM | | p | | +------------------+ | e | | | DMA remmapping | | r | | | | | a | | | INTR remmapping | | t | | +-----------^------+ | i | +-------|-------|----------+ o | | | INTR n | v | | +---------+ +------------->| Deivce | +---------+ 圖 9
這將大大減少Guest驅動同物理設備之間的路徑(省去了KVM的涉入),去掉了虛擬設備的模擬邏輯,不過IO性能的提高是以增加KVM的邏輯複雜度的代價換來的。此時,IO的性能瓶頸從Qemu/KVM轉移到物理設備,但是IO的穩定性、安全性將會更加依賴於KVM的remapping邏輯實現。
3.3 普通設備的複用
在普通設備的直接分配的基礎上,我們甚至可以在多個Guest之間複用設備,好比m個進程跑在n個處理器上一樣(n < m)。比如將一個硬盤分成多個區,每一個分區作爲一個塊設備直接分配給Guest;或者直接將n個網卡分配給m個Guest(n < m)。其中磁盤的複用,只需在KVM中添加分區管理的邏輯,而網卡的複用則要複雜一些:KVM需要爲設備提供多個設備上下文(每一個設備上下文對應着一個Guest),同時還需要提供算法邏輯對設備上下文進行切換和調度。如下圖所示:
| KVM | | Device context | | queue | +------+ | +-+ | |Guest |---------->| | | -------+ | +-+ | | | | +------+ | +-+ | |Guest |---------->| | +----------+ | +------+ | +-+ | Device | | | | | Scheduler| | +------+ | +-+ +----------+ | |Guest |---------->| |-----+ | +------+ | +-+ | | | +--v--------+ | | Current--->+--+ DM | | +-----+ | Context | +--+------------->| NIC | | +-----------+ | +-----+ | | 圖 10
其中,Device Modle(DM)實現前文提到的remapping邏輯,Device Scheduler用於選擇和切換設備上下文實現物理設備的複用。在普通設備直接分配的基礎上,通過對現有普通設備的複用,將會帶來廉價、靈活、高效的IO性能。與之相對的是,目前已經有支持SR-IOV的網卡,從硬件上實現複用功能,支持多個(靜態,即最大數目是內置的)虛擬的PCI網卡設備,價格昂貴,且受到一個網口總帶寬有限的限制(軟件複用技術,可以複用多個網卡,進而提高系統總的帶寬)。
參考:
1[代碼] qemu-kvm. git://git.kernel.org/pub/scm/virt/kvm/qemu-kvm.git
2[代碼] Linux-2.6{/virt/kvm/*, arch/x86/kvm/*, drivers/virtio/*, drivers/block/virtio_blk.c, drivers/vhost/*}
3[手冊] Intel? Virtualization Technology for Directed I/O
4[手冊] Intel? 64 and IA-32 Architectures Software Developer’s Manual 3B.
5[論文] Towards Virtual Passthrough I/O on Commodity Devices. 2008.
6[論文] kvm: the Linux Virtual Machine Monitor. 2007.
7[論文] virtio: Towards a De-Facto Standard For Virtual I/O Devices. 2008
8[論文] High Performance Network Virtualization with SR-IOV. 2010.
9[論文] QEMU, a Fast and Portable Dynamic Translator. 2005.