天天看點

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化

背景

  • Read the fucking source code!

    --By 魯迅
  • A picture is worth a thousand words.

    --By 高爾基

說明:

  1. KVM版本:5.9.1
  2. QEMU版本:5.0.0
  3. 工具:Source Insight 3.5, Visio

1. 概述

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 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處理器的異常模式如下表所示:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化

然鵝,到了ARMv8,

Exception Level(EL)

取代了特權級,其中處理器的異常模式與

Exception Level

的映射關系如下圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 當異常發生時,處理器将改變

    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轉換

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 當有了虛拟機時,情況就不太一樣了,比如Qemu運作在Linux系統之上時,它隻是Linux系統的一個使用者程序,

    Guest OS

    所認為自己通路的實體位址,其實是Linux的使用者程序虛拟位址,到最終的實體位址還需要進一步的映射;
  • Hypervisor

    可以通過

    Stage 2轉換

    來控制虛拟機的記憶體視圖,控制虛拟機是否可以通路某塊實體記憶體,進而達到隔離的目的;
【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 整個位址的映射分成了兩個階段:
    1. Stage 1: VA(Virutal Address) -> IPA(Intermediate Physical Address)

      ,作業系統控制

      Stage 1轉換

      ;
    2. 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

    會整合成一個最終的也有效值,選擇限制更嚴格的屬性,如下圖:
【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 圖中的

    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

位址空間:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 通路外設有兩種情況: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;

軟體模拟外設的示例流程如下:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 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控制器的工作情況如下:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • DMA控制器由核心的驅動程式來控制,能確定作業系統層面的記憶體的保護不會被破壞,使用者程式無法通過DMA去通路被限制的區域;

虛拟化下DMA控制器,VM中的驅動直接與DMA控制器互動會出現什麼問題呢?如下圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • DMA控制器不受

    Stage 2轉換

    的限制,會破壞VM的隔離性;
  • Guest OS以為的實體位址是IPA位址,而DMA看到的位址是真實的實體位址,兩者的視角不一緻,為了解決這個問題,需要捕獲每次VM與DMA控制器的互動,并提供轉換,當記憶體出現碎片化時,這個處理低效且容易引入問題;

SMMUs

可以用于解決這個問題:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • SMMU

    也叫

    IOMMU

    ,對IO部件提供MMU功能,虛拟化隻是SMMU的一個應用;
  • Hypervisor

    可以負責對

    SMMU

    進行程式設計,以便讓上層的控制器和虛拟機VM以同一個視角對待記憶體,同時也保持了隔離性;

2.3 Trapping and emulation of Instructions

Hypervisor

也需要具備捕獲(

trap

)和模拟指令的能力,比如當VM中的軟體需要配置底層處理器來進行功耗管理或者緩存一緻性操作時,為了不破壞隔離性,

Hypervisor

就需要捕獲操作并進行模拟,以便不影響其他的VM。如果設定了捕獲某個操作時,當該操作被執行時會向更高一級的

Exception Level

觸發異常(比如

Hypervisor

為EL2),進而在相應的異常進行中完成模拟。

例子來了:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 在ARM處理器中執行

    WFI(wait for interrupt)

    指令,可以讓CPU處于一個低功耗的狀态;
  • HCR_EL2(Hypervisor Control Register)

    ,當該寄存器的

    TWI==1

    時,vCPU執行

    WFI

    指令會觸發EL2異常,進而

    Hypervisor

    可以對其進行模拟,将任務排程到另外一個vCPU即可;

捕獲(

traps

)的另一個作用是可以用于向Guest OS呈現寄存器的虛拟值,如下:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 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

吧,看過之前中斷子系統系列文章的同學,應該見過下圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • Hypervisor

    可以将GIC中的

    Virtual CPU Interface

    映射到VM中,進而允許VM中的軟體直接與GIC進行通信,

    Hypervisor

    隻需要進行配置即可,這樣可以減少虛拟中斷的overhead;

來個虛拟中斷的例子吧:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  1. 外設觸發中斷信号到GIC;
  2. GIC産生實體中斷

    IRQ

    或者

    FIQ

    信号,如果設定了

    HCR_EL2.IMO/FMO

    ,中斷信号将被路由到

    Hypervisor

    Hypervisor

    會檢查中斷信号轉發給哪個

    vCPU

  3. Hypervisor

    設定GIC,将該實體中斷信号以虛拟中斷的形式發送給某個

    vCPU

    ,如果此時處理器運作在EL2,中斷信号會被忽略;
  4. Hypervisor

    将控制權傳回給

    vCPU

  5. 處理器運作在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的内部:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化

簡化之後是這樣的:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • ARM體系架構每個處理器都包含了一組通用定時器,從圖中可以看到兩個子產品:

    Comparators

    Counter Module

    ,當

    Comparators

    的值小于等于系統的count值時便會産生中斷,我們都知道在作業系統中

    timer

    的中斷就是系統的脈搏了;

下圖展示虛拟化系統中運作的

vCPU

的時序:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 實體時間4ms,每個

    vCPU

    運作2ms,如果設定

    vCPU0

    T=0

    之後的3ms後産生中斷,那希望是實體時間的3ms後(也就是

    vCPU0

    的虛拟時間2ms)産生中斷,還是虛拟時間3ms後産生中斷?ARM體系結構支援這兩種設定;
  • vCPU

    上的軟體可以同時通路兩種時鐘:

    EL1實體時鐘

    EL1虛拟時鐘

EL1實體時鐘

EL1虛拟時鐘

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • EL1實體時鐘

    與系統計數器子產品直接比較,使用的是

    wall-clock

    時間;
  • EL1虛拟時鐘

    與虛拟計數器比較,而虛拟計數器是在實體計數器上減去一個偏移;
  • Hypervisor

    負責為目前排程運作的

    vCPU

    指定對應的偏移,這種方式使得虛拟時間隻會覆寫

    vCPU

    實際運作的那部分時間;

來一張示例圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 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

兩個比特位來控制,如下圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化

VHE

的引入,需要考慮虛拟位址空間的問題,如下圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 我們在記憶體子系統分析時提到過虛拟位址空間的問題,分為使用者位址空間(

    EL0

    )和核心位址空間(

    EL1

    ),兩者的區域不一緻,而在

    EL2

    隻有一個虛拟位址空間區域,這是因為

    Hypervisor

    不支援應用程式,是以也就不需要分成核心空間和使用者空間了;
  • EL0/EL1

    虛拟位址空間也同時支援

    ASID(Address Space Identifiers)

    EL2

    不支援,原因也是

    Hypervisor

    不需要支援應用程式;

從上兩點可以看出,為了支援

Host OS

能運作在

EL2

,需要添加一個位址空間區域,以及支援

ASID

,設定

HCR_EL2.E2H

的寄存器位可以解決這個問題,如下圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化

Host OS

EL2

需要解決的另一個問題就是寄存器通路重定向,在核心中需要通路

EL1

的寄存器,比如

TTBR0_EL1

,而當核心運作在

EL2

時,不需要修改核心代碼,可以通過寄存器的設定來控制通路流,如下圖:

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化
  • 重定向通路寄存器引入一個新的問題,

    Hypervisor

    在某些情況下需要通路真正的

    EL1

    寄存器,ARM架構引入了一套新的别名機制,以

    _EL12/_EL02

    結尾,如下圖,可以在

    ECH==1

    EL2

    通路

    TTBR0_EL1

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化

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

歡迎關注個人公衆号,不定期更新技術文章。

【原創】Linux虛拟化KVM-Qemu分析(二)之ARMv8虛拟化

作者:LoyenWang

出處:https://www.cnblogs.com/LoyenWang/

公衆号:

LoyenWang

版權:本文版權歸作者和部落格園共有

轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任

繼續閱讀