深入分析Linux虛擬化KVM-Qemu之ARMv8虛擬化
說明:
KVM版本:5.9.1
QEMU版本:5.0.0
工具:Source Insight 3.5, Visio
1. 概述

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

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

當(dāng)異常發(fā)生時(shí),處理器將改變
Exception Level
(相當(dāng)于ARMv7中的處理器模式切換),來處理異常類型;圖中可以看出
Hypervisor
運(yùn)行在EL2
,而Guest OS
運(yùn)行在EL1
,可以通過HVC (Hypervisor Call)
指令向Hypervisor
請(qǐng)求服務(wù),響應(yīng)虛擬化請(qǐng)求時(shí)就涉及到了Exception Level
的切換;
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ??


2.2 Stage 2 translation
Stage 2轉(zhuǎn)換
與內(nèi)存虛擬化息息相關(guān),這部分內(nèi)容不僅包括常規(guī)的內(nèi)存映射訪問,還包含了基于內(nèi)存映射的I/O(MMIO
)訪問,以及系統(tǒng)內(nèi)存管理單元(SMMUs
)控制下的內(nèi)存訪問。
2.2.1 內(nèi)存映射
OS在訪問物理內(nèi)存前,需要先建立頁表來維護(hù)虛擬地址到物理地址的映射關(guān)系,看過之前內(nèi)存管理分析的同學(xué)應(yīng)該熟悉下邊這張圖,這個(gè)可以認(rèn)為是Stage 1轉(zhuǎn)換
:

當(dāng)有了虛擬機(jī)時(shí),情況就不太一樣了,比如Qemu運(yùn)行在Linux系統(tǒng)之上時(shí),它只是Linux系統(tǒng)的一個(gè)用戶進(jìn)程,
Guest OS
所認(rèn)為自己訪問的物理地址,其實(shí)是Linux的用戶進(jìn)程虛擬地址,到最終的物理地址還需要進(jìn)一步的映射;Hypervisor
可以通過Stage 2轉(zhuǎn)換
來控制虛擬機(jī)的內(nèi)存視圖,控制虛擬機(jī)是否可以訪問某塊物理內(nèi)存,進(jìn)而達(dá)到隔離的目的;

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

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

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

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

DMA控制器由內(nèi)核的驅(qū)動(dòng)程序來控制,能確保操作系統(tǒng)層面的內(nèi)存的保護(hù)不會(huì)被破壞,用戶程序無法通過DMA去訪問被限制的區(qū)域;
虛擬化下DMA控制器,VM中的驅(qū)動(dòng)直接與DMA控制器交互會(huì)出現(xiàn)什么問題呢?如下圖:

DMA控制器不受
Stage 2轉(zhuǎn)換
的約束,會(huì)破壞VM的隔離性;Guest OS以為的物理地址是IPA地址,而DMA看到的地址是真實(shí)的物理地址,兩者的視角不一致,為了解決這個(gè)問題,需要捕獲每次VM與DMA控制器的交互,并提供轉(zhuǎn)換,當(dāng)內(nèi)存出現(xiàn)碎片化時(shí),這個(gè)處理低效且容易引入問題;
SMMUs
可以用于解決這個(gè)問題:

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

在ARM處理器中執(zhí)行
WFI(wait for interrupt)
命令,可以讓CPU處于一個(gè)低功耗的狀態(tài);HCR_EL2(Hypervisor Control Register)
,當(dāng)該寄存器的TWI==1
時(shí),vCPU執(zhí)行WFI
指令會(huì)觸發(fā)EL2異常,從而Hypervisor
可以對(duì)其進(jìn)行模擬,將任務(wù)調(diào)度到另外一個(gè)vCPU即可;
捕獲(traps
)的另一個(gè)作用是可以用于向Guest OS呈現(xiàn)寄存器的虛擬值,如下:

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

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

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

簡(jiǎn)化之后是這樣的:

ARM體系架構(gòu)每個(gè)處理器都包含了一組通用定時(shí)器,從圖中可以看到兩個(gè)模塊:
Comparators
和Counter Module
,當(dāng)Comparators
的值小于等于系統(tǒng)的count值時(shí)便會(huì)產(chǎn)生中斷,我們都知道在操作系統(tǒng)中timer
的中斷就是系統(tǒng)的脈搏了;
下圖展示虛擬化系統(tǒng)中運(yùn)行的vCPU
的時(shí)序:

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

EL1物理時(shí)鐘
與系統(tǒng)計(jì)數(shù)器模塊直接比較,使用的是wall-clock
時(shí)間;EL1虛擬時(shí)鐘
與虛擬計(jì)數(shù)器比較,而虛擬計(jì)數(shù)器是在物理計(jì)數(shù)器上減去一個(gè)偏移;Hypervisor
負(fù)責(zé)為當(dāng)前調(diào)度運(yùn)行的vCPU
指定對(duì)應(yīng)的偏移,這種方式使得虛擬時(shí)間只會(huì)覆蓋vCPU
實(shí)際運(yùn)行的那部分時(shí)間;
來一張示例圖:

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

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

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

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

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

Host OS
運(yùn)行在EL2
還需要考慮異常處理的問題,前邊提到過HCR_EL2.IMO/FMO/AMO
的比特位可以用來控制物理異常路由到EL1/EL2
。當(dāng)運(yùn)行在EL0
且TGE==1
時(shí),所有物理異常都會(huì)被路由到EL2
(除了SCR_EL3控制的),這是因?yàn)?code>Host Apps運(yùn)行在EL0
,而Host OS
運(yùn)行在EL2
。
2.7 總結(jié)
本文涉及到內(nèi)存虛擬化(stage 2轉(zhuǎn)換),I/O虛擬化(包含了SMMU,中斷等),中斷虛擬化,以及指令
trap and emulation
等內(nèi)容;基本的套路就是請(qǐng)求虛擬化服務(wù)時(shí),路由到
EL2
去處理,如果有硬件支持的則硬件負(fù)責(zé)處理,否則可以通過軟件進(jìn)行模擬;
原文作者:LoyenWang
