虛擬化是雲計算的基石,拋開虛擬化談雲計算無異於緣木求魚,不得要領。前端
虛擬化簡介
虛擬化是一種技術,它是對物理硬件資源的虛擬。經過虛擬化使得應用運行在虛擬化以後的虛擬機上,達到充分利用物理資源的目的。
根據虛擬化的類型可將虛擬化分爲 I 型虛擬化和 II 型虛擬化。I 型虛擬化是直接做用於裸機上的,如 Xen 等虛擬化技術。II 型虛擬化是在操做系統之上的虛擬化,如 qemu-kvm,HyperV 等虛擬化技術。
根據虛擬化技術可將虛擬化分爲:全虛擬化,半虛擬化和硬件輔助的虛擬化。
全虛擬化
如上圖所示,全虛擬化是經過 QEMU 的模擬代碼來模擬 Guest 請求的虛擬化。全虛擬化須要通過捕獲/攔截/模擬等一系列流程。
物理上的 CPU 做用在不一樣的運行級別上,分別是 Ring3,Ring2,一直到 Ring0。對於 kernel 的代碼,CPU 是運行在 Ring0 級別,能夠執行特權指令,例如訪問硬件資源等指令。而,做用在其它級別上時,CPU 是處於受限模式的。對於 QEMU 和 Guest 來講,CPU 在執行它們的指令時沒有用 Ring0 級別去執行,當 Guest 執行 I/O 請求的時候,CPU 會報異常,這個異常會被內核模塊的 KVM 捕獲到,KVM 將該異常消息放入 I/O sharing page 中,而後經過 /dev/kvm 接口,告知 QEMU 去讀取 sharing page 中的異常消息,QEMU 讀取到該異常消息,經過本身的模擬代碼來驅動內核中相應的設備模塊實現 I/O 操做。當操做成功後,把結果放到 I/O sharing page 中,並通知 KVM 內核去讀取該操做成功後,把結果,KVM 讀取到該結果,將結果發給 Guest 的驅動程序實現整個 I/O 請求。
全虛擬化的指令都須要通過捕獲/攔截/模擬的過程,很複雜,效率很低。
半虛擬化
如圖所示,半虛擬化是對 Guest 的模塊進行修改的虛擬化。如今的半虛擬化一般是基於 virtio 框架的虛擬化。在 Guest 中包含 virtio 的 Front-end,在 Host 上安裝 virtio 的 Back-end。當有 I/O 請求時,Guest 上的前端 virtio driver 會直接和 Host 上的 virtio devcie 進行通訊,實現對硬件資源的訪問。
相比於 qemu 的全虛擬化,半虛擬化繞過了 QEMU ,從而少了指令捕獲/攔截/模擬的過程,效率要更高。
完整的 Guest I/O 流程以下圖所示:
硬件輔助的全虛擬化
硬件輔助的全虛擬化最少須要硬件 CPU 支持。Intel VT 技術支持硬件輔助的全虛擬化方案,在使用時須要進入 BIOS 中打開 VT。
在硬件輔助的全虛擬化中,CPU 有兩種執行模式,root 模式和 non-root 模式,對於 QEMU + kernel 代碼的運行,CPU 是運行在 root 模式,而對於 Guest 代碼的執行,CPU 運行在 non-root 模式。在 non-root 模式中,CPU 處於受限狀態,只能執行限制的指令,沒法執行特權指令。當 Guest 須要執行特權指令時,CPU 會從 non-root 模式切到 root 模式來執行該指令,此時 Guest 會被置於掛起狀態。當執行完特權指令以後,退出到 non-root 模式,繼續 Guest 的運行。
從 root 到 non-root 模式的切換叫 VM-Entry,從 non-root 到 root 模式的切換叫 VM-Exit。
相比於全虛擬化,硬件輔助的虛擬化不須要逐條翻譯 Guest 指令,大部分指令均可以經過 CPU 執行,從而縮短了「流程」,提高了虛擬化的效率。
虛擬化原理
虛擬化從本質上來講是對 CPU / 內存 以及 I/O 資源的虛擬,使得 Guest 有本身的硬件資源,從 Guest 角度看,它就是一臺獨立的 server。
CPU 虛擬化
CPU 的虛擬化是經過 KVM 內核模塊完成的,KVM 虛擬出 vCPU,Guest 上看到的 socket / core 其實是綁定到 vCPU 上的。
vCPU 有三種模式,kernel / user 和 guest 模式,當 Guest 執行 I/O 相關的操做時,vCPU 是運行在 user 模式下的,當執行非 I/O 的用戶空間操做,vCPU 是處在 guest 模式下的,當執行 kernel 代碼時,vCPU 是處在 kernel 模式下的。
從 Host 上來看,Guest 就是一個 qemu-kvm 進程,而 Guest 上的 CPU 就是 qemu-kvm 上的一個線程,物理 CPU 會像調用普通進程同樣調度它們。正由於 vCPU 是普通線程,因此它並不能真正代替 CPU 去執行 CPU 的指令,真正執行 Guest 上指令的仍是 Host 上的物理 CPU。
從上圖能夠看到,vCPU 上的操做經過 CPU Scheduler 層層調度到指定的物理 CPU core 上執行,真正完成 Guest 操做的是物理 CPU(這也是爲何硬件輔助虛擬化最少須要物理 CPU 支持的緣由)。
固然,能夠經過綁核的機制實現 vCPU 和物理 CPU core 的綁定。
內存虛擬化
KVM 實現客戶機內存的方式是,利用 mmap 系統調用,在 QEMU 主線程的虛擬地址空間中申明一段連續大小的空間用於客戶機物理內存映射。
在 VM 中,進程的虛擬內存(VA)會映射到 VM 的物理內存(PA),而 PA 向下被映射到 Host 的機器內存(MA)。 可是 VM 操做系統不能直接實現 PA 到 MA 的映射,須要 VMM(KVM) 實現 PA 到 MA 的映射。
VMM 實現 PA 到 MA 的映射有兩種方式:
1. 軟件方式:經過影子頁表技術實現內存地址的翻譯;
2. 硬件方式:基於 CPU 的硬件輔助虛擬化,如 Intel 的 EPT 和 AMD 的 NPT 技術。
I/O 資源的虛擬化
I/O 資源的虛擬化,分爲:
1. 設備模擬:在虛擬機監控器中模擬傳統 I/O 設備的特性,好比在 QEMU 中模擬一個 Intel 的網卡和 IDE 硬盤驅動器,在客戶機中就暴露爲對應的硬件設備。客戶機中的 I/O 請求都由虛擬機監控器捕獲並模擬執行後返回給客戶機(前面全虛擬化方式介紹的就是這種)。
2. 先後端驅動接口:在 VMM 和 VM 之間定義一種全新的適合於虛擬化環境的交互接口,常見的如 virtio 框架下,暴露給客戶機的 virtio-net,virtio-blk 等網絡和磁盤設備(前端設備),在 QEMU 中實現爲相應的後端驅動。
3. 設備直接分配:將 Host 上的物理設備直接分配給 VM,如網卡或硬盤驅動器直接分配給 VM 等。
4. 設備共享分配:設備直接分配的 I/O 虛擬化分配的設備是極爲有限的,設備共享分配將物理設備虛擬爲多個虛擬機功能接口,該接口獨立地分配給客戶機使用,從而支持多個 VM 的設備分配。如 SRIOV 就是一種常見的設備共享分配的虛擬化 I/O 方式。
搭建 KVM 虛擬機
這裏實現的是 qemu-kvm 硬件輔助的虛擬化方式。首先搭建虛擬化環境,打開 BIOS 中的 VT,編譯 kvm 模塊進內核,下載安裝 qemu 軟件,準備好 VMM 環境:
[root@localhost home]# lsmod | grep -i kvm
kvm_intel 170181 5
kvm 554609 1 kvm_intel
irqbypass 13503 15 kvm,vfio_pci
[root@localhost home]# rpm -qa | grep qemu
qemu-img-rhev-2.6.0-28.el7_3.6.x86_64
qemu-kvm-common-rhev-2.6.0-28.el7_3.6.x86_64
qemu-kvm-rhev-2.6.0-28.el7_3.6.x86_64
libvirt-daemon-driver-qemu-2.0.0-10.el7_3.4.x86_64
ipxe-roms-qemu-20160127-5.git6366fa7a.el7.noarch
環境準備好以後建立磁盤鏡像,將操做系統加載到磁盤鏡像中,經過 qemu-kvm 命令建立 VM:
[lianhua@localhost qemu-kvm]$ /usr/libexec/qemu-kvm -m 1G -smp 4 Virtualized.qcow2 -monitor stdio
QEMU 2.6.0 monitor - type 'help' for more information
(qemu) VNC server running on '::1;5900'
(qemu) info status
VM status: running
(qemu) info cp
cpus cpustats
(qemu) info cpus
* CPU #0: pc=0xffffffff9141c0ee (halted) thread_id=825030
CPU #1: pc=0xffffffff9141c0ee (halted) thread_id=825031
CPU #2: pc=0xffffffff9141c0ee (halted) thread_id=825032
CPU #3: pc=0xffffffff9141c0ee (halted) thread_id=825033
[lianhua@localhost ~]$ ps -lef | grep -i qemu
2 S lianhua 825024 819281 32 80 0 - 537525 poll_s 16:56 pts/0 00:02:16 /usr/libexec/qemu-kvm -m 1G -smp 4 Virtualized.qcow2 -monitor stdio
這裏磁盤鏡像已經事先製做好了,經過 qemu-kvm 啓動 VM,其中 -m 表示 VM 的運行內存;-smp 表示 VM 的 CPU 架構, -smp 4 表示有 4 個 core;-monitor 表示直接將 VMM monitor 重定向到當前命令行所在的標準輸入輸出設備上。
進入 monitor 中使用 info status 和 info cpus 查詢 VM 的狀態和 CPU 的使用狀況。能夠看到 CPU 實際上是 Host 上的線程,CPU 0 對應的線程 id 是 825030,CPU 1 對應的線程 id 是 825031,依次類推...,而 VM 則是 Host 上的一個 qemu 進程,其進程 id 爲 825024。
值得注意的是,即時使用一個空的磁盤鏡像,qemu-kvm 仍是能讓它運行,可是能夠想見,因爲空磁盤鏡像並無安裝操做系統,其實是沒有任何意義的(能想到的用處是測試 OpenStack 上 cinder / swift 組件是否正常,一般會使用一個測試鏡像測試該鏡像是否能夠上傳/安裝/被 VM 使用):
[lianhua@localhost qemu-kvm]$ ll -h lianhua.raw
-rw-r--r--. 1 lianhua lianhua 20G Jul 5 16:52 lianhua.raw
[lianhua@localhost qemu-kvm]$ du lianhua.raw
0 lianhua.raw
[lianhua@localhost qemu-kvm]$ /usr/libexec/qemu-kvm -m 1G -smp 5 lianhua.raw -monitor stdio
WARNING: Image format was not specified for 'lianhua.raw' and probing guessed raw.
Automatically detecting the format is dangerous for raw images, write operations on block 0 will be restricted.
Specify the 'raw' format explicitly to remove the restrictions.
QEMU 2.6.0 monitor - type 'help' for more information
(qemu) VNC server running on '::1;5900'
(qemu) info status
VM status: running
(qemu) info cpus
* CPU #0: pc=0x000000003fefa56a thread_id=888039
CPU #1: pc=0x00000000000fd374 (halted) thread_id=888041
CPU #2: pc=0x00000000000fd374 (halted) thread_id=888042
CPU #3: pc=0x00000000000fd374 (halted) thread_id=888043
CPU #4: pc=0x00000000000fd374 (halted) thread_id=888044
(qemu) info cpus
* CPU #0: pc=0x00000000000fc373 (halted) thread_id=888039
CPU #1: pc=0x00000000000fd374 (halted) thread_id=888041
CPU #2: pc=0x00000000000fd374 (halted) thread_id=888042
CPU #3: pc=0x00000000000fd374 (halted) thread_id=888043
CPU #4: pc=0x00000000000fd374 (halted) thread_id=888044
仔細看在建立 image 的時候, qemu 會聰明的不讓鏡像佔用磁盤空間,而是等須要佔用的時候再爲它分配磁盤空間。使用 prealloction=full 選項能夠在建立的時候爲鏡像分配磁盤空間。git