参考
1、ARMv8 Virtualization Extension Overview
ARMv8 为了支持虚拟化扩展在 CPU 的运行级别上引入了 Exception Level(异常级别)的概念,AArch64 对应的 Exception Level 视图如下图:

- EL0:用户态程序的运行级别,Guest 内部的 App 也运行在这个级别
- EL1:内核的运行级别,Guest 的内核也运行在这个级别
- EL2:Hypervisor 的运行级别,Guest 在运行的过程中会触发特权指令后陷入到 EL2 级别,将控制权交给 Hypervisor
- EL3:Monitor Mode,CPU 在 Secure World 和 Normal World 直接切换的时候会先进入 EL3,然后发生 World 切换
注:当 CPU 的 Virtualization Extension 被 disable 的时候,软件就运行在 EL0 和 EL1 上,这时候 EL1 有权限访问所有的硬件。
2、Stage 2 translation
2.1 What is stage 2 translation
在 ARMv8-A 上,每个地址翻译域(Translation Regime)可能包括 1 个 stage,也可能包括 2 个 stage。 每个异常级别 (Exception Level)都有自己的地址翻译机制,使用不同的页表基地址(如 TTRBx_ELx)寄存器 。通常情况下,大部分 EL 仅包含一个 stage 的翻译过程, 而 Non-Secure EL1&0 包括了 2 个 stage 的地址翻译过程。 每个 stage 都有自己独立的一系列 Translation tables,每个 stage 都能独立的 enable 或者 disable。 每个 stage 都是将输入地址(IA)翻译成输出地址(OA)。

- VA (Virtual Address): 指令中使用的地址(如数据或指令地址),即虚拟地址。
- IPA (Intermediate Physical Address): 中间物理地址。如果不使能 Stage 2 翻译,IPA 即为最终的物理地址(PA)。
- PA (Physical Address): CPU 发送给内存控制器的最终物理地址。
在虚拟化场景下,ARM 的解决方案与 x86 类似,均采用了两阶段地址翻译来实现从客户机虚拟地址(GVA)到宿主机物理地址(HPA)的映射。其核心流程如下:
- stage 1 翻译(GVA -> GPA): 虚拟机运行在 Non-secure EL1&0。当虚拟机内的进程访问其虚拟地址(GVA)时,MMU 会将其翻译为中间物理地址(IPA),在虚拟化语境下通常称为客户机物理地址(GPA)。这一过程由虚拟机的页表控制,负责将客户机视角的虚拟内存映射到客户机视角的物理内存。
- stage 2 翻译(GPA -> HPA): 随后,MMU 会再次介入,将 IPA(GPA)翻译为宿主机的物理地址(HPA)。这一过程由 Hypervisor 控制的 Stage 2 页表完成。
💡 为什么需要 stage 2?
你可能会问,stage 1 翻译已经将虚拟地址映射到了"物理地址",为什么还需要 stage 2?
答案在于安全与隔离 。stage 1 翻译完全由虚拟机内部控制,它产生的"物理地址"(即 GPA/IPA)只是 hypervisor 分配给它的"影子地址"。如果缺乏 stage 2 的介入,虚拟机可能会试图访问真实的硬件资源 ,或者越界访问其他虚拟机的内存。因此,stage 2 转换的存在,是为了让 hypervisor 能够掌握最终解释权,控制虚拟机的内存视图。
stage 2 转换允许 hypervisor 精确地控制虚拟机的内存视图。具体而言,hypervisor 可以决定哪一块真实的系统资源允许 VM 访问,以及这些资源在 VM 看来位于哪一块地址空间。这种对内存访问的精细控制对于实现系统的隔离性 (Isolation)和沙盒 (Sandbox)特性至关重要。通过 stage 2 转换,系统能够确保 VM 只能访问到被明确分配给它的物理资源,从而保障了整个系统的安全。

2.2 TTBRx_ELx register
了解了 stage 1 和 stage 2 的翻译流程后,我们不禁要问:硬件 MMU 是如何知道去哪里查找这些转换表的呢?这就涉及到了页表基地址寄存器(TTBR)。这些寄存器保存了各级页表的起始物理地址,是地址翻译过程的"指路牌"。
在 ARMv8 的地址翻译机制中,Translation Table Base Register (TTBR) 扮演着至关重要的角色。它存储了当前翻译表层级结构(Translation Table Hierarchy)在内存中的起始物理地址。
根据不同的异常级别(Exception Level)和翻译阶段(stage),ARMv8 定义了不同的 TTBR 寄存器来分别管理页表基地址:
- TTBR0_EL1 / TTBR1_EL1 :
用于 stage 1 翻译,运行在 EL1(通常是操作系统内核)。ARMv8 将虚拟地址空间分为两部分,TTBR0_EL1通常指向低地址区域的页表,而TTBR1_EL1指向高地址区域的页表(就是常说的 Linux 中的 User Space 和 Kernel Space)。 - TTBR0_EL2 :
用于 stage 1 翻译,运行在 EL2(Hypervisor)。当 EL2 自身需要访问内存时(例如 Hypervisor 自己的代码和数据),使用此寄存器。 - TTBR0_EL3 :
用于 stage 1 翻译,运行在 EL3(安全监控模式)。 - VTTBR_EL2 (Virtualization Translation Table Base Register):
这是虚拟化扩展引入的关键寄存器 。它专门用于 stage 2 翻译。hypervisor 通过配置VTTBR_EL2,告诉硬件去哪里查找 stage 2 的页表,从而完成从 IPA 到 HPA 的转换。

通过灵活配置这些寄存器,ARMv8 能够支持复杂的虚拟化内存管理,确保每个层级都能独立且正确地管理其地址空间。
2.3 VMIDs
上文提到的 VTTBR_EL2 寄存器不仅保存了 stage 2 页表的基地址,它还承载着另一个关键信息------虚拟机标识符(VMID)。在虚拟化环境中,仅仅知道页表位置是不够的,Hypervisor 还需要一种机制来高效地区分不同虚拟机的地址翻译缓存(TLB 表项)。这就是 VMID 的作用所在。
在虚拟化场景下,多个虚拟机(VM)共享同一个物理处理器核心。如果每次发生虚拟机切换(Context Switch)时都强制刷新翻译后备缓冲器(TLB),将会带来巨大的性能开销。为了解决这个问题,ARMv8 引入了 VMID 机制。
VMID 的作用
VMID 被用来标记 TLB 中的条目。当 MMU 进行 stage 2 地址翻译时,硬件会自动比对 "当前 VTTBR_EL2 寄存器中的 VMID" 与 "TLB 条目中记录的 VMID"。
- 隔离性: 只有 VMID 匹配的 TLB 条目才会被使用,从而确保了不同虚拟机之间的地址翻译互不干扰。
- 性能优化: 这种标记机制允许不同虚拟机的翻译条目同时存在于 TLB 中。这意味着在虚拟机切换时,无需刷新 TLB,极大地降低了上下文切换的开销。
VMID 存储在 VTTBR_EL2 寄存器的特定比特位中。根据 ARM 架构的版本,VMID 的长度有所不同:
- Armv8.0: 支持 8 位 VMID。
- Armv8.1-A 及以后: 增加了对 16 位 VMID 的可选支持,这允许系统中支持更多的虚拟机而无需重新分配 ID。
VMID 的具体行为(如 8 位或 16 位模式)通常由 VTCR_EL2(虚拟化翻译控制寄存器)中的 VS 位进行控制。


2.4 VMID interaction with ASIDs
在讨论完用于隔离虚拟机的 VMID 后,我们还需要关注另一个用于隔离用户进程的机制 ------地址空间标识符(ASID)。虽然 VMID 解决了不同虚拟机之间的缓存冲突,但在虚拟机内部,操作系统同样需要高效地管理多个应用程序的并发运行,这就是 ASID 发挥作用的地方。
ASID 的作用
TLB 条目不仅可以被 VMID 标记,还可以被 ASID 标记。ASID 通常由操作系统分配给应用程序,所有属于该应用程序的 TLB 条目都会被标记上这个 ASID。
- 进程级隔离: 这意味着不同应用程序的 TLB 条目可以在 TLB 中共存,而不会发生冲突。当进程切换时,操作系统只需更新
TTBR0_EL1中的 ASID,而无需刷新整个 TLB,从而显著提高了上下文切换的性能。 - 层级关系: 你可以将 ASID 理解为"进程ID"的硬件缓存标签,而 VMID 则是"虚拟机ID"的硬件缓存标签。


VMID 与 ASID 的协同
每个虚拟机都有自己独立的 ASID 命名空间。例如,虚拟机 A 和虚拟机 B 可能都使用了 ASID 5,但它们指向的是完全不同的进程地址空间。
因此,在支持虚拟化的 ARM 系统中,真正唯一标识一个 TLB 条目的是 ASID 和 VMID 的组合。
- 硬件逻辑: 硬件在进行 TLB 查找时,会同时检查 VMID(确保属于当前虚拟机)和 ASID(确保属于当前进程)。
- 寄存器配合:
- VMID 存储在
VTTBR_EL2中,由 Hypervisor 管理。 - ASID 存储在
TTBR0_EL1中,由虚拟机内部的操作系统管理。
- VMID 存储在
这种双层标记机制(VMID + ASID)使得 ARM 处理器能够在多级虚拟化环境中,实现极高效率的 TLB 管理和上下文切换。
2.5 Attribute combining and overriding
stage 1 和 stage 2 映射都包含属性,例如存储类型,访问权限等。内存管理单元(MMU)会将两个阶段的属性整合成一个最终属性,整合的原则是选择更有限制的属性。
MMU 的合并原则是"取其严者",也就是选择限制性更强的那个属性。正如你在这里看到的例子:

在这个示例中,Device(设备)类型的限制性要强于 Normal(普通)类型。因此,最终合并出的类型就是 Device。即使我们将示例反过来,让第一阶段 = Normal,第二阶段 = Device,最终的结果依然是一样的。
这种属性合并机制适用于绝大多数使用场景,但在某些特定情况下,Hypervisor 可能希望打破这种常规行为。例如在虚拟机(VM)的早期启动阶段。针对这些情况,系统提供了一些控制位来覆盖默认行为:
- HCR_EL2.CD: 控制所有 stage 1 属性为 Non-cacheable。
- HCR_EL2.DC:强制所有 stage 1 属性为 Normal,Write-Back Cacheable。
- HCR_EL2.FWB (Armv8.4-A引入):使用 stage 2 属性覆盖 stage 1 属性,而不是使用默认的限制性整合原则
2.6 Emulating Memory-mapped Input/Output (MMIO)
与物理机器的物理地址空间类似,VM 的 IPA 地址空间包含了内存 与外围设备两种区域。如下图所示

虚拟机可以利用外设区域来访问两种设备:一种是真实存在的物理外设(通常称为"直通设备 "),另一种是"虚拟外设"。
虚拟外设完全由 Hypervisor 通过软件模拟实现,正如下面的图示所强调的:

直通设备 是指已经分配给虚拟机并映射到其地址空间中的真实物理设备。这使得运行在虚拟机内的软件可以直接与该外设进行交互。
虚拟外设 则是由 Hypervisor 通过软件模拟的设备。在地址转换的第二阶段页表中,对应虚拟外设的表项会被标记为"触发异常"。虚拟机中的软件认为自己正在直接与外设通信,但每一次访问都会触发第二阶段异常,随后由 Hypervisor 在异常处理程序中模拟该外设的访问行为。
为了模拟一个外设,Hypervisor 不仅需要知道访问了哪个外设,还需要知道访问了该外设中的哪个寄存器、是读操作还是写操作、访问的数据大小,以及用于传输数据的寄存器信息。
首先是地址信息。在异常模型中,我们介绍了 FAR_ELx 寄存器。在处理第一阶段异常时,这些寄存器会报告触发异常的虚拟地址。但这对 Hypervisor 来说帮助不大,因为 Hypervisor 通常不知道客户机操作系统是如何配置其虚拟地址空间的。而对于第二阶段异常,还有一个额外的寄存器 HPFAR_EL2,它会报告导致异常的中间物理地址(IPA)。由于中间物理地址空间是由 Hypervisor 控制的,因此它可以利用这一信息来确定需要模拟的是哪个寄存器。

异常模型展示了 ESR_ELx 寄存器如何报告异常信息。对于那些触发第二阶段异常的、涉及单个通用寄存器的加载或存储操作,系统会提供额外的"综合征信息"。这些信息包括访问的数据大小以及源寄存器或目标寄存器,使 Hypervisor 能够确定对虚拟外设进行的是何种类型的访问。
关于 ESR_ELx 寄存器后面章节会有详细介绍,这个寄存器很重要
下图说明了"捕获并模拟访问"的整个过程:

这一过程可以通过以下步骤来描述:
- 虚拟机内的软件尝试访问虚拟外设。在本例中,目标是虚拟 UART 的接收 FIFO(先入先出队列)。
- 该访问请求在第二阶段地址转换时被拦截,导致产生一个路由到 Hypervisor 的异常。
a. 该异常会自动向ESR_EL2寄存器写入有关异常的信息,包括访问的字节数、目标寄存器以及操作类型(是加载还是存储)。
b. 该异常还会自动向HPFAR_EL2寄存器写入导致中止的访问所对应的 IPA。 - Hypervisor 利用来自
ESR_EL2和HPFAR_EL2的信息来识别被访问的虚拟外设寄存器。这些信息使 Hypervisor 能够模拟该操作(模拟操作就会把 Hypervisor 想让 vCPU 看到的结果写到 x0 中),随后通过 ERET 指令返回到虚拟 CPU。
c. 系统从触发异常的 LDR(加载)指令的下一条指令处恢复执行。
2.7 System Memory Management Units (SMMUs)
到目前为止,我们主要讨论了源自处理器的各类内存访问。然而,系统中的其他总线主控设备(masters),例如 DMA 控制器,也可能被分配给虚拟机(VM)使用。因此,我们需要一种方法将第二阶段(stage 2)的内存保护机制同样扩展到这些设备上。
非虚拟化环境下的 DMA

考虑一个未启用虚拟化的系统中的 DMA 控制器。该控制器通常由内核空间的驱动程序进行编程配置。内核驱动能够确保不违反操作系统级别的内存保护策略,这意味着普通应用程序无法利用 DMA 越权访问其本不应看到的内存区域。
虚拟化环境下的挑战

现在让我们看看在虚拟机中运行操作系统的情况。此时,Hypervisor 利用 stage 2 地址转换来提供 VM 之间的隔离,软件对内存的可见性完全受限于 Hypervisor 控制的 stage 2 页表。
允许 VM 内的驱动程序直接与 DMA 控制器交互会引发两大核心问题:
- 隔离性(Isolation):DMA 控制器本身不受 stage 2 页表的约束。如果缺乏额外保护,它可能被用来突破 VM 的沙箱限制,访问宿主机或其他 VM 的内存。
- 地址空间不一致(Address space):在两级地址转换架构下,VM 内核所认为的"物理地址(PA)"实际上是"中间物理地址(IPA)"。然而,DMA 控制器看到的仍然是真实的系统物理地址(PA)。这导致内核与 DMA 控制器对内存的认知出现了偏差。为了解决这个问题,Hypervisor 可以拦截(trap)VM 与 DMA 之间的每一次交互并进行地址转换。但在内存碎片化严重的情况下,这种拦截模拟的方式效率极低且难以维护。
SMMU/IOMMU:硬件层面的解决方案
与其通过软件拦截和模拟驱动程序的访问,更优的替代方案是将 stage 2 的保护机制扩展到包括 DMA 在内的其他主控设备。这就要求这些设备也配备 MMU,即所谓的系统内存管理单元(SMMU),在业界也常被称为 IOMMU。

Hypervisor 将负责配置 SMMU,使得上游主控设备(如本例中的 DMA)看到的内存视图与其所分配的 VM 完全一致。这一机制完美解决了上述两个难题:
- SMMU 能够强制执行 VM 间的隔离,确保外部主控设备无法被利用来突破沙箱。
- SMMU 为 VM 内的软件和分配给该 VM 的外部主控设备提供了一致且统一的内存视图。
值得注意的是,虚拟化并非 SMMU 的唯一应用场景,但由于篇幅所限,本指南将不涵盖其他非虚拟化用例。
3、Trapping and emulation of instructions
在某些情况下,Hypervisor 需要对虚拟机(VM)内部的操作进行模拟。例如,VM 内的软件可能会尝试配置与电源管理或缓存一致性相关的底层处理器控制寄存器。通常情况下,我们绝不能赋予 VM 直接访问这些控制的权 限,因为这可能会被利用来破坏隔离性,甚至影响系统中的其他 VM。
什么是陷阱?
陷阱是一种机制,当执行特定动作(例如读取某个寄存器)时,会强制触发一个异常。Hypervisor 需要利用这种能力来拦截 VM 中配置底层控制的操作,并在不干扰其他 VM 的前提下对其进行模拟。
ARM 架构包含陷阱控制功能,用于拦截 VM 内的操作并由 Hypervisor 进行模拟。一旦设置了陷阱,执行原本被允许的操作就会触发异常,并将控制权移交给更高异常级别(Exception Level)的软件。Hypervisor 正是利用这些陷阱来实现对 VM 内部操作的模拟。
实例解析:WFI 指令
以执行"等待中断(WFI)"指令为例。通常情况下,执行该指令会让 CPU 进入低功耗状态。
-
机制:通过置位
HCR_EL2.TWI位(即设置HCR_EL2.TWI==1),当 EL0 或 EL1 级别执行 WFI指令时,CPU 不会进入休眠,而是会触发一个异常并跳转到 EL2。
-
应用:在虚拟机环境中,客户机操作系统(Guest OS)通常会在空闲循环中执行 WFI 指令。如上图所示,Hypervisor 可以拦截这一操作,并利用这段空闲时间在物理 CPU 上调度另一个虚拟 CPU(vCPU)运行,从而极大地提高了资源利用率。

注意:陷阱机制并非专为虚拟化设计,系统中也存在由 EL3 和 EL1 控制的陷阱。然而,陷阱对于虚拟化软件而言尤为关键。本指南将仅讨论那些通常与虚拟化相关的陷阱。
3.1 Presenting virtual values of registers
利用陷阱机制的另一个典型示例是呈现寄存器的"虚拟值"。例如,ID_AA64MMFR0_EL1 寄存器用于报告处理器对内存系统相关特性的支持情况。操作系统通常会在启动阶段读取此寄存器,以确定在内核中启用哪些特性。而 Hypervisor 可能希望向客户机操作系统呈现一个不同的值,即所谓的"虚拟值"。
为了实现这一点,Hypervisor 会启用针对该寄存器读取操作的陷阱。当触发陷阱异常时,Hypervisor 会判断是哪个陷阱被触发,随后模拟该操作。在此示例中,Hypervisor 会将 ID_AA64MMFR0_EL1 的虚拟值填充到目标寄存器中,如下所示:

陷阱机制也可用于惰性上下文切换。例如,操作系统通常会在启动期间初始化内存管理单元配置寄存器(TTBR<n>_EL1、TCR_EL1 和 MAIR_EL1),此后便不再重新编程它们。Hypervisor 可以利用这一特性来优化其上下文切换例程,即在上下文切换时仅恢复这些寄存器,而无需保存它们。
然而,操作系统可能会在启动后执行某些非常规操作并重新编程这些寄存器。为了避免由此引发问题,Hypervisor 可以设置 HCR_EL2.TVM 陷阱。该设置会导致任何对内存管理单元(MMU)相关寄存器的写入操作都生成一个进入 EL2 的陷阱,从而允许 Hypervisor 检测是否需要更新其保存的这些寄存器副本。
注意:ARM 架构使用"陷阱(trapping)"和"路由(routing)"这两个术语来指代独立但相关的概念。回顾一下,陷阱是指在执行给定操作(例如读取寄存器)时引发异常。而路由则是指异常生成后被路由到的异常等级
3.2 MIDR and MPIDR
利用陷阱机制来虚拟化某个操作需要消耗大量的计算资源。该过程涉及操作触发进入 EL2 的陷阱异常,随后 Hypervisor 需判定所需操作、执行模拟,最后再返回客户机。像 ID_AA64MMFR0_EL1 这样的特性寄存器,操作系统访问频率并不高。这意味着,通过陷阱将这些寄存器的访问拦截至 Hypervisor 进行模拟读取,其计算开销是可以接受的。
然而,对于那些访问频繁或处于性能关键代码路径中的寄存器,你需要避免此类计算负载。这类寄存器及其值的示例包括:
- MIDR_EL1:处理器类型,例如 Cortex-A53
- MPIDR_EL1:亲和性信息,例如处理器 2 的核心 1
Hypervisor 可能希望客户机操作系统看到这些寄存器的虚拟值,但又无需对每次访问都进行陷阱拦截。针对这些寄存器,架构提供了一种替代陷阱的机制:
- VPIDR_EL2:这是针对 EL1 读取
MIDR_EL1时应返回的值。 - VMPIDR_EL2:这是针对 EL1 读取
MPIDR_EL1时应返回的值。
Hypervisor 可以在进入虚拟机之前配置这些寄存器。如果虚拟机内部运行的软件读取 MIDR_EL1 或 MPIDR_EL1,硬件将自动返回虚拟值,而无需触发陷阱。
注意:VMPIDR_EL2 和 VPIDR_EL2 没有定义的复位值。它们必须在首次进入 EL1 之前由启动代码进行初始化。这在裸机环境中尤为重要
4、Virtualizing the Generic Timers
4.1 Physical Generic Timers
核心寄存器组成与架构视图

ARM 物理定时器架构分为全局系统计数器 和每核私有定时器两部分。主要涉及以下几类寄存器:
- 控制寄存器 (CNTP_CTL_EL0)------【每核私有】
- 负责定时器的启停控制
- 管理中断状态和屏蔽
- 提供定时器状态查询
- 比较值寄存器 (CNTP_CVAL_EL0)------【每核私有】
- 存储 64 位比较值
- 与物理计数器比较产生中断
- 支持原子更新操作
- 定时值寄存器 (CNTP_TVAL_EL0)------【每核私有】
- 提供 32 位递减视图
- 简化定时器编程接口
- 自动转换为比较值
- 物理计数寄存器 (CNTPCT_EL0)------ 【全局唯一】
- 64 位单调递增计数器
- 频率通常为1-50 MHz
- 提供系统时间基准
注意,虽然寄存器的后缀是 _EL0,但是 EL1 等更高层级都是可以正常访问的
主要原理(两种使用方式):
绝对时间触发中断
使用 CNTPCT_EL0、CNTP_CVAL_EL0 寄存器。CNTPCT_EL0 寄存器按照相应的时钟频率单调递增。当其值大于或等于 CNTP_CVAL_EL0 寄存器中的值时,便会触发中断。软件通过写入 CNTP_CVAL_EL0 寄存器来设定中断触发时间
相对时间触发中断
使用 CNTPCT_EL0、CNTP_TVAL_EL0 寄存器。CNTPCT_EL0 寄存器按照相应的时钟频率单调递增。软件写 CNTP_TVAL_EL0 寄存器的值 X 时,硬件会自动执行以下操作:
- 读取当前的系统计数值 (
CNTPCT_EL0)。 - 计算一个绝对的目标时间:目标时间 = 当前系统计数值 + X。
- 将这个计算出的"目标时间"自动写入到
CNTP_CVAL_EL0寄存器中
4.2 Virtual Generic Timers
下图展示了一个托管两个虚拟 CPU 的 Hypervisor 系统示例:

注意:在此示例中,我们忽略了 Hypervisor 在虚拟 CPU 之间进行上下文切换的开销。
经过 4ms 的物理时间(即挂钟时间)后,每个虚拟 CPU 各运行了 2ms。如果虚拟 CPU0 在 T=0 时设置其比较器以在 3ms 后生成中断,你预期该中断会触发吗?或者,你是希望在虚拟时间(即虚拟 CPU 所经历的时间)2ms 后触发中断,还是在挂钟时间 2ms 后触发?
Arm 架构提供了实现这两种计时的能力,具体取决于虚拟化的用途。让我们来看看它是如何做到的。
运行在虚拟 CPU 上的软件可以访问两个定时器:
- EL1 物理定时器(4.1 小节所讲的物理定时器)
- EL1 虚拟定时器
- CNTV_CVAL_EL0, Counter-timer Virtual Timer CompareValue register
- CNTV_TVAL_EL0, Counter-timer Virtual Timer TimerValue register
- CNTVCT_EL0, Counter-timer Virtual Count register
Hypervisor 也有自己的定时器:
- CNTHP_CVAL_EL2, Counter-timer Hypervisor Physical Timer CompareValue register
- CNTHP_TVAL_EL2, Counter-timer Hypervisor Physical Timer TimerValue register
从这里也可以看出,只有 CNTPCT_EL0 只读寄存器的时间,是全局唯一物理时间,是墙上时间,且 EL0、EL1、EL2 都可以访问
EL1 虚拟定时器与一个虚拟计数值(CNTVCT_EL0)进行比对。该虚拟计数值等于物理计数值(CNTPCT_EL0)减去一个偏移量(CNTVOFF_EL2)。
CNTVCT_EL0, Counter-timer Virtual Count register

可以看到,这寄存器是只读寄存器。该寄存器的值 = CNTPCT_EL0 寄存器的值 - CNTVOFF_EL2 寄存器的值。

Hypervisor 的核心职责就是管理 CNTVOFF_EL2 寄存器,以此来为每个虚拟机创造独立且连续的时间视图,隐藏虚拟 CPU 未被调度运行期间的时间流逝。
- 创建虚拟机:当一个虚拟机启动时,Hypervisor 会读取当前的物理时间
CNTPCT_EL0,然后将CNTVOFF_EL2设置为相同的值。根据上面的公式,虚拟机读到的CNTVCT_EL0初始值就是 0,仿佛它从一个干净的时间起点开始运行。 - 暂停与恢复:这是虚拟化时间管理最巧妙的地方。
- 当虚拟机被暂停时,Hypervisor 会记录下当时的物理时间。
- 当虚拟机被恢复时,Hypervisor 会计算从暂停到恢复这段时间物理世界流逝了多久,然后更新
CNTVOFF_EL2的值,抵消掉这段"暂停"的时间。
通过这种方式,虚拟机完全感知不到自己曾被暂停过,它看到的 CNTVCT_EL0 时间流是平滑、连续的,这保证了其内部应用(如数据库、定时任务)的稳定运行。
注意,ARM 架构只是提供这种功能。具体是否使用,要根据软件设计者的需求。例如,某些虚拟化架构,根本用不到
CNTVOFF_EL2,直接使用CNTPCT_EL0寄存器值即可。
5、ARMv8 异常处理关键寄存器(EL2 视角)
ELR_EL2 (Exception Link Register)
作用:
- 保存发生异常时的下一条指令地址
- 用于异常处理完成后返回
使用方式:
- 异常进入 EL2 时自动写入
- 返回时通过 ERET 使用
典型场景:
Hypervisor 捕获 trap 后,处理完再返回 Guest

ESR_ELx (Exception Syndrome Register)
作用:
- 描述异常类型 + 具体原因
- 是异常分析最关键的寄存器
关键字段:
- EC(Exception Class)
- 异常大类(如 SVC、Data Abort、HVC 等)
- ISS(Instruction Specific Syndrome)
- 更细节的信息(如访问权限、指令编码等)
可以理解为:
"为什么会触发异常?"
举例:
- Guest 执行 HVC → trap 到 EL2
- 非法内存访问(Data Abort)
- 执行特权指令


- EC(Exception Class):异常类型。表示当前产生异常的原因。例如:
- EC == 000001:Trapped WFI or WFE instruction execution
- EC == 010101:SVC instruction execution in AArch64 state
- EC == 010110:HVC instruction execution in AArch64 state
- EC == 100100:Data Abort from a lower Exception level
- EC == 100000:Instruction Abort from a lower Exception level
- ... ...
- ISS(Instruction Specific Syndrome):指令特征码,用于记录异常发生的具体诊断信息或被拦截指令的操作参数,以辅助异常处理程序精准定位原因或模拟执行。例如:
- ISS encoding for an exception from SMC instruction execution in AArch64 state,保存 SMC 指令的参数

- ISS encoding for an exception from a Data Abort,保存产生异常时的详细信息。包括
- DFSC (Data Fault Status Code,bit[5:0]):数据故障状态码,指出具体的错误原因(如翻译错误、权限错误、地址大小错误等)。
- WnR (Write not Read,bit[6]):指示该异常是由写操作(1)还是读操作(0)引起的。
- SAS (Size of Access):指示发生异常时的数据访问大小(如字节、半字、字等)


- ... ...
- ISS encoding for an exception from SMC instruction execution in AArch64 state,保存 SMC 指令的参数
FAR_EL2 (Fault Address Register)
作用:
- 保存导致异常的内存地址
只在某些异常有效:
- Data Abort
- Instruction Abort
可以理解为:
"访问哪个地址出问题了?"
