ARM 虚拟化

从软件开发者视角理解 ARMv7-A 虚拟化扩展的设计思想,以及 Hypervisor 如何借助硬件实现多个 Guest OS 的安全隔离。

1. 引言

现代 Cortex-A 处理器拥有越来越强的计算能力,但很多场景并不满足于”运行一个操作系统”。

例如,在一辆智能汽车中,娱乐系统通常运行 Linux,而制动、转向等安全关键任务则运行实时操作系统(RTOS)。两者需要共享同一套硬件平台,却又必须彼此隔离:Linux 的崩溃不能影响 RTOS,普通应用也不能访问安全关键资源。

类似的需求也广泛存在于工业控制、通信设备和云服务器中。它们希望:

  • 在一颗 CPU 上同时运行多个操作系统;
  • 每个操作系统拥有独立的地址空间和设备资源;
  • 一个系统发生故障,不影响其他系统继续运行。

最直接的办法当然是使用多颗 CPU,但这意味着更高的成本、更大的功耗以及更复杂的硬件设计。

于是,虚拟化提供了另一种思路:

让一颗物理 CPU 看起来像多颗独立的 CPU,每个操作系统都认为自己独占整个系统。

这就是虚拟化(Virtualization)的核心思想。

然而,这里马上会产生一个新的问题。

Linux 内核一直认为自己运行在处理器的最高特权级,可以自由访问所有内存、控制所有外设、响应所有异常。如果现在同时运行两个 Linux,它们都会认为自己拥有整个系统,那么冲突几乎不可避免。

因此,要实现虚拟化,仅仅依靠软件远远不够。处理器必须提供专门的硬件支持,让多个 Guest OS 都能够正常运行,却又始终处于 Hypervisor 的管理之下。

1.1 ARMv7-A 虚拟化扩展

ARMv7-A 虚拟化扩展并不是重新设计处理器,而是在原有架构基础上增加了一系列专门面向虚拟化的硬件能力,使多个 Guest OS 能够在同一套硬件上高效运行,并尽可能无需修改原有代码。

主要新增了以下四项能力:

  • Hyp Mode(PL2):新增比 PL1 更高的特权级,专门运行 Hypervisor,负责管理 Guest OS 和系统资源。
  • Stage 2 地址翻译:在传统 MMU 的基础上增加第二阶段地址翻译,使 Hypervisor 能够控制 Guest 对物理内存的访问。
  • 中断虚拟化:物理中断可以先路由到 Hypervisor,再由 Hypervisor 转发给对应的 Guest,实现多个 Guest 共享同一套中断控制器。
  • HVC(Hypervisor Call)指令:Guest OS 可通过 HVC 主动请求 Hypervisor 提供服务,类似于用户程序通过 SVC 调用操作系统。

这些硬件机制共同构成了 ARMv7-A 虚拟化扩展的基础。它们的目标只有一个:

让多个 Guest OS 都认为自己独占整台机器,而实际的资源分配和访问控制则全部由 Hypervisor 负责。

后续章节将分别介绍这些机制的工作原理,其中最核心的便是 Hyp Mode两阶段地址翻译(Stage 2 Translation)

1.2 Hypervisor 的两种部署方式

Hypervisor 根据自身所处的位置,通常分为两种类型。

  • Type 1(Bare Metal Hypervisor):Hypervisor 直接运行在硬件之上,最先接管处理器和外设,再启动各个 Guest OS。由于没有 Host OS 参与,资源控制能力强、开销低,因此广泛应用于服务器、汽车电子和工业控制等场景。
1
2
3
4
5
6
7
 Guest OS
Guest OS
Guest OS
───────────────
Hypervisor
───────────────
Hardware

常见实现包括 Xen、Jailhouse、VMware ESXi 等。


  • Type 2(Hosted Hypervisor):Hypervisor 作为普通应用运行在 Host OS 之上,由 Host OS 负责管理硬件资源,再由 Hypervisor 为 Guest OS 构建虚拟机。这种方式开发简单,更适合桌面开发和测试环境。
1
2
3
4
5
6
7
 Guest OS
───────────────
Hypervisor
───────────────
Host OS
───────────────
Hardware

典型代表包括 VirtualBox、VMware Workstation


不过,对于 ARMv7-A 虚拟化扩展来说,这种分类并不是重点。无论 Hypervisor 属于哪一种类型,它们最终都依赖处理器提供的 Hyp Mode、两级地址翻译和虚拟中断等硬件机制来管理 Guest OS。本文后续讨论的内容,同样适用于这两类 Hypervisor。


2. PL2 特权级别

在虚拟化扩展出现之前,ARMv7-A 的 Normal World 中只有两个特权级别:

特权级别 运行模式 典型软件
PL0 User (USR) 用户态应用程序
PL1 SVC, FIQ, IRQ, ABT, UND, SYS 操作系统内核

虚拟化扩展新增了一个更高的特权级——PL2,以及对应的 Hyp mode(模式编码 11010)。Hyp mode 仅存在于 Non-secure(Normal)World,专门用于运行 Hypervisor。

它具有以下几个特点:

  • 拥有独立的 SP_hypSPSR_hyp,与 PL1 的寄存器相互隔离。
  • 复用 User 模式的 LR 作为函数返回地址,并新增专用的 ELR_hyp 保存异常返回地址。
  • 只有 Hyp mode 才能访问虚拟化扩展提供的相关功能。

需要注意两点:

  1. SP_hypSPSR_hyp 是独立的 banked 寄存器,而 ELR_hyp 是 Hyp mode 专用的异常返回寄存器。
  2. Hyp mode 仅存在于 Normal World。与之对应,Monitor(MON)模式属于 Secure World 的 PL1,负责 TrustZone 的世界切换,两者职责完全不同。

下图展示了 Normal World 和 Secure World 中各特权级别的分布:

2.1 Bare Metal 虚拟化的启动流程

Type 1(Bare Metal) 虚拟化中,Hypervisor 是系统启动后运行的最高特权 Non-secure 软件。它首先完成自身初始化,然后为各个 Guest OS 建立运行环境,最后依次启动它们。

典型启动流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
Boot


Hypervisor(PL2)初始化
├─ 建立自身页表(HTTBR)
├─ 为各 Guest 建立 Stage 2 页表(VTTBR)
├─ 配置中断路由
└─ 启动各 Guest OS(进入 PL1)

├── Guest 1
├── Guest 2
└── ...

从 Guest OS 的视角看,它仍然运行在 PL1,并认为自己独占整台机器;而 Hypervisor 始终运行在 PL2,负责管理所有 Guest 以及底层硬件资源。


3. 两阶段地址翻译

虚拟化最大的挑战在于:Guest OS 认为自己拥有整个物理内存。

例如,Guest Linux 建立了一张页表,将虚拟地址映射到 0x80000000。如果处理器直接把这个地址当作真实物理地址访问,那么多个 Guest 就会读写同一块内存,彼此完全无法隔离。

因此,ARMv7-A 虚拟化扩展在传统 MMU 的基础上增加了第二阶段地址翻译(Stage 2 Translation)

在传统系统中,地址只需翻译一次:

1
VA ── Stage 1 ──→ PA

Stage 1 页表由操作系统维护,MMU 根据页表直接得到物理地址。

而在虚拟化系统中,地址需要经过两次翻译:

1
2
VA ── Stage 1 ──→ IPA ── Stage 2 ──→ PA
Guest OS Hypervisor

其中:

  • Stage 1 仍由 Guest OS 管理,将虚拟地址(VA)转换为中间物理地址(IPA)
  • Stage 2 由 Hypervisor 管理,再将 IPA 转换为真正的物理地址(PA)。

Guest OS 始终认为自己得到的是物理地址,实际上它看到的只是 IPA,真正的物理内存布局完全由 Hypervisor 决定。

也正因为如此,多个 Guest 即使都把自己的内核放在 0x80000000,最终仍可以映射到不同的物理内存,而彼此互不干扰。

注意:

  • Stage 2 的页表由 Hypervisor 创建和维护,但实际的 IPA → PA 转换由 MMU 硬件在 Guest 运行过程中自动完成,并不会进入 Hyp mode。

3.1 Stage 1 (PL1&0):Guest OS 眼中的”物理地址”

Stage 1 页表完全由 Guest OS 管理。Guest 像运行在裸机上一样,配置 TTBR0/TTBR1、建立页表、设置访问权限,MMU 的工作方式与传统系统没有任何区别。

唯一的不同在于,Stage 1 的输出不再是真正的物理地址(PA),而是中间物理地址(Intermediate Physical Address,IPA)

1
VA ── Stage 1(Guest OS)──→ IPA

虽然称为 IPA,但 Guest 并不知道它的存在。在 Guest 看来,Stage 1 输出的就是”物理地址”,它会像往常一样继续访问内存。

实际上,这个地址还会经过 Hypervisor 控制的 Stage 2 翻译,最终才能得到真正的物理地址(PA)。

因此,可以把 Stage 1 理解为Guest OS 眼中的地址翻译:Guest 始终认为自己控制着整个物理内存,而真正的物理内存布局则完全由 Stage 2 决定。

3.2 Stage 2 (PL1&0):Hypervisor 控制的 IPA → PA

Stage 2 由运行在 PL2 Hyp mode 的 Hypervisor 控制

Hypervisor 通过 VTTBR 指向 Stage 2 页表:

1
IPA ── Stage 2 ──→ PA

Stage 2 的作用是控制:

  • Guest 的物理地址空间;
  • Guest 可访问的内存范围;
  • 不同 Guest 之间的内存隔离。

例如:

1
2
3
4
5
6
              Stage 2
Guest A:
IPA 0x40000000 ───────────> PA 0x80000000

Guest B:
IPA 0x40000000 ───────────> PA 0x90000000

两个 Guest 可以拥有完全一样的 IPA 地址,但最终映射到不同的物理内存。

3.3 Stage 2 始终由 Hypervisor 控制

Suest OS 运行在 PL1,通过 SCTLR.M 控制自己的 Stage 1:

1
2
SCTLR.M = 1  →  Stage 1 enabled
SCTLR.M = 0 → Stage 1 disabled

而 Hypervisor 运行在 PL2 Hyp mode,通过 HSCTLRHCRVTTBR 等寄存器控制虚拟化环境。Stage 2 的启用和页表映射由 Hypervisor 管理,不受 Guest 的 SCTLR.M 影响。

因此,即使 Guest OS 关闭自己的 MMU,访问仍然需要经过 Hypervisor 控制的 Stage 2 转换。:

1
Guest Address ── Stage 2 ──> Physical Address

这正是 ARMv7-A 虚拟化隔离的关键:

  • Stage 1 负责 Guest OS 自己的虚拟地址空间;
  • Stage 2 负责 Hypervisor 对 Guest 物理地址空间的管理。

两者相互独立:

1
2
3
Guest PL1:  SCTLR.M  ──>  Stage 1  ──>  IPA

Hyp PL2: HCR + VTTBR ──> Stage 2 ──> PA

因此,Guest OS 虽然拥有完整的内存管理能力,但它管理的只是自己的地址视图;真实物理内存的分配和隔离始终由 Hypervisor 控制。

3.4 PL2 翻译机制:Hypervisor 自己的 VA → PA

Hypervisor 运行在 ARMv7-A 的 PL2 Hyp mode 下,同样需要访问内存。因此,ARM 为 Hyp 模式提供了独立的地址翻译机制。

与 Guest 不同,Hypervisor 不经过 Stage 2:

1
Hyp VA ── PL2 Translation ──> PA

PL2 使用 Hypervisor 自己维护的页表,由 HTTBR(Hyp Translation Table Base Register)指向。

因此,地址转换关系为:

1
2
3
4
5
6
7
8
Guest:

VA ── Stage 1 ──> IPA ── Stage 2 ──> PA


Hypervisor:

VA ── PL2 Translation ──> PA

Hypervisor 拥有整个系统的资源管理权限,因此不需要像 Guest 一样通过 Stage 2 虚拟化自己的物理地址视图。

3.5 三种翻译机制对比

1
2
3
4
5
6
7
8
9
模式              地址转换路径                                控制者
──────────────────────────────────────────────────────────────────────
PL1&0 (裸机) VA ── Stage 1 ──> PA OS

PL1&0 (虚拟化) VA ── Stage 1 ──> IPA ── Stage 2 ──> PA
Guest OS
+ Hypervisor

PL2 (Hyp) VA ── PL2 Translation ──> PA Hypervisor

这三种翻译机制共存于同一个 MMU 硬件中。

当处理器运行在不同模式下时,MMU 根据当前特权级别选择对应的页表和翻译路径:

  • Guest PL1/0 使用 Guest 的 Stage 1;
  • Hypervisor PL2 使用自己的 Hyp 页表;
  • Stage 2 只用于 Guest 对物理资源的访问。

因此,Guest OS 只看到自己的 Stage 1 地址空间,并不知道 Hypervisor 在背后额外进行了一次 IPA → PA 转换。


4. Stage 2 故障处理的细节

两级地址转换意味着故障也分属于不同层级。

Stage 1 和 Stage 2 的页表由不同的软件维护,因此对应的异常处理者也不同:

  • Stage 1 故障(例如 Guest 页表缺失、访问权限错误)
    → 进入 Guest OS 的 Abort handler,由 Guest 自己处理。
  • Stage 2 故障(例如 Hypervisor 未建立对应 IPA 的映射、访问权限不允许)
    → 进入 Hyp 模式,由 Hypervisor 捕获并处理。

例如:

1
2
3
4
5
Guest VA ── Stage 1 ──> IPA ── Stage 2 ──X
|
v
Hyp mode
(Hypervisor处理)

Stage 2 fault 对 Guest OS 是不可见的。Guest 只知道自己的内存访问失败,而具体原因(IPA 无映射、权限限制等)由 Hypervisor 决定。

Hypervisor 可以根据故障类型采取不同策略:

  • 对于合法场景(例如动态分配 Guest 内存),建立新的 Stage 2 映射后继续执行;
  • 对于非法访问或不可恢复错误,可以终止 Guest。

因此,两级翻译机制形成了清晰的故障隔离:

Guest OS 负责管理自己的 Stage 1 地址空间;Hypervisor 负责管理 Stage 2 映射以及对应的资源隔离。

简单来说:

谁维护页表,谁处理对应层级的故障。


5. Hypervisor 软件的职责全景

以”功能清单”的方式逐一列出了 Hypervisor 的职责。我将它们重新组织为六个核心维度来阐述。

5.1 内存管理

内存管理是 Hypervisor 最基础的任务。归纳了三层内存管理:

在 Secure/Non-secure 两套 TTBR 之外,还有 VTTBR(指向当前 Guest 的 Stage 2 页表)和 HTTBR(指向 Hypervisor 自身的 PL2 页表)。

寄存器 用途 格式
TTBR (Secure) Secure 世界 Stage 1 页表 短描述符 / 长描述符
TTBR (Non-secure) Guest OS 使用的 Stage 1 页表 短描述符 / 长描述符
VTTBR 指向当前 Guest 的 Stage 2 页表 仅长描述符
HTTBR 指向 Hypervisor 自身的 PL2 页表 仅长描述符

两个关键设计点:

  • VTTBR 和 HTTBR 强制使用 long-descriptor 格式(短描述符的最大物理地址范围不足以支持虚拟化场景)
  • Stage 2 的 Abort 都在 Hyp 模式处理——Hypervisor 可以决定是分配更多物理内存还是终止 Guest

实例:三个 Guest 的 Stage 2 页表共存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
物理内存布局 (1GB total):
PA 0x0000_0000 — 0x0FFF_FFFF → Hypervisor 自身使用
PA 0x1000_0000 — 0x3FFF_FFFF → Guest A 独占区
PA 0x4000_0000 — 0x5FFF_FFFF → Guest B 独占区
PA 0x6000_0000 — 0x7FFF_FFFF → Guest C 独占区

Guest A 的 Stage 2 页表 (VTTBR → 指向):
IPA 0x0000_0000–0x2FFF_FFFF → PA 0x1000_0000–0x3FFF_FFFF
(Guest A 看到的"物理内存" 0–768MB,实际在 PA 256MB–1GB)

Guest B 的 Stage 2 页表 (VTTBR → 指向):
IPA 0x0000_0000–0x1FFF_FFFF → PA 0x4000_0000–0x5FFF_FFFF
(Guest B 看到的"物理内存" 0–512MB,实际在 PA 1GB–1.5GB)

Guest C 的 Stage 2 页表 (VTTBR → 指向):
IPA 0x0000_0000–0x1FFF_FFFF → PA 0x6000_0000–0x7FFF_FFFF

切换 Guest 时: VTTBR 值更新 → 新的 Stage 2 页表生效 → 同名 IPA 映射到不同 PA
三个 Guest 都认为"物理内存从 0 开始",实际使用互不重叠的物理区域

5.2 设备模拟 (Device Emulation)

平台设备通常采用 Memory-mapped I/O(MMIO) 方式访问。Guest 访问设备寄存器地址时,该访问同样会经过 Stage 2 翻译。

如果 Hypervisor 没有在对应 IPA 区域的 Stage 2 页表中建立有效映射,MMU 会触发 Stage 2 Abort,随后陷入 Hyp 模式。

Hypervisor 在 Abort handler 中分析异常原因,识别出这是 Guest 对设备寄存器的访问后,可以通过软件模拟设备行为:

  • 返回虚拟寄存器值;
  • 模拟写入效果;
  • 更新设备状态;
  • 触发虚拟中断。

需要注意的是:

Stage 2 fault 并不是设备模拟本身,而是 Hypervisor 利用 Stage 2 fault 作为一种“陷入机制”,主动截获 Guest 对设备的访问。

设备模拟主要解决两个问题:

  1. 共享设备仲裁

    多个 Guest 都知道并使用同一个平台设备,但不能允许 Guest 直接访问真实硬件。

    Hypervisor 截获访问请求,通过软件模拟统一管理设备状态,避免多个 Guest 之间产生冲突。

  2. 设备隐藏

    某些设备不应该暴露给 Guest,例如:

    • 设备已经分配给其他 Guest;
    • 设备只允许 Hypervisor 使用;
    • 平台安全策略禁止 Guest 访问。

    此时 Hypervisor 可以不建立对应 Stage 2 映射,使访问触发 fault,并返回虚拟设备信息。

实例:Hypervisor 用 Stage 2 fault 模拟 UART 设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
平台 UART 真实地址: PA 0xE000_1000
Guest A 认为 UART 在: IPA 0x2000_0000
Guest B 认为 UART 在: IPA 0x2000_0000

Hypervisor 的策略: 不为 UART 区域在 Stage 2 页表中建立任何映射

Guest A 执行: STR R0, [0x2000_0000] (写 UART 数据寄存器)
Stage 1: VA → IPA 0x2000_0000 (成功)
Stage 2: IPA 0x2000_0000 → fault(未映射)→ 进入 Hyp mode
Hypervisor Abort handler:
1. 识别 fault address = 0x2000_0000 → 属于 UART 区域
2. 读取 Guest A 的上下文 → 知道 Guest A 在写数据
3. 软件模拟: 将数据写入真实 UART PA 0xE000_1000
4. 模拟 UART 状态寄存器变化 → 设置虚拟 IRQ (HCR.VI=1)
5. 返回 Guest A → Guest A 的写指令"成功完成"

结果: Guest A "以为"自己成功访问了地址 0x2000_0000 上的 UART
实际上 Hypervisor 软件完成了真正的硬件操作
Guest A 完全不知道这中间发生了什么

5.3 设备分配 (Device Assignment)

设备模拟功能强大,但代价高昂——每次 Guest 访问设备都必须陷入 Hyp 模式进行软件模拟。描述了另一种选择:

Hypervisor 还可以将设备”赠与”某个 Guest 独占——Guest 可以直接操作设备而无需 Hypervisor 介入,性能接近裸机。挑战在于通过 Stage 2 映射和中断虚拟化隐藏设备的真实物理地址和中断号。

设备分配的思路很简单:把设备”送给”某个 Guest 独占。挑战在于两点:

  • 设备的真实物理地址可能与 Guest 预期的不一致 → 通过 Stage 2 映射修正
  • 设备产生的中断 ID 可能与 Guest 预期的不一致 → 通过中断虚拟化修正
  • 一旦映射建立,Guest 就可以直接访问该设备,无需 Hypervisor 介入——性能接近裸机

通过 Stage 2 的透明映射和虚拟中断,Hypervisor 可以让 Guest 以为设备就在它期望的地址上。

实例:将 NIC 设备”赠与”Guest B 独占使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
真实 NIC 物理地址: PA 0xE000_2000
真实 NIC 中断号: IRQ #42
Guest B 期望 NIC 在: IPA 0x1C00_0000, Virtual IRQ #17

Hypervisor 配置 Stage 2 映射:
IPA 0x1C00_0000–0x1C00_0FFF → PA 0xE000_2000–0xE000_2FFF

Hypervisor 配置中断虚拟化:
Physical IRQ #42 → Virtual IRQ #17 → 注入 Guest B

结果:
Guest B 认为 NIC 位于 0x1C00_0000,中断号为 #17
实际访问的是 PA 0xE000_2000,中断号为 #42

Stage 2 隐藏设备地址差异,Interrupt virtualization 隐藏中断号差异。

5.4 异常处理与虚拟异常

ARM 虚拟化扩展引入了 虚拟异常(Virtual Exceptions) 的概念,这是虚拟化环境中隔离 Guest 与真实硬件的重要机制。

物理异常(Physical Exception) 是硬件真实产生的事件,例如:外设产生 IRQ/FIQ、MMU 触发 Abort、执行非法指令产生 Undefined Exception。

虚拟异常(Virtual Exception) 是由 Hypervisor 注入给 Guest 的异常事件。它可能对应真实的物理异常,也可能完全由 Hypervisor 软件模拟产生,例如模拟设备产生的虚拟中断。

关键区别在于:

  • 物理异常属于 Hypervisor 管理范围;
  • 虚拟异常属于 Guest OS 感知范围。

典型流程如下:

1
物理异常 ──> Hypervisor ──> 虚拟异常 ──> Guest OS

当真实物理异常发生后,Hypervisor 可以根据当前虚拟化策略决定:

  • 由自己处理;
  • 转换成虚拟异常注入 Guest;
  • 完全模拟一个不存在的硬件事件。

因此,Guest OS 看到的只是虚拟异常,并不知道底层真实硬件事件的存在。

例如:

  • 真实 UART 产生物理 IRQ;
  • Hypervisor 捕获该事件;
  • 更新虚拟 UART 状态;
  • 通过虚拟 GIC 注入 Virtual IRQ;
  • Guest UART 驱动收到中断。

从 Guest 角度看,它只是收到了一个普通设备中断。

实例:Hypervisor 利用 HCR 注入虚拟 IRQ 到 Guest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Hypervisor 收到物理 IRQ #37(以太网控制器)
|
v
判断该设备属于当前 Guest

Step 1 — Hypervisor 处理物理中断,并准备注入虚拟 IRQ
真实设备:
Physical IRQ #37
Guest 看到:
Virtual IRQ #17

Step 2 — Hypervisor 更新虚拟 GIC 状态
Virtual IRQ #17 = Pending
(虚拟 GIC 保存虚拟中断状态,
HCR.VI 用于控制虚拟 IRQ 路由)

Step 3 — 返回 Guest 执行
CPU 检测到:
- Virtual IRQ pending
- HCR 允许虚拟 IRQ 注入
- Guest CPSR.I 未屏蔽 IRQ

→ 向 Guest 注入 Virtual IRQ #17
Guest 进入 IRQ mode,
执行自己的 ISR handler

Step 4 — Guest 完成中断处理
Guest 写 Virtual GIC EOI
Virtual GIC 清除 Virtual IRQ #17 的 active/pending 状态
Guest 继续正常执行

同样,HCR.VF(bit 6)对应虚拟 FIQ,HCR.VI(bit 7)对应虚拟 IRQ,HCR.VA(bit 8)对应虚拟 Abort,这些位是 ARMv7-A 虚拟化扩展提供的虚拟异常注入机制。

下图汇总了物理中断从产生、被 Hypervisor 截获、到最终注入虚拟中断的完整路由路径:

5.5 中断虚拟化

虚拟化环境中的中断处理比裸机系统复杂,因为中断目标可能不是当前正在运行的 Guest。

例如:

  • 当前核心上正在运行的 Guest → Hypervisor 可以直接注入虚拟中断;
  • 另一个核心上的 Guest → Hypervisor 需要进行跨核调度和中断转发;
  • 已经挂起的 Guest → Hypervisor 需要保存中断状态,等待 Guest 下次运行时注入。

解决方案:Hypervisor 接管物理中断,并负责虚拟中断分发。

注入流程如下:

  1. 物理中断触发 → 根据配置进入 Hyp 模式 → Hypervisor 处理;
  2. Hypervisor 判断该中断属于哪个 Guest,以及 Guest 当前是否正在运行;
  3. Hypervisor 通过虚拟 GIC 设置对应的 Virtual IRQ/FIQ 状态,恢复 Guest 执行后,由硬件向 Guest 注入虚拟异常。
1
Physical IRQ ──> Hypervisor ──> Virtual GIC ──> Virtual IRQ ──> Guest OS

Guest OS 收到的是一个普通的中断事件,处理流程与裸机环境完全一致。它只知道自己收到了一次设备中断,并不知道该中断实际上经过了 Hypervisor 的转换和注入。

5.6 Guest 调度

Hypervisor 负责调度 Guest 的虚拟 CPU(vCPU)到物理 CPU 核心上,这与传统 OS 调度任务类似。

区别在于:

  • OS 调度的是进程/线程;
  • Hypervisor 调度的是 vCPU。

Guest OS 认为自己的 vCPU 就是真实 CPU,可以像裸机一样运行自己的调度器,管理内部线程和进程,而不知道底层物理 CPU 的分配情况。

调度链如下:

1
2
3
4
Hypervisor 调度 vCPU(粗粒度)
|
v
Guest OS 调度线程/进程(细粒度)

这种两级调度结构是虚拟化系统的典型特征:

  • Hypervisor 负责物理 CPU 资源分配;
  • Guest OS 负责自身任务调度。

Guest OS 只看到自己的 vCPU,并不知道它实际运行在哪个物理核心上。

5.7 上下文切换

当 Hypervisor 将一个 Guest 换出、切换到另一个 Guest 时,需要保存当前 Guest 的运行状态,并恢复目标 Guest 的上下文。

Guest 上下文切换需要保存和恢复:

  • 通用寄存器(包括各异常模式下的 banked 寄存器);
  • 系统寄存器(MMU、权限控制等配置);
  • 虚拟中断状态(pending / active);
  • 虚拟定时器状态。

上下文切换流程:

1
2
3
4
5
6
保存 Guest A 上下文  →  恢复 Guest B 上下文

├─ 通用寄存器 R0-R14 + banked 寄存器
├─ 系统寄存器 SCTLR、TTBR0/TTBR1、TTBCR、DACR 等
├─ 虚拟 GIC 中断状态(pending / active)
└─ 虚拟定时器寄存器

实例:一次完整的 Guest 上下文切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
切换场景:
Guest A (Linux) 被换出 → Guest B (RTOS) 被换入


Hypervisor 保存 Guest A 状态:

R0-R14
→ 保存到 Guest A 上下文区域

R13_usr、R13_svc、R13_irq、R13_abt、R13_und、R13_fiq
→ 保存各模式 Stack Pointer

SPSR_svc、SPSR_irq、SPSR_abt、SPSR_und、SPSR_fiq
→ 保存异常返回状态

TTBR0、TTBR1、TTBCR、DACR、SCTLR
→ 保存 Guest A 的 Stage 1 MMU 配置

Virtual GIC 状态
→ 保存 Guest A 的虚拟中断状态


恢复 Guest B:

加载 Guest B 上下文
更新 VTTBR 指向 Guest B 的 Stage 2 页表

ERET 返回 Guest B

注意:物理内存页本身不参与保存和恢复。

Stage 2 翻译保证不同 Guest 使用不同的物理内存区域,Guest 切换时只需要:

1
2
3
4
更新 VTTBR
|
v
切换到新的 Stage 2 地址空间

而不需要复制或搬移内存内容。

这也是虚拟化高效运行的关键:Hypervisor 切换的是 CPU 状态和地址映射关系,而不是整个 Guest 的内存。


6. 虚拟化与 TrustZone 安全扩展的关系

ARM 系统中存在两条正交的隔离轴:

  • 特权级别(Exception Level / Privilege Level)
  • 安全状态(Secure / Non-secure)

两者独立工作,共同决定当前代码运行环境。

在 ARMv7-A 中:

  • TrustZone 将系统划分为 Normal World 和 Secure World;
  • Virtualization Extension 在 Normal World 中增加 PL2 Hyp mode。

两者关系如下:

1
2
3
4
5
6
7
8
Normal World                  Secure World
───────────── ────────────
PL0: User PL0: User

PL1: SVC, ABT, IRQ, PL1: SVC, ABT, IRQ,
FIQ, UND, SYS FIQ, UND, SYS

PL2: Hyp Monitor mode

其中:

  • Hyp mode(PL2)属于 Normal World,用于运行 Hypervisor;
  • Monitor mode 属于 TrustZone,用于 Secure / Non-secure 世界切换。

关键结论:

Hypervisor 的控制范围仅限于 Normal World

它可以管理:

  • Guest OS;
  • Normal World 内存;
  • Normal World 设备;
  • 虚拟中断和 CPU 资源。

但它不能直接控制 Secure World:

  • 不能访问 Secure World 内存;
  • 不能修改 Secure OS 状态;
  • 不能替代 Monitor mode。

Secure World 由 Secure Monitor 管理,通过 SMC 指令完成 Normal World 与 Secure World 的切换。


7. LPAE:为虚拟化准备的地址空间扩展

虚拟化扩展要求处理器同时实现 LPAE(Large Physical Address Extension)

原因有两个:

  1. 多 Guest 运行会增加物理内存需求;
  2. 更重要的是,ARMv7-A 的 Stage 2 翻译依赖 LPAE 的 long-descriptor 格式

传统 ARMv7-A:

1
VA ── Stage 1 ──→ PA

物理地址通常限制在 32-bit(最大 4GB)。

而虚拟化环境:

1
VA ── Stage 1 ──→ IPA ── Stage 2 ──→ PA

需要 Hypervisor 管理更大的物理地址空间。

LPAE 将物理地址扩展到 40-bit(最大 1TB)

  • 虚拟地址仍保持 32-bit;
  • 物理地址扩展到 40-bit;
  • 支持 long-descriptor 页表格式;
  • 支持 Stage 2 地址翻译。

例如:

1
2
3
4
5
Guest A:
IPA 0x40000000 → PA 0x08_0000_0000

Guest B:
IPA 0x40000000 → PA 0x18_0000_0000

不同 Guest 可以拥有相同 IPA,而通过 Stage 2 映射到不同物理地址。


7.1 Long-Descriptor 格式

虚拟化 + LPAE 引入新的页表格式:

long-descriptor(64-bit 描述符)

与传统 short-descriptor 共存。

Long-descriptor 特性:

  • 64-bit 页表描述符;
  • 最多三级页表;
  • 支持 40-bit 物理地址;
  • 支持 1GB / 2MB / 4KB 页;
  • 支持 Stage 2 翻译;
  • 支持 PXN 权限控制;
  • 支持 Shareable 属性。

与短描述符类似:

  • 使用 TTBR0/TTBR1 指向页表;
  • 使用 TTBCR 控制地址空间划分。

区别:

  • Stage 1 可以使用 short 或 long descriptor;
  • Stage 2 只能使用 long descriptor。

7.2 Long-Descriptor 三级页表结构

对于 32-bit VA + 4KB 页

1
2
3
4
VA[31:30] → Level 1 索引
VA[29:21] → Level 2 索引
VA[20:12] → Level 3 索引
VA[11:0] → 页内偏移

页表结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Level 1
|
+-- Block descriptor → 1GB 区域
|
+-- Table descriptor
|
v
Level 2
|
+-- Block descriptor → 2MB 区域
|
+-- Table descriptor
|
v
Level 3
|
+-- Page descriptor → 4KB 页面

一次 Stage 1 翻译:

1
VA → L1 → L2 → L3 → PA

最多需要 3 次页表访问。

如果开启 Stage 2:

1
2
3
4
5
6
7
8
9
VA
|
Stage 1
|
IPA
|
Stage 2
|
PA

两个阶段都可能需要三级页表查询,因此最坏可能产生 6 次页表访问。

这也是为什么虚拟化系统依赖 TLB 缓存转换结果。


实例:一次 LPAE 地址翻译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VA = 0x0040_1234

Level 1:
VA[31:30] = 0
→ 查询 L1 Table[0]

Level 2:
VA[29:21] = 0x002
→ 查询 L2 Table[2]

Level 3:
VA[20:12] = 0x012
→ 查询 L3 Table[18]

得到:
VA 0x0040_1234 → PA 0x08_0000_2234

结果:

1
2
3 次页表查询完成地址翻译
输出 40-bit 物理地址

7.3 VMID:为虚拟化而生的 TLB 标签

虚拟化扩展在 VTTBR 中引入 VMID(Virtual Machine ID),用于区分不同 Guest 的 Stage 2 地址空间。

传统系统:

1
ASID + VA → PA

ASID 用于区分不同进程。

虚拟化后:

1
2
3
4
5
6
7
8
Stage 1:

ASID + VA → IPA


Stage 2:

VMID + IPA → PA

VMID 用于区分不同 Guest。

例如:

1
2
3
4
5
6
7
8
Guest A:
VMID=1
IPA 0x40000000 → PA 0x80000000


Guest B:
VMID=2
IPA 0x40000000 → PA 0x90000000

即使两个 Guest 使用相同地址,TLB 也不会混淆。

因此,切换 Guest 时无需刷新整个 TLB,只需要切换 VTTBR 指向新的 Stage 2 页表。

VMID 是 ARMv7-A 虚拟化能够高效运行的重要硬件支持。


8. 关键要点总结

  1. PL2 (Hyp mode) 是 ARMv7-A 虚拟化扩展的核心硬件增强——一个高于 Guest OS 的特权级别,仅存在于 Normal World,用于运行 Hypervisor。
  2. Stage 2 翻译是虚拟化隔离的核心——地址转换流程为:
1
VA → IPA → PA

其中 Stage 1 由 Guest OS 控制,将 VA 转换为 IPA;Stage 2 由 Hypervisor 控制,将 IPA 转换为真实物理地址。Guest OS 认为 IPA 就是自己的物理地址,无法感知 Stage 2 的存在。

  1. Stage 2 不受 Guest 控制——即使 Guest OS 关闭自己的 Stage 1(MMU disabled),Guest 的地址访问仍然受到 Hypervisor 管理的 Stage 2 约束。
  2. 三种地址翻译机制共存
  • PL1&0 Stage 1:Guest OS 控制,VA → IPA;
  • PL1&0 Stage 2:Hypervisor 控制,IPA → PA;
  • PL2 翻译:Hypervisor 自身使用,VA → PA。
  1. 虚拟异常是 Hypervisor 注入的事件——物理异常由 Hypervisor 根据配置进行处理,必要时向 Guest 注入虚拟 IRQ/FIQ/Abort,让 Guest 以为自己直接处理硬件异常。
  2. 中断虚拟化让 Hypervisor 管理物理中断分发——物理中断可以路由到 Hyp 模式,由 Hypervisor 判断归属,并转换为虚拟中断交付给对应 Guest。
  3. Guest 上下文切换只保存 CPU 状态,不搬移内存内容——切换时主要保存寄存器、系统寄存器、中断状态和定时器状态;通过切换 VTTBR 指向不同 Stage 2 页表,实现 Guest 内存隔离。
  4. Hypervisor 与 TrustZone 是两条独立隔离路径——运行在 Normal World PL2 的 Hypervisor 无法控制 Secure World,Secure World 由 Monitor 和 Secure OS 管理。
  5. LPAE 为虚拟化提供地址空间支持——将物理地址扩展到 40-bit(最大 1TB),并提供 long-descriptor 页表格式。Stage 2、VTTBR 和 HTTBR 使用 long-descriptor 格式。
  6. VMID 用于区分不同 Guest 的 Stage 2 地址空间——与 ASID 一起帮助 TLB 区分不同虚拟机和进程,使 Guest 切换时无需刷新整个 TLB,提高虚拟化性能。