1. 虛擬化概念
什么是虛擬化
虛擬化是使用所謂虛擬機管理程序從一台物理機上創建若干個虛擬機的過程。虛擬機的行為和運轉方式與物理機一樣,但它們會使用物理機的計算資源,如 CPU 、內存和存儲。虛擬機管理程序會根據需要將這些計算資源分配給每個虛擬機。
虛擬化有哪些優勢
(1)提高硬件資源使用效率
一個服務器可以開多個虛擬機,給不同的應用使用。打破了一個應用一台服務器的限制。
(2)避免應用和服務直接的軟件沖突
很多應用和服務不能安裝在同一個系統下。
(3)提高穩定性
實現負載均衡、動態遷移、故障自動隔離,減少關機事件。在共享存儲的前提下,可以動態的進行遷移,甚至不用關機。
(4)便於管理,降級管理成本
應用的隔離,每個應用使用獨立的虛擬機,減少相互影響。
(5)更快的重新部署、更簡單備份
可以使用如下功能:模板、克隆、快照
(6)通過動態資源的配置提高 IT 對業務的靈活適應力
業務重點變化時,可以更加靈活、更快的分配計算、存儲資源
通過 cpu 虛擬化、內存虛擬化及 I/O虛擬化從根本上了解虛擬化的原理
2. CPU虛擬化
x86 操作系統是設計直接運行在物理硬件之上的,因此完全占有硬件資源。x86 架構提供了 四個特權級別給操作系統和應用程序來訪問硬件。Ring 是指 CPU 的運行級別,Ring0 是最高級別,Ring 1-3 依次遞減。
應用程序是運行在 Ring3上的,如果應用程序需要訪問磁盤,比如寫文件,那就需要通過執行系統調用(函數),執行系統調用的時候,CPU 運行級別將從 Ring 3 切換到 Ring 0,並跳轉到系統調用對應的內核代碼位置執行,這樣內核就為你完成了設備的訪問,完成之后再從 Ring0 切換到 Ring3,這個過程稱之為 用戶態 和 內核態 的切換。
那么問題來了,因為宿主機(物理機)工作在 ring0 的,客戶機(GuestOS)就不能工作在 ring0了,但是 客戶機操作系統是不知道的,所以這時候就需要 虛擬機管理程序(VMM)來避免這件事的發生。虛擬機通過 VMM 實現 GuestOS CPU 對硬件的訪問,根據其原理不同有三種實現技術:
(1)全虛擬化
(2)半虛擬化
(3)硬件輔助的虛擬化
2.1 基於二進制翻譯的全虛擬化
客戶操作系統運行在 Ring 1, 它在執行特權指令時,會觸發異常(CPU 的機制,沒有權限的指令會觸發異常),然后 VMM 捕獲這個異常,在異常里面做翻譯,模擬,最后返回到客戶操作系統內,客戶操作系統認為自己的特權指令工作正常,繼續運行。但是這個性能損耗,就非常大,簡單的一條指令,執行完,了事,現在卻要通過復雜的異常處理過程。
當一個進程要調用 cpu 指令(特權+普通)時,GuestOS認為自己就運行硬件之上,直接對虛擬的CPU進行調用,但是它沒有權限執行,當GuestOS 執行特權指令時,會拋出異常,然后HostOS會捕獲這個異常翻譯,在HostOS中執行指令調用,進而通過內核對真正的cpu執行指令調用,這中間的異常捕獲,翻譯會消耗大量的資源,性能比較差。
2.2 半虛擬化
半虛擬化的思想就是,修改操作系統內核,替換掉不能虛擬化的指令,通過超級調用(hypercall)直接和底層的虛擬化層hypervisor來通訊,hypervisor同時也提供了超級調用接口來滿足其他關鍵內核操作,比如內存管理、中斷和時間保持。
這種做法省去了全虛擬化中的捕獲和模擬,大大提高了效率。所以像XEN這種半虛擬化技術,客戶機操作系統都是有一個專門的定制內核版本,和x86、mips、arm這些內核版本等價。這樣以來,就不會有捕獲異常、翻譯、模擬的過程了,性能損耗非常低。這就是XEN這種半虛擬化架構的優勢。這也是為什么XEN只支持虛擬化Linux,無法虛擬化windows原因,微軟不改代碼啊。
GuestOS 的內核進行修改過,明確知道自己運行在虛擬化環境中,在進行系統調用時,直接通過 Host 的內核對CPU進行調用,因為中間少了虛擬化捕獲異常,翻譯的過程,性能會大大的提升。代表之一:XEN
2.3 硬件輔助的虛擬化
2005年后,CPU廠商Intel 和 AMD 開始支持虛擬化了。 Intel 引入了 Intel-VT (Virtualization Technology)技術。 這種 CPU,有 VMX root operation 和 VMX non-root operation兩種模式,兩種模式都支持Ring 0 ~ Ring 3 共 4 個運行級別。這樣,VMM 可以運行在 VMX root operation模式下,客戶 OS 運行在VMX non-root operation模式下。
而且這兩種操作模式可以相互轉換。運行在 VMX root operation 模式下的 VMM 通過顯式調用 VMLAUNCH 或 VMRESUME 指令切換到 VMX non-root operation 模式,硬件自動加載 Guest OS 的上下文,於是 Guest OS 獲得運行,這種轉換稱為 VM entry。Guest OS 運行過程中遇到需要 VMM 處理的事件,例如外部中斷或缺頁異常,或者主動調用 VMCALL 指令調用 VMM 的服務的時候(與系統調用類似),硬件自動掛起 Guest OS,切換到 VMX root operation 模式,恢復 VMM 的運行,這種轉換稱為 VM exit。VMX root operation 模式下軟件的行為與在沒有 VT-x 技術的處理器上的行為基本一致;而VMX non-root operation 模式則有很大不同,最主要的區別是此時運行某些指令或遇到某些事件時,發生 VM exit。
也就是,硬件這層就做了區分,這樣全虛擬化下,那些靠“捕獲異常 - 翻譯 - 模擬” 的實現就不需要了。而且 CPU 廠商,支持虛擬化的力度越來越大,靠硬件輔助的全虛擬化技術的性能逐漸逼近半虛擬化,再加上硬件輔助虛擬化不需要修改客戶操作系統這一優勢,硬件輔助虛擬化應該是未來的發展趨勢。
區分三者的主要特點:
1. 全虛擬化:欺騙 GuestOS,讓GuestOS以為自己是運行在物理機之上
2. 硬件輔助虛擬化:需要CPU硬件的支持
3. 半虛擬化:需要修改GuestOS 的操作系統,windows不支持
3. KVM CPU虛擬化
kvm 是基於 cpu 輔助的全虛擬化方案,它需要 CPU 虛擬化特性的支持。
3.1 KVM 虛擬機創建過程
(1)qemu-kvm 通過對 /dev/kvm 的 一系列 ICOTL 命令控制虛機;
(2)一個 KVM 虛機即一個 Linux qemu-kvm 進程,與其他 Linux 進程一樣被Linux 進程調度器調度;
(3)KVM 虛機包括虛擬內存、虛擬CPU和虛機 I/O設備,其中,內存和 CPU 的虛擬化由 KVM 內核模塊負責實現,I/O 設備的虛擬化由 QEMU 負責實現;
(4)KVM虛機系統的內存是 qemu-kvm 進程的地址空間的一部分;
(5)KVM 虛機的 vCPU 作為 線程運行在 qemu-kvm 進程的上下文中。
vCPU、QEMU 進程、Linux 進程調度和物理CPU之間的邏輯關系:
2.2 CPU 中虛擬化功能
因為 cpu 中虛擬化功能的支持,並不存在虛擬的 CPU ,KVM Guest 代碼是運行在物理 CPU 之上。支持虛擬化的 CPU 中都增加了新的功能。以 Intel VT 技術為例,它增加了兩種運行模式:VMX root 模式和 VMX nonroot 模式。通常來講,主機操作系統和 VMM 運行在 VMX root 模式中,客戶機操作系統及其應用運行在 VMX nonroot 模式中。
因為兩個模式都支持所有的 ring,因此,客戶機可以運行在它所需要的 ring 中(OS 運行在 ring 0 中,應用運行在 ring 3 中),VMM 也運行在其需要的 ring 中 (對 KVM 來說,QEMU 運行在 ring 3,KVM 運行在 ring 0)。CPU 在兩種模式之間的切換稱為 VMX 切換。從 root mode 進入 nonroot mode,稱為 VM entry;從 nonroot mode 進入 root mode,稱為 VM exit。可見,CPU 受控制地在兩種模式之間切換,輪流執行 VMM 代碼和 Guest OS 代碼。
對 KVM 虛機來說,運行在 VMX Root Mode 下的 VMM 在需要執行 Guest OS 指令時執行 VMLAUNCH 指令將 CPU 轉換到 VMX non-root mode,開始執行客戶機代碼,即 VM entry 過程;
在 Guest OS 需要退出該 mode 時,CPU 自動切換到 VMX Root mode,即 VM exit 過程。
可見,KVM 客戶機代碼是受 VMM 控制直接運行在物理 CPU 上的。QEMU 只是通過 KVM 控制虛機的代碼被 CPU 執行,但是它們本身並不執行其代碼。也就是說,CPU 並沒有真正的被虛擬化成虛擬的 CPU 給客戶機使用。
VMM 完成 Vcpu、內存的初始化后,通過 ioctl 調用KVM 接口,完成虛擬機的創建,並創建一個線程來運行 VM,由於VM在前期初始化的時候會設置各種寄存器來幫助KVM查找到需要加載的指令的入口(main函數)。所以線程在調用了KVM接口后,物理CPU的控制權就交給了VM。VM運行在VMX non-root模式,這是Intel-V或者AMD-V提供的一種特殊的CPU執行模式。然后當VM執行了特殊指令的時候,CPU將當前VM的上下文保存到VMCS寄存器(這個寄存器是一個指針,保存了實際的上下文地址),然后執行權切換到VMM。VMM 獲取 VM 返回原因,並做處理。如果是IO請求,VMM 可以直接讀取VM的內存並將IO操作模擬出來,然后再調用VMRESUME指令,VM繼續執行,此時在VM看來,IO操作的指令被CPU執行了。
Intel-V 在 ring0~ring3 的基礎上,增加了VMX模式,VMX分為root和non-root。這里的VMX root模式是給VMM(前面有提到VM monitor),在KVM體系中,就是qemu-kvm進程所運行的模式。VMX non-root模式就是運行的Guest,Guest也分ring0~ring3,不過他並不感知自己處於VMX non-root模式下。
Intel 的虛擬架構基本分為兩個部分:
虛擬機監視器
客戶機(GuestOS)
虛擬機監視器(Virtual-machine monitors - VMM)
虛擬機監視器在宿主機上表現為一個提供虛擬機CPU,內存以及一系列硬件虛擬的實體,這個實體在KVM體系中就是一個進程,如qemu-kvm。VMM負責管理虛擬機的資源,並擁有所有虛擬機資源的控制權,包括切換虛擬機的CPU上下文等。
Guest
這個Guest可能是一個操作系統(OS),也可能就是一個二進制程序,whatever,對於VMM來說,他就是一堆指令集,只需要知道入口(rip寄存器值)就可以加載。
Guest運行需要虛擬CPU,當Guest代碼運行的時候,處於VMX non-root模式,此模式下,該用什么指令還是用什么指令,該用寄存器該用cache還是用cache,但是在執行到特殊指令的時候(比如Demo中的out指令),把CPU控制權交給VMM,由 VMM來處理特殊指令,完成硬件操作。
VMM 和 Guest 的切換
KVM的CPU虛擬化依托於Intel-V提供的虛擬化技術,將Guest運行於VMX模式,當執行了特殊操作的時候,將控制權返回給VMM。VMM處理完特殊操作后再把結果返回給Guest。
幾個概念:socket(顆,cpu 的物理單位),core(核,每個cpu中的物理內核),thread(超線程,通常來說,一個 cpu core 只提供一個 thread,這時客戶機就只看到一個cpu;但是,超線程技術實現了 cpu 核的虛擬化,一個核被虛擬化出多個邏輯 cpu,可以同時運行多個線程)。
上圖分三層,他們分別是VM層,VMKernel層和物理層。對於物理服務器而言,所有的CPU資源都分配給單獨的操作系統和上面運行的應用。應用將請求先發送給操作系統,然后操作系統調度物理的CPU資源。在虛擬化平台比如 KVM 中,在VM層和物理層之間加入了VMkernel層,從而允許所有的VM共享物理層的資源。VM上的應用將請求發送給VM上的操作系統,然后操縱系統調度Virtual CPU資源(操作系統認為Virtual CPU和物理 CPU是一樣的),然后VMkernel層對多個物理CPU Core進行資源調度,從而滿足Virtual CPU的需要。在虛擬化平台中OS CPU Scheduler和Hypervisor CPU Scheduler都在各自的領域內進行資源調度。
KVM 中,可以指定 socket,core 和 thread 的數目,比如:設置 -smp 5, sockets=5, cores=1, threads=1,則 vCPU 的數目為 5*1*1 = 5。客戶機看到的是基於 KVM vCPU 的 CPU 核,而 vCPU 作為 QEMU 線程被 Linux 作為普通的線程/輕量級進程調度到物理的 CPU 核上。其結論是在 VMware ESXi 上,性能沒什么區別,只是某些客戶機操作系統會限制物理 CPU 的數目,這種情況下,可以使用少 socket 多 core。
2.3 客戶機系統的代碼是如何運行的
一個普通的 Linux 內核有兩種執行模式:內核模式(Kernel)和用戶模式 (User)。為了支持帶有虛擬化功能的 CPU,KVM 向 Linux 內核增加了第三種模式即客戶機模式(Guest),該模式對應於 CPU 的 VMX non-root mode。
KVM 內核模塊作為 User mode 和 Guest mode 之間的橋梁:
User mode 中的 QEMU-KVM 會通過 ICOTL 命令來運行虛擬機
KVM 內核模塊收到該請求后,它先做一些准備工作,比如將vcpu 上下文加載到 VMCS(virtual machine control structure)等,然后驅動 CPU 進入 VMX non-root 模式,開始執行客戶機代碼。
三種模式的分工為:
Guest 模式:執行客戶機系統非 I/O 代碼,並在需要的時候驅動 CPU 退出該模式;
Kernel 模式:負責將 CPU 切換到 Guest mode 執行 Guest OS 代碼,並在 CPU 退出 Guest mode 時回到 Kernel 模式
User 模式:代表客戶機系統執行 I/O 操作
QEMU-KVM 相比原生 QEMU 的改動:
原生的 QEMU 通過指令翻譯實現 CPU 的完全虛擬化,但是修改后的 QEMU-KVM 會調用 ICOTL 命令來調用 KVM 模塊;
原生的 QEMU 是單線程實現,QEMU-KVM 是多線程實現。
主機 Linux 將一個虛擬機視作一個 QEMU 進程,該進程包括下面幾種線程:
I/O 線程用於管理模擬設備;
vCPU 線程用於運行 Guest 代碼;
其他線程,比如處理 event loop,offloaded tasks 等的線程。
kvm 是一個內核模塊,它實現了一個 /dev/kvm 的字符設備來與用戶交互,通過調用一系列 ioctl 函數可以實現 qemu 和 kvm 之間的切換。
qemu-kvm 進程工作過程:
1. 啟動一個子線程,創建初始化 vcpu,主線程等待;
2. 子線程創建初始化vcpu完畢,子線程等待,並等候通知主線程運行;
3. 主線程繼續初始化虛擬化工作,初始化完成,通知子線程繼續運行;
4. 子線程繼續啟動虛擬機 kvm_run,主線程執行 select 交互處理。
kvm 進程分析:
Thread-1:主線程,這個線程loop循環,循環操作 select 實際就是查看有無讀寫文件描述符,有的話進行讀寫操作;
Thread-2:子線程,異步進行 I/O 操作,主要針對磁盤映射操作(block drive);
Thread-3:子線程,vcpu 線程,kvm_run啟動和運行虛擬機。
示例:通過 qemu-kvm 開啟一個 2 核心的虛擬機
# 通過 qemu-kvm 創建一個 2 核心的虛機 [root@localhost ~]# qemu-kvm -cpu host -smp 2 -m 512m -drive file=/root/cirros-0.3.5-i386-disk.img -daemonize VNC server running on `::1:5900' # 查看 qemu-kvm 主進程 [root@localhost ~]# ps -ef | egrep qemu root 24066 1 56 13:59 ? 00:00:10 qemu-kvm -cpu host -smp 2 -m 512m -drive file=/root/cirros-0.3.5-i386-disk.img -daemonize root 24077 24041 0 13:59 pts/0 00:00:00 grep -E --color=auto qemu # 查看 qemu-kvm 子線程 [root@localhost ~]# ps -Tp 24066 PID SPID TTY TIME CMD 24066 24066 ? 00:00:00 qemu-kvm 24066 24067 ? 00:00:00 qemu-kvm 24066 24070 ? 00:00:07 qemu-kvm 24066 24071 ? 00:00:02 qemu-kvm 24066 24073 ? 00:00:00 qemu-kvm # 通過 gdb 查看子線程的作用 (gdb) thread 1 [Switching to thread 1 (Thread 0x7fb830cb2ac0 (LWP 24066))] #0 0x00007fb829eaebcd in poll () from /lib64/libc.so.6 (gdb) thread 2 [Switching to thread 2 (Thread 0x7fb7fddff700 (LWP 24073))] #0 0x00007fb82de0e6d5 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0 (gdb) thread 3 [Switching to thread 3 (Thread 0x7fb81fe6e700 (LWP 24071))] #0 0x00007fb829eb02a7 in ioctl () from /lib64/libc.so.6 (gdb) thread 4 [Switching to thread 4 (Thread 0x7fb82066f700 (LWP 24070))] #0 0x00007fb829eb02a7 in ioctl () from /lib64/libc.so.6
通過上面的數據得出:
1 個 2 核心的虛擬機,4個子線程:
(1)thread-1:主線程 loop 循環,循環操作 select 查看有無讀寫文件描述符,有的話進行讀寫操作;
(2)thread-2:子線程 異步 I/O 操作,主要針對磁盤映像操作(block drive);
(3)thread-3:子線程 vcpu 線程,kvm_run 啟動和運行虛擬機;
(4)thread-4 同 thread-3
2.4 從客戶機線程到物理 cpu 的兩次調度
要將客戶機中的線程調度到某個物理 cpu,需要經歷兩個過程:
(1)客戶機線程調度到客戶物理cpu 即 KVM vCPU,該調度由客戶機操作系統負責,每個客戶機操作系統的實現方式不同。在kvm上,vCPU 在客戶機系統看起來就像物理 cpu,因此其調度方法也沒有什么不同;
(2)vCPU 線程調度到物理 CPU 即主機物理 CPU,該調度由 Hypervisor 即 Linux 負責。
KVM 使用了標准的 Linux 進程調度方法來調度 vCPU進程。Linux系統中,線程和進程的區別是 進程有獨立的內核空間,線程是代碼的執行單位,也就是調度的基本單位。Linux中,線程是輕量級的進程,也就是共享了部分資源的進程,所以線程也按照進程的調度方式來進行調度。
2.5 客戶機 VCPU 數目的分配方法
1. 不是客戶機的 vcpu 越多,其性能越好,因為線程切換會消耗大量的時間;應該根據負載需要分配最少的 vcpu;
2. 主機上客戶機的 vcpu 總數不應該超過 物理 cpu 內核總數。不超過的話,就不存在 cpu 競爭,每個 vcpu 線程在一個物理 cpu 核上被執行;超過的話,會出現部分線程等待 cpu 以及 一個 cpu 核上的線程之間的切換,這會 overhead;
3. 將負載分為 計算負載 和 I/O負載, 對計算負載,需要分配較多的 vcpu,甚至考慮 cpu 親和性,將制定的物理 cpu 核分給這些客戶機。
確定 Vcpu 數目的步驟。假如我們要創建一個VM,以下幾步可以幫助確定合適的vCPU數目:
1. 了解應用並設置初始值
該應用是否是關鍵應用,是否有Service Level Agreement。一定要對運行在虛擬機上的應用是否支持多線程深入了解。咨詢應用的提供商是否支持多線程和SMP(Symmetricmulti-processing)。參考該應用在物理服務器上運行時所需要的CPU個數。如果沒有參照信息,可設置1vCPU作為初始值,然后密切觀測資源使用情況。
2. 觀測資源使用情況
確定一個時間段,觀測該虛擬機的資源使用情況。時間段取決於應用的特點和要求,可以是數天,甚至數周。不僅觀測該VM的CPU使用率,而且觀測在操作系統內該應用對CPU的占用率。特別要區分CPU使用率平均值和CPU使用率峰值。假如分配有4個vCPU,如果在該VM上的應用的CPU:
(1)使用峰值等於 25%,也就是僅僅能最多使用 25% 的全部CPU資源,說明該應用是單線程,僅能夠使用一個vCPU;
(2)平均值小於38%,而峰值小於45%,考慮減少 vCPU 數目;
(3)平均值大於75%,而峰值大於90%,考慮增加 vCPU 數目。
3. 更改 vCPU 數據並觀測結果
每次的改動盡量少,如果可能需要 4vCPU,先設置 2vCPU 在觀測性能是否可以接受。