背景
Read the fucking source code!--By 魯迅A picture is worth a thousand words.--By 高爾基
說明:
- KVM版本:5.9.1
- QEMU版本:5.0.0
- 工具:Source Insight 3.5, Visio
1. 概述

- KVM虛擬化離不開底層硬件的支持,本文將介紹ARMv8架構處理器對虛擬化的支持,包括內存虛擬化、中斷虛擬化、I/O虛擬化等內容;
- ARM處理器主要用於移動終端領域,近年也逐漸往服務器領域靠攏,對虛擬化也有了較為完善的支持;
Hypervisor軟件,涵蓋的功能包括:內存管理、設備模擬、設備分配、異常處理、指令捕獲、虛擬異常管理、中斷控制器管理、調度、上下文切換、內存轉換、多個虛擬地址空間管理等;- 本文描述的ARMv8虛擬化支持,對於理解
arch/arm64/kvm下的代碼很重要,脫離硬件去看Architecture-Specific代碼,那是耍流氓;
開始旅程!
2. ARMv8虛擬化
2.1 Exception Level
- ARMv7之前的架構,定義了一個處理器的異常處理模式,比如
USR, FIQ, IRQ, SVC, ABT, UND, SYS, HYP, MON等,各個異常模式所處的特權級不一樣,比如USR模式的特權級就為PL0,對應為用戶態程序運行; - 處理器的異常模式可以在特權級軟件控制下進行主動切換,比如修改
CPSR寄存器,也可以被動進行異常模式切換,典型的比如中斷來臨時切換到IRQ模式;
ARMv7處理器的異常模式如下表所示:

然鵝,到了ARMv8,Exception Level(EL)取代了特權級,其中處理器的異常模式與Exception Level的映射關系如下圖:

- 當異常發生時,處理器將改變
Exception Level(相當於ARMv7中的處理器模式切換),來處理異常類型; - 圖中可以看出
Hypervisor運行在EL2,而Guest OS運行在EL1,可以通過HVC (Hypervisor Call)指令向Hypervisor請求服務,響應虛擬化請求時就涉及到了Exception Level的切換;
2.2 Stage 2 translation
Stage 2轉換與內存虛擬化息息相關,這部分內容不僅包括常規的內存映射訪問,還包含了基於內存映射的I/O(MMIO)訪問,以及系統內存管理單元(SMMUs)控制下的內存訪問。
2.2.1 內存映射
OS在訪問物理內存前,需要先建立頁表來維護虛擬地址到物理地址的映射關系,看過之前內存管理分析的同學應該熟悉下邊這張圖,這個可以認為是Stage 1轉換:

- 當有了虛擬機時,情況就不太一樣了,比如Qemu運行在Linux系統之上時,它只是Linux系統的一個用戶進程,
Guest OS所認為自己訪問的物理地址,其實是Linux的用戶進程虛擬地址,到最終的物理地址還需要進一步的映射; Hypervisor可以通過Stage 2轉換來控制虛擬機的內存視圖,控制虛擬機是否可以訪問某塊物理內存,進而達到隔離的目的;

-
整個地址的映射分成了兩個階段:
Stage 1: VA(Virutal Address) -> IPA(Intermediate Physical Address),操作系統控制Stage 1轉換;Stage 2: IPA(Intermediate Physical Address) -> PA(Physical Address),Hypervisor控制Stage 2轉換;
-
Stage 2轉換與Stage 1轉換機制很類似,不同點在於Stage 2轉換時判斷內存類型是normal還是device時,是存放進頁表信息里了,而不是通過MAIR_ELx寄存器來判斷; -
每個虛擬機(VM,Virtual Machine)都會分配一個
VMID,用於標識TLB entry所屬的VM,允許在TLB中同時存在多個不同VM的轉換; -
操作系統會給應用程序分配一個
ASID(Address Space Identifier),也可以用於標識TLB entry,屬於同一個應用程序的TLB entry都有相同的ASID,不同的應用程序可以共享同一塊TLB緩存。每個VM都有自己的ASID空間,通常會結合VMID和ASID來同時使用; -
Stage 1和Stage 2的轉換頁表中,都包含了屬性的相關設備,比如訪問權限,存儲類型等,在兩級轉換的過程中,MMU會整合成一個最終的也有效值,選擇限制更嚴格的屬性,如下圖:

- 圖中的
Device屬性限制更嚴格,則選擇Device類型; Hypervisor如果想要改變默認整合行為,可以通過寄存器HCR_EL2(Hypervisor Configuration Register)來配置,比如設置Non-cacheable,Write-Back Cacheable等特性;
2.2.2 MMIO(Memory-Mapped Input/Output)
Guest OS認為的物理地址空間,實際是IPA地址空間,就像真實物理機中一樣,IPA的地址空間,也分成內存地址空間和I/O地址空間:

- 訪問外設有兩種情況:1)直通訪問真實的外設;2)觸發
fault,Hypervisor通過軟件來模擬; VTTBR_EL2:Virtualization Translation Table Base Register,虛擬轉換表基地址寄存器,存放Stage 2轉換的頁表;- 為了模擬外設,
Hypervisor需要知道訪問的是哪個外設以及訪問的寄存器,讀訪問還是寫訪問,訪問長度是多少,使用哪些寄存器來傳送數據等。Stage 2轉換有一個專門的Hypervisor IPA Fault Address Register, EL2(HPFAR_EL2)寄存器,用於捕獲Stage 2轉換過程中的fault;
軟件模擬外設的示例流程如下:

- 1)虛擬機VM中的軟件嘗試訪問串口設備;
- 2)訪問時
Stage 2轉換被block住,並觸發abort異常路由到EL2。異常處理程序查詢ESR_EL2(Exception Syndrome Register)寄存器關於異常的信息,如訪問長度、目標寄存器,Load/Store操作等,異常處理程序還會查詢HPFAR_EL2寄存器,獲取abort的IPA地址; - 3)
Hypervisor通過ESR_EL2和HPFAR_EL2里的相關信息對相關虛擬外圍設備進行模擬,完成后通過ERET指令返回給vCPU,從發生異常的下一條指令繼續運行;
2.2.3 SMMUs(System Memory Management Units)
訪問內存的另外一種case就是DMA控制器。
非虛擬化下DMA控制器的工作情況如下:

- DMA控制器由內核的驅動程序來控制,能確保操作系統層面的內存的保護不會被破壞,用戶程序無法通過DMA去訪問被限制的區域;
虛擬化下DMA控制器,VM中的驅動直接與DMA控制器交互會出現什么問題呢?如下圖:

- DMA控制器不受
Stage 2轉換的約束,會破壞VM的隔離性; - Guest OS以為的物理地址是IPA地址,而DMA看到的地址是真實的物理地址,兩者的視角不一致,為了解決這個問題,需要捕獲每次VM與DMA控制器的交互,並提供轉換,當內存出現碎片化時,這個處理低效且容易引入問題;
SMMUs可以用於解決這個問題:

SMMU也叫IOMMU,對IO部件提供MMU功能,虛擬化只是SMMU的一個應用;Hypervisor可以負責對SMMU進行編程,以便讓上層的控制器和虛擬機VM以同一個視角對待內存,同時也保持了隔離性;
2.3 Trapping and emulation of Instructions
Hypervisor也需要具備捕獲(trap)和模擬指令的能力,比如當VM中的軟件需要配置底層處理器來進行功耗管理或者緩存一致性操作時,為了不破壞隔離性,Hypervisor就需要捕獲操作並進行模擬,以便不影響其他的VM。如果設置了捕獲某個操作時,當該操作被執行時會向更高一級的Exception Level觸發異常(比如Hypervisor為EL2),從而在相應的異常處理中完成模擬。
例子來了:

- 在ARM處理器中執行
WFI(wait for interrupt)命令,可以讓CPU處於一個低功耗的狀態; HCR_EL2(Hypervisor Control Register),當該寄存器的TWI==1時,vCPU執行WFI指令會觸發EL2異常,從而Hypervisor可以對其進行模擬,將任務調度到另外一個vCPU即可;
捕獲(traps)的另一個作用是可以用於向Guest OS呈現寄存器的虛擬值,如下:

ID_AA64MMFR0_EL1寄存器用於查詢處理器對內存系統相關特性的支持,系統可能在啟動階段會讀取該寄存器,Hypervisor可以向Guest OS呈現一個不同的虛擬值;- 當vCPU讀取該寄存器時,觸發異常,
Hypervisor在trap_handler中進行處理,設置一個虛擬值,並最終返回給vCPU; - 通過
trap來虛擬化一個操作需要大量的計算,包括觸發異常、捕獲,模擬、返回等一系列操作,像ID_AA64MMFR0_EL1寄存器訪問並不頻繁,這種方式問題不大。但是當需要頻繁訪問的寄存器,比如MIDR_EL1和MPIDR_EL1等,出於性能的考慮,應該避免陷入到Hypervisor中進行模擬處理,可以通過其他機制,比如提供VPIDR_EL2和VMIDR_EL2寄存器,在進入VM前就設置好該值,當讀取MIDR_EL1和MPIDR_EL1時,硬件就返回VPIDR_EL2和VMIDR_EL2的值,避免了陷入處理;
2.4 Virtualizing exceptions
Hypervisor對虛擬中斷的處理比較復雜,Hypervisor本身需要機制來在EL2處理中斷,還需要機制來將外設的中斷信號發送到目標虛擬機VM(或vCPU)上,為了使能這些機制,ARM體系架構包含了對虛擬中斷的支持(vIRQs,vFIQs,vSErrors);- 處理器只有在EL0/EL1執行狀態下,才能收到虛擬中斷,在EL2/EL3狀態下不能收到虛擬中斷;
Hypervisor通過設置HCR_EL2寄存器來控制向EL0/EL1發送虛擬中斷,比如為了使能vIRQ,需要設置HCR_EL2.IMO,設置后便會將物理中斷發送至EL2,然后使能將虛擬中斷發送至EL1;
有兩種方式可以產生虛擬中斷:1)在處理器內部控制HCR_EL2寄存器;2)通過GIC中斷控制器(v2版本以上);其中方式一使用比較簡單,但是它只提供了產生中斷的方式,需要Hypervisor來模擬VM中的中斷控制器,通過捕獲然后模擬的方式,會帶來overhead,當然不是一個最優解。
讓我們來看看GIC吧,看過之前中斷子系統系列文章的同學,應該見過下圖:

Hypervisor可以將GIC中的Virtual CPU Interface映射到VM中,從而允許VM中的軟件直接與GIC進行通信,Hypervisor只需要進行配置即可,這樣可以減少虛擬中斷的overhead;
來個虛擬中斷的例子吧:

- 外設觸發中斷信號到GIC;
- GIC產生物理中斷
IRQ或者FIQ信號,如果設置了HCR_EL2.IMO/FMO,中斷信號將被路由到Hypervisor,Hypervisor會檢查中斷信號轉發給哪個vCPU; Hypervisor設置GIC,將該物理中斷信號以虛擬中斷的形式發送給某個vCPU,如果此時處理器運行在EL2,中斷信號會被忽略;Hypervisor將控制權返回給vCPU;- 處理器運行在EL0/EL1時,虛擬中斷會被接受和處理
- ARMv8處理器中斷屏蔽由
PSTATE中的比特位來控制(比如PSTATE.I),虛擬化時比特位的作用有些不一樣,比如設置HCR_EL2.IMO時,表明物理IRQ路由到EL2,並且對EL0/EL1開啟vIRQs,因此,當運行在EL0/EL1時,PSTATE.I比特位針對的是虛擬vIRQs而不是物理的pIRQs。
2.5 Virtualizing the Generic Timers
先來看一下SoC的內部:

簡化之后是這樣的:

- ARM體系架構每個處理器都包含了一組通用定時器,從圖中可以看到兩個模塊:
Comparators和Counter Module,當Comparators的值小於等於系統的count值時便會產生中斷,我們都知道在操作系統中timer的中斷就是系統的脈搏了;
下圖展示虛擬化系統中運行的vCPU的時序:

- 物理時間4ms,每個
vCPU運行2ms,如果設置vCPU0在T=0之后的3ms后產生中斷,那希望是物理時間的3ms后(也就是vCPU0的虛擬時間2ms)產生中斷,還是虛擬時間3ms后產生中斷?ARM體系結構支持這兩種設置; - 運行在
vCPU上的軟件可以同時訪問兩種時鍾:EL1物理時鍾和EL1虛擬時鍾;
EL1物理時鍾和EL1虛擬時鍾:

EL1物理時鍾與系統計數器模塊直接比較,使用的是wall-clock時間;EL1虛擬時鍾與虛擬計數器比較,而虛擬計數器是在物理計數器上減去一個偏移;Hypervisor負責為當前調度運行的vCPU指定對應的偏移,這種方式使得虛擬時間只會覆蓋vCPU實際運行的那部分時間;
來一張示例圖:

- 6ms的時間段里,每個
vCPU運行3ms,Hypervisor可以使用偏移寄存器來將vCPU的時間調整為其實際的運行時間;
2.6 Virtualization Host Extensions(VHE)
- 先拋出一個問題:通常
Host OS的內核都運行在EL1,而控制虛擬化的代碼運行在EL2,這就意味着傳統的上下文切換,這個顯然是比較低效的; VHE用於支持type-2的Hypervisor,這種擴展可以讓內核直接跑在EL2,減少host和guest之間共享的系統寄存器數量,同時也減少虛擬化的overhead;
VHE由系統寄存器HCR_EL2的E2H和TGE兩個比特位來控制,如下圖:

VHE的引入,需要考慮虛擬地址空間的問題,如下圖:

- 我們在內存子系統分析時提到過虛擬地址空間的問題,分為用戶地址空間(
EL0)和內核地址空間(EL1),兩者的區域不一致,而在EL2只有一個虛擬地址空間區域,這是因為Hypervisor不支持應用程序,因此也就不需要分成內核空間和用戶空間了; EL0/EL1虛擬地址空間也同時支持ASID(Address Space Identifiers),而EL2不支持,原因也是Hypervisor不需要支持應用程序;
從上兩點可以看出,為了支持Host OS能運行在EL2,需要添加一個地址空間區域,以及支持ASID,設置HCR_EL2.E2H的寄存器位可以解決這個問題,如下圖:

Host OS運行在EL2需要解決的另一個問題就是寄存器訪問重定向,在內核中需要訪問EL1的寄存器,比如TTBR0_EL1,而當內核運行在EL2時,不需要修改內核代碼,可以通過寄存器的設置來控制訪問流,如下圖:

- 重定向訪問寄存器引入一個新的問題,
Hypervisor在某些情況下需要訪問真正的EL1寄存器,ARM架構引入了一套新的別名機制,以_EL12/_EL02結尾,如下圖,可以在ECH==1的EL2訪問TTBR0_EL1:

Host OS運行在EL2還需要考慮異常處理的問題,前邊提到過HCR_EL2.IMO/FMO/AMO的比特位可以用來控制物理異常路由到EL1/EL2。當運行在EL0且TGE==1時,所有物理異常都會被路由到EL2(除了SCR_EL3控制的),這是因為Host Apps運行在EL0,而Host OS運行在EL2。
2.7 總結
- 本文涉及到內存虛擬化(stage 2轉換),I/O虛擬化(包含了SMMU,中斷等),中斷虛擬化,以及指令
trap and emulation等內容; - 基本的套路就是請求虛擬化服務時,路由到
EL2去處理,如果有硬件支持的則硬件負責處理,否則可以通過軟件進行模擬; - 盡管本文還沒涉及到代碼分析,但是已經大概掃了一遍了,大體的輪廓已經了然於胸了,說了可能不信,我現在都有點小興奮了;
參考
《ArmV8-A virtualization.pdf》
《vm-support-ARM-may6-2019.pdf》
《aarch64_virtualization_100942_0100_en.pdf》
《ARM Cortex-A Series Programmer's Guide for ARMv8-A》
arm64: Virtualization Host Extension support
歡迎關注個人公眾號,不定期更新技術文章。

