2. ARM64架构基础

在嵌入式虚拟化工程中,ARM64 并不是“把 x86 的虚拟化概念平移过来”那么简单。AArch64 执行状态、EL0~EL3 异常等级、系统寄存器、弱内存模型以及多级页表,共同构成了 KVM/ARM 在底层运行、陷入、模拟和隔离的硬件基础。理解这些机制,才能看懂 KVM 为什么在异常入口处保存哪些寄存器、为什么需要 stage-2 页表、为什么必须插入内存屏障。

一、AArch64 执行状态与异常等级

AArch64 是 ARMv8-A 引入的 64 位执行状态,提供 31 个 64 位通用寄存器(X0~X30)、独立的 SP_ELx 栈指针、PC 以及 PSTATE。与 AArch32 不同,AArch64 的异常等级 EL0~EL3 是权限与异常处理的核心分层:EL0 是用户态,EL1 通常运行 Guest OS 内核或 Host 内核,EL2 是虚拟化扩展层,EL3 是安全监控层。KVM 在 ARM64 上通常运行于 EL2,Guest 内核运行于 EL1,Guest 用户进程运行于 EL0。

异常等级决定了“谁能访问哪些系统寄存器、谁能执行哪些特权指令”。例如,Guest 内核在 EL1 执行 WFI、访问 TTBR0_EL1 时,若未开启对应 trap,则直接在 EL1 生效;若 KVM 设置了 HCR_EL2.TWI 等陷入位,则会触发异常进入 EL2,由 KVM 决定是模拟还是注入。EL2 与 EL1 之间的切换由 ERET、SVC、HVC、IRQ、FIQ 等异常向量驱动。

AArch64 异常等级与 KVM 运行位置 EL0 用户态:Guest 应用 / Host 用户进程,无特权,不能直接访问系统寄存器 EL1 Guest 内核 / Host 内核:页表基址、异常向量、系统寄存器访问 EL2 KVM 虚拟化层:HCR_EL2 陷入控制、stage-2 页表、vCPU 上下文切换 EL3 安全监控层:TrustZone 切换、安全世界与非安全世界隔离 SVC / IRQ / 缺页 HVC / 陷入 SMC 异常等级越高权限越大;KVM 利用 EL2 截获 Guest 的敏感操作并模拟

二、系统寄存器与陷入控制

ARM64 的系统寄存器是虚拟化控制的关键抓手。KVM 通过 HCR_EL2(Hypervisor Configuration Register)决定哪些操作会从 EL1 陷入到 EL2。常见控制位包括:TWI 陷入 WFI、TWE 陷入 WFE、TSC 陷入 SMC、IMO/FMO 路由 IRQ/FIQ、VM 开启 stage-2 页表。此外,VBAR_EL2 指定 EL2 异常向量基址,ESR_EL2 记录异常原因,FAR_EL2 记录出错地址,HPFAR_EL2 记录 stage-2 缺页的 IPA。

在 vCPU 切换时,KVM 需要保存和恢复 TTBR0_EL1、TTBR1_EL1、TCR_EL1、MAIR_EL1、SCTLR_EL1 等寄存器,否则 Guest 的地址翻译上下文会互相污染。下面是一段典型的 vCPU 进入 Guest 前的寄存器准备逻辑:

/* 简化示意:KVM/ARM64 vcpu_enter_guest 前的关键寄存器设置 */
write_sysreg(read_sysreg(HCR_EL2) | HCR_VM | HCR_TWI | HCR_TWE, HCR_EL2);
write_sysreg(vcpu->arch.vttbr, VTTBR_EL2);      /* stage-2 页表基址 */
write_sysreg(vcpu->arch.vtcr, VTCR_EL2);        /* stage-2 翻译控制 */
write_sysreg(vcpu->arch.ttbr0, TTBR0_EL1);      /* Guest 用户页表 */
write_sysreg(vcpu->arch.ttbr1, TTBR1_EL1);      /* Guest 内核页表 */
write_sysreg(vcpu->arch.tcr,   TCR_EL1);
write_sysreg(vcpu->arch.mair,  MAIR_EL1);
write_sysreg(vcpu->arch.sctlr, SCTLR_EL1);
isb();                                          /* 确保系统寄存器生效 */

三、内存模型与页表机制

ARM64 采用弱内存模型,CPU 和编译器都可能对访存指令重排。虚拟化场景下,Guest 与 Host 共享内存、vCPU 与物理 CPU 并发访问,必须使用 dmb、dsb、isb 等屏障指令保证顺序。例如,在修改 stage-2 页表后,需要执行 dsb 再 tlbi,最后 isb,否则可能读到旧翻译。

页表方面,ARM64 支持 4KB、16KB、64KB 三种粒度,常用 4KB 粒度下采用 4 级或 3 级页表。虚拟化引入两阶段翻译:stage-1 由 Guest 的 TTBR0_EL1/TTBR1_EL1 完成 VA→IPA,stage-2 由 VTTBR_EL2 完成 IPA→PA。KVM 通过 stage-2 页表实现 Guest 物理地址的隔离与映射,缺页时产生 stage-2 fault,由 KVM 填充页表或注入异常。

机制 关键寄存器/指令 在 KVM 中的作用
异常等级 EL0~EL3、PSTATE 划分 Guest 用户、Guest 内核、KVM、安全监控权限
陷入控制 HCR_EL2、ESR_EL2 截获 WFI/WFE/SMC/IRQ 等,交给 KVM 模拟
两阶段翻译 TTBR0/1_EL1、VTTBR_EL2 VA→IPA→PA,实现 Guest 内存隔离
内存屏障 dmb、dsb、isb 保证页表修改、vCPU 切换的顺序可见性
缺页处理 FAR_EL2、HPFAR_EL2 定位 stage-2 缺页地址并填充映射

四、典型场景与常见误区

假设 Guest 内核在 EL1 执行 WFI 进入空闲。若 HCR_EL2.TWI=1,该指令会陷入 EL2,KVM 捕获后可将 vCPU 标记为阻塞,调度到其他 vCPU,从而避免物理 CPU 空转。若未设置 TWI,Guest 的 WFI 会直接在 EL1 生效,Host 可能失去调度机会。这就是“陷入控制位决定虚拟化行为”的典型体现。

常见误区:把 EL2 当成“另一个内核态”而忽略 HCR_EL2 的细粒度控制;或在修改 stage-2 页表后只执行 tlbi 而遗漏 dsb/isb,导致 TLB 与页表不一致;又或者认为 Guest 的 TTBR0_EL1 可以直接被 Host 复用,造成地址空间串扰。

调试建议:遇到 Guest 异常时,优先查看 ESR_EL2 的 EC 字段判断异常类型,再结合 FAR_EL2、HPFAR_EL2 定位地址;若怀疑内存序问题,可在关键路径插入 dsb sy 验证是否消失。理解 EL0~EL3、系统寄存器、弱内存模型与两阶段页表,是后续阅读 KVM/ARM64 源码和排查虚拟化故障的基本功。