之前在 Intel 做開源虛擬化專案 ACRN (https://projectacrn.github.io/latest/index.html),現在虛擬化還是很火熱,所以希望總結下之前的學習和開發經驗,也由淺到深分享下,從虛擬化的分類,實現,和我們 ACRN 中的實現;
1. 虛擬化概述
1.0 概述
區別與直接排程片上資源/使用物理平臺,使用虛擬化技術對於資源的排程會更加靈活和高效,而且可以達到硬隔離的目的;
我們需要 Hypervisor / VMM ( Virtual Machine Monitor) 來實現虛擬化;
虛擬化的目的可以用一句話來概述:虛擬化技術的目的是希望能夠截獲上層作業系統應用對硬體資源的訪問,然後重定向到 VMM 的資源池中,再由 VMM 來對片上資源進行管理;
“虛擬機器可以看作是物理機的一種高效隔離的複製”,有以下三個典型特徵:
- 同質,虛擬機器的執行環境和物理機的環境本質上相同,但是表現上能夠有一些差異;
- 高效,虛擬機器中執行的軟體需要有接近物理機(native)中執行的效能;
- 資源受控,VMM 需要對系統資源有完全控制能力和管理許可權,資源的分配 / 監控 / 回收;
基於這樣的需求,我們有了虛擬機器方案, 比如 KVM, Xen, VMware, ACRN 等等;
1.1 核心態(Kernel mode) 和 使用者態(User mode)
x86 CPU 中的操作有兩個特權形態:核心態 和 使用者態
- 核心態:如果 CPU 處於核心態,執行的程式可以執行任何 CPU 指令,並且訪問記憶體中的所有地址,包括外圍裝置,比如硬碟/網路卡等等;
- 使用者態:如果處於使用者態,只能訪問受限的資源,而且不能引用記憶體或者直接訪問外圍裝置;
所有使用者程式執行在使用者態,但是有些程式需要做核心態的事情(比如讀取硬碟資料,獲取硬碟輸入),所以這個應用程式 APP x 就需要進行從使用者態到核心態的切換,簡單來說過程如下:使用者態執行 APP x 收到一個 system call,然後設定 mode bit=0 切換到核心態,當核心態中執行完,設定 mode bit=1 切換回到使用者態;
1.2 特權指令與敏感指令
首先引入 特權指令(Privileged Instruction)和 敏感指令(Sensitive Instruction)的概念:
- 特權指令(Privileged Instruction):對於系統中一些敏感資源的管理和讀寫的指令被定位特權指令,只有處於 Ring 0 才能進行正確執行,否則會丟出異常;
- 敏感指令(Sensitive Instruction):由於虛擬化的引入,由於 OS 現在處於 Ring1 所以不能執行特權指令,所以交由 Ring 0 的 VMM 來處理執行,這部分指令稱為敏感指令;可以理解為客戶機中必須交由 VMM 處理的指令;
對於有虛擬化的環境,客戶機處於 Ring 1 而不是 Ring 0,如果所有的敏感指令都是特權指令,那麼執行任意的敏感指令都會產生 trap,這樣保證了客戶機中如果進行這些“敏感”操作的指令,都會交給處於 Ring 0 的 VMM 處理;
敏感指令包括:
- 所有 I/O 指令;
- 企圖訪問或者修改 VM mode 或者機器狀態的指令;
- 企圖訪問或者修改敏感暫存器 / 儲存單元的指令;
- 企圖訪問儲存保護系統或記憶體 / 地址分配系統的指令;
但是 x86 中有些指令,必須由處於 Ring 0 狀態的 VMM 處理,但是工作在 Ring 1 不會產生 Trap,這樣的話如果處於 Ring 1 的客戶機執行這些指令,不會產生 Trap,也不能被定義為特權指令,這與上一句中的目的相沖突,所以必須也要 Trap 這些 “非特權指令”,x86 中稱之為 臨界指令(Critical Instructions);
所以 x86 中,敏感指令 = 特權指令 + 非特權指令 / 臨界指令,如果一個系統上 敏感指令 = 特權指令,那麼為了讓 VMM 完全控制硬體資源,我們讓虛擬機器上的 OS 處於 Ring 1,不能直接執行 敏感/特權指令,而 VMM 處於 RIng 0 ,所以 OS 上執行 敏感/特權指令 的時候,就會 引起陷入 / cause a trap 到 VMM,再由 VMM 來模擬執行引起異常的指令;
臨界指令 包括 敏感指令 中的 敏感暫存器指令 和 保護系統指令;
2. 虛擬化分類
根據虛擬化實現的方法,我們可以大概分為 作業系統級別虛擬化(OS-level virtulization),全虛擬化(Full virtualization),類/半虛擬化(Para virtulization)和 混合虛擬化(Hybrid-Para virtualization);
作業系統級別的虛擬化技術 不需要對於底層進行改動或者考慮 OS 下面,也沒有所謂的 VMM 去監管分配底層資源,而是通過 OS 共享核心的方式,為上層應用提供多個完成且隔離的環境("the kernel allows the existence of multiple isolated user space instances"),這些 例項(instances),就被稱之為 容器(container),虛擬化資源和效能開銷很小,而且也不需要硬體的支援,是一種輕量化的虛擬化實現技術;
VMM 虛擬的是現實存在的平臺,而且客戶機不知道自己是虛擬出來的平臺,以為是真實的平臺,不需要對於 OS 進行修改,這是 完全虛擬化(Full virtulization);
但是有些情況 VMM 虛擬的平臺是現實中不存在的(要經過 VMM 重新定義,需要對於客戶機的 OS 進行修改),這是 類/半虛擬化(Para virtulization);
對於完全虛擬化,可以通過硬體/軟體輔助的方式來實現;
2.1 全虛擬化(Full virtualization)
全虛擬化會模擬足夠的硬體裝置,而且不需要對作業系統核心進行修改;
客戶機(Guest OS)不知道自己在一個虛擬化的環境,所以硬體的虛擬化都在 VMM 或者宿主機中完成,所以客戶機可以呼叫它以為真實硬體的控制命令;
根據“截獲並重定向”的實現方式,我們將全虛擬化分為 軟體虛擬化 和 硬體虛擬化;
2.1.1 全虛擬化中的軟體輔助虛擬化
因為之前 x86 的平臺的硬體沒有從硬體層面支援虛擬化,所以採用純軟體的方式實現 “截獲重定向”;
通過讓客戶機的特權指令陷入異常,從而觸發宿主機進行虛擬化處理的機制來處理,具體的實現方法通過以下兩種方式相結合;
- 優先順序壓縮 (由於虛擬化的引入,應用從 Ring 3 -> Ring 3, 作業系統從 Ring 0 -> Ring 1,VMM 將取代 OS 處於 Ring 0)
- 二進位制程式碼翻譯(優先順序壓縮並不能很好的處理截獲所有的特權指令,需要通過二進位制翻譯來掃描修改客戶機的二進位制程式碼,來將這些難以虛擬化的指令轉換為支援虛擬化的指令)
2.1.2 硬體虛擬化
後來 x86 平臺的物理裝置本身慢慢的開始支援虛擬化,提供了對特殊指令截獲重定向的硬體支援;
比如 Intel 的 VT-x 技術;
2.1.2.1 硬體虛擬化中的 Type-1 Hypervisor
Type-1 Hypervisor,或者稱之為 Bare-metal Hypervisor,虛擬機器直接執行在 Hardware 之上,系統上電之後載入執行虛擬機器監控程式,資源的排程是 HW->VMM->VM;
這種虛擬機器將上層的 OS 和底層的硬體脫離,所以上層的軟體也不依賴或者侷限於特殊的硬體裝置或者驅動;
2.1.2.2. 硬體虛擬化中的 Type 2 hypervisor
Type-2 Hypervisor,或者稱之為 Hosted Hypervisor,虛擬機器不是直接執行在硬體資源之上,而是在作業系統之上;
所以系統上電之後,會先啟動作業系統,然後載入執行虛擬機器監控程式,資源的排程是 HW -> OS -> VMM -> VM;
比如 VMware Workstation (需要先啟動 Windows,再啟動 VMware 來啟動 Ubuntu);
Type 1 的虛擬機器監控程式可以視為一個為虛擬機器進行設計裁剪的作業系統核心,Type 2 的虛擬機器監控程式依賴於作業系統來進行排程和管理,所以會有限制性;
2.2 類/半虛擬化(Para virtulization)
完全虛擬化中會遇到一些,需要通過二進位制程式碼翻譯的方式來處理的不友好的特權指令集合,而類虛擬化採用另一種處理方式來解決這種問題;
類虛擬化(或稱之為半虛擬化)需要修改客戶機核心原始碼( API 級別),使得不再需要去模擬硬體裝置,取而代之的是通過呼叫這個特殊 API 來實現虛擬化 ;
在原始碼級別修改指令集,來避免虛擬化漏洞的方式,使得 VMM 能夠管理片上資源實現虛擬化;
而且這種情況下,客戶機(Guest OS)是知道自己是一個客戶機;
根據片上硬體資源,我們將逐步介紹 CPU 虛擬化 / 記憶體虛擬化 / IO 虛擬化 / GPU 虛擬化 / ..
3. 虛擬化的實現
3.1 CPU 虛擬化
3.1.1 Socket / Core / Thread, Physical / Logical CPU
在介紹 CPU 虛擬化之前,要了解 Socket / Core / Thread 以及 物理 / 邏輯 CPU 的概念:
- Socket / 插槽: 主機板上提供給一個物理封裝處理器的插槽;
- Core / 核心: 一個完整的一套暫存器,執行單元,訊息佇列,代表一個獨立的 CPU;
- Thread / 執行緒: 一個核心中有一個或者多個執行緒,執行緒是作業系統能夠進行運算排程的最小單元,是程式中的實際運作單位;
- Physical CPU: 每顆晶片上的物理 CPU 個數,Cores 數目,4C8T 有4個物理 CPU;
- Logical CPU: 考慮多執行緒,比如 4C8T,有8個 Logical CPU;
以 Intel i7-8809G 為例,是 4C8T,4核8執行緒,
因為支援超執行緒 (Hyper-threading),所以執行緒數是核心數的兩倍,4個 Physical CPU / 物理 CPU,8個 Logical CPU / 邏輯 CPU:
在 Linux 中 check CPU,可以得到 邏輯 CPU / 每個物理 CPU 上面的 cores / 每個物理 CPU 上面的邏輯 CPU
# Check physical CPUs echo "physical_cpu:" cat /proc/cpuinfo |grep "physical id"|sort |uniq |wc -l # 1, 一個物理 CPU,socket # Check logical CPUs echo "logical_cpu:" cat /proc/cpuinfo |grep "processor" -c # 8,4核8執行緒,8個邏輯 CPU # Check cores on each physical CPU (Hyper-threading not include) echo "core_per_phy_cpu:" cat /proc/cpuinfo |grep "core id" |sort |uniq |wc -l # 4,4個核心 cores # Check logical CPU nums on each physical CPU echo "logical_core_per_phy_cpu:" cat /proc/cpuinfo |grep "sib" |sort |uniq |awk -F ' ' '{print $3}' # 8,8個邏輯 CPU
3.1.2 CPU 虛擬化的例項
舉一個例子,有一條指令 "MOV CR0, EAX",也就是將 EAX 暫存器的值,傳給給暫存器 CR0;
3.1.2.1 無虛擬化
如果沒有 VMM,那麼處理器將這條指令丟給 VM,作業系統可以訪問物理處理器,處在最高特權模式,可以控制片上所有物理資源,直接對物理暫存器 CR0 進行賦值修改;
3.1.2.2 虛擬化引入
VMM 加入之後,我們的 VM 不是最高特權了,而 VMM 現在是最高特權,這時候對於片上關鍵資源的訪問,就成了敏感指令,VMM 對於這種敏感指令的執行,會觸發異常處理,從而陷入 VMM 進行模擬;
因為 VMM 的加入,所以會攔截掉處理器丟給 VM 的這條指令,讀取 EAX 的值然後放到記憶體中,虛擬的 vCR0 中,這樣的話執行該條 MOV 指令並不會改變真實的 CR0 的值;
下次如果要訪問 CR0 值的時候,VMM 進行截獲,返回的也是記憶體中虛擬的 vCR0 的值,而不是物理的 CR0;
3.2 記憶體虛擬化(Memory Virtualization)
3.2.1 無虛擬化
對於沒有虛擬化的 native 環境,作業系統 OS 對於記憶體的管理和使用需要滿足以下兩點:
- 記憶體都是從實體地址 0 開始;
- 記憶體是連續的,或者至少在一些大的粒度(如 256MB)上是連續的;
3.2.2 虛擬化引入
虛擬化的引入,也要滿足以上兩點,所以我們對於 VM,引入了虛擬的 客戶機實體地址空間 (Guest Physical Address, GPA) 概念;
關於地址和地址空間的介紹:
地址 是訪問 地址空間 的索引,可以分為:
- 邏輯地址
- 存在於 X86 機制中,程式直接使用的地址,由 16位段選擇符 和 23位偏移量 構成;
- 線性地址
- 又稱虛擬地址,是邏輯地址轉換後的結果,用於索引線性地址空間;當 CPU 使用 paging 分頁機制時,線性地址必須轉為實體地址才能訪問平臺記憶體/硬體資源
- 實體地址
- 用於索引實體地址空間;
- 分頁和分段機制都啟動:邏輯地址 -> 線性地址 -> 實體地址
- 分段啟動,分頁不啟動:邏輯地址 -> 線性地址 = 實體地址
Address Space, 地址空間
Memory, 記憶體可以視為一個大的陣列,地址就是這個大資料的索引;
而地址空間則是一個更大的陣列,是所有可用資源的集合,地址就是這個陣列的索引;
Address Space 可以分為兩類:
- Physical Address Space / 實體地址空間
- Linear Address Space / 線性地址空間
在虛擬化環境下,記憶體的排程使用,需要進行兩層轉換(GVA->GPA,GPA->HPA):
- 從 客戶機虛擬地址 (GVA, Guest Virtual Address) 到 客戶機實體地址 (GPA,Guest Physical Address)(由客戶機作業系統負責)
- 從 客戶機實體地址 (GPA,Guest Physical Address)到 宿主機實體地址 (HPA, Host Physical Address)(由 Hypervisor 負責)
所以記憶體虛擬化其實解決了如下兩個問題:
1. 虛擬機器維護 客戶機實體地址 / GPA 到 宿主機實體地址 / HPA 的對映;
2. 截獲 VM 對於 客戶機實體地址 / GPA 的訪問,並且根據對映關係,將其轉換為 宿主機實體地址 / HPA;
3.3 I/O 虛擬化
3.3.1 I/O 訪問方式
CPU 需要通過 I/O 來訪問外部資源,x86 中的 I/O 根據訪問方式不同,可以分為兩類:
- Port I/O,通過 I/O 埠號來訪問裝置暫存器;
- MMIO(Memory Map I/O),通過記憶體訪問的方式訪問裝置暫存器或者裝置 RAM;
3.3.2 DMA
引入 DMA (Direct Memory Access) / 直接記憶體讀取 的概念;
通過 DMA 控制器可以直接訪問硬體裝置資源,不需要 CPU 的參與(如果裝置向記憶體複製資料都要經過 CPU 的話,會佔用 CPU 時間降低系統效能);
根據 DMA 的特性,如果一個 I/O 裝置是支援 DMA 的,那麼我們可以繞過處理器來直接訪問目標記憶體(如果裝置的驅動未加修改,那麼裝置模擬器接收到的 DMA 目的地址就是客戶機的實體地址);
3.3.3 裝置模型(Device Model)
VMM 中要進行 I/O 裝置的模擬,並且要能夠處理和響應裝置的請求,這個功能由 裝置模型(Device Model) 來完成;
裝置模型 需要模擬出目標裝置的軟體介面和功能,獨立於裝置的驅動,通過下圖中這種呼叫方式:
裝置模型 是虛擬機器裝置驅動(Device Driver)和實際裝置驅動之間的一個模組;
當客戶機請求 I/O,作為核心模組的 VMM 會將 I/O 請求進行攔截,然後通過宿主機的核心態-使用者態介面,傳遞給使用者態的 裝置模型 進行處理;
3.3.5 Intel VT-d
無 VT-d 引入,那麼 I/O 裝置的 DMA 可以訪問整個實體記憶體;
如果我們引入 VT-d, Intel 的 VT-d 是從硬體上支援 I/O 虛擬化,在北橋上引入 DMA-Remapping (DMA 重對映)硬體,如下圖中的右圖(DMA-Remapping HW);
這樣的話,虛擬機器中對於 I/O 裝置的訪問,都會被 DMA 重對映硬體 截獲,然後查詢對應 I/O 裝置的頁表,重對映硬體對 DMA 中的地址進行轉換,而不是讓 I/O 裝置直接對實體記憶體直接訪問;
、