Armv8-A virtualization 笔记 (二)

参考:《Learn the architecture - Generic Interrupt Controller v3 and v4, Virtualization》

阅读本文前,建议先阅读:ARM GICv3 学习笔记(一)

本文以 GICv3 架构为例,讨论 ARMv8-A 虚拟化场景下的中断处理机制。默认读者已具备 ARM GICv3 基础知识。

1、ARMv8 异常模型概述

ARMv8 的异常分为两大类:

同步异常(Synchronous)

由指令执行直接产生,可预测且精确。异常返回地址指向触发该异常的指令。典型场景:

  • 未定义指令、指令/数据中止、对齐错误
  • 系统调用(SVC / HVC / SMC)
  • 断点/观察点(BKPT)

异步异常(Asynchronous)

不由当前指令流直接产生,不可预测。异常返回地址不保证指向触发源。典型场景:

  • IRQ(普通中断)
  • FIQ(快速中断)
  • SError(系统错误:总线错误、奇偶校验错误等)

1.1 异常向量表

ARMv8 的异常向量表按以下维度组织:异常来源(当前级别同栈 / 当前级别独立栈 / 低级别 AArch64 / 低级别 AArch32)× 异常类型(Sync/IRQ/FIQ/SError),共 16 个表项。每个表项 128 字节,基址由 VBAR_ELx 寄存器指定。


四种异常来源的含义:

  • Current Exception level with SP_EL0:异常处理程序运行在 EL1/2/3,但栈指针借用了 EL0 的 SP(不常见
  • Current Exception level with SP_ELx:异常处理程序运行在 EL1/2/3,且栈指针使用自己级别的 SP_ELx
  • Lower Exception level, where the implemented level immediately lower than the target level is using AArch64:异常来自更低的异常级别,且那个更低的级别运行在 AArch64(64位) 状态下
  • Lower Exception level, where the implemented level immediately lower than the target level is using AArch32:异常来自更低的异常级别,且那个更低的级别运行在 AArch32(32位) 状态下

Linux 内核的向量表实现在 arch/arm64/kernel/entry.S

c 复制代码
SYM_CODE_START(vectors)
	kernel_ventry	1, sync_invalid			// Synchronous EL1t
	kernel_ventry	1, irq_invalid			// IRQ EL1t
	kernel_ventry	1, fiq_invalid			// FIQ EL1t
	kernel_ventry	1, error_invalid		// Error EL1t

	kernel_ventry	1, sync				// Synchronous EL1h
	kernel_ventry	1, irq				// IRQ EL1h
	kernel_ventry	1, fiq_invalid			// FIQ EL1h
	kernel_ventry	1, error			// Error EL1h

	kernel_ventry	0, sync				// Synchronous 64-bit EL0
	kernel_ventry	0, irq				// IRQ 64-bit EL0
	kernel_ventry	0, fiq_invalid			// FIQ 64-bit EL0
	kernel_ventry	0, error			// Error 64-bit EL0

#ifdef CONFIG_COMPAT
	kernel_ventry	0, sync_compat, 32		// Synchronous 32-bit EL0
	kernel_ventry	0, irq_compat, 32		// IRQ 32-bit EL0
	kernel_ventry	0, fiq_invalid_compat, 32	// FIQ 32-bit EL0
	kernel_ventry	0, error_compat, 32		// Error 32-bit EL0
#else
	kernel_ventry	0, sync_invalid, 32		// Synchronous 32-bit EL0
	kernel_ventry	0, irq_invalid, 32		// IRQ 32-bit EL0
	kernel_ventry	0, fiq_invalid, 32		// FIQ 32-bit EL0
	kernel_ventry	0, error_invalid, 32		// Error 32-bit EL0
#endif
SYM_CODE_END(vectors)

1.2 向量基址寄存器 VBAR

每个异常级别有独立的向量基址寄存器:

  • VBAR_EL1 --- EL1 向量表基址
  • VBAR_EL2 --- EL2 向量表基址
  • VBAR_EL3 --- EL3 向量表基址

VBAR_EL1 为例,其低 11 位固定为 0(保证 2KB 对齐),高位存储向量表物理基址。异常发生时,处理器根据异常来源和类型计算偏移,跳转到对应表项:

PC = VBAR_ELx + (2^11 × 向量表索引)

2、GICv3 CPU Interface 的三组寄存器

在虚拟化场景下,GICv3 的 CPU Interface 寄存器被划分为三组:

寄存器组 命名格式 用途
ICC (Physical) ICC_*_ELx 处理物理中断的物理 CPU 接口寄存器
ICH (Hypervisor Control) ICH_*_EL2 Hypervisor 用于控制虚拟化特性的寄存器,仅在 EL2 可访问
ICV (Virtual) ICV_*_EL1 Guest OS 看到的虚拟 CPU 接口寄存器

下图展示了三组寄存器与 Physical PE / Virtual PE 的对应关系:

:Physical PE 和 Virtual PE 在硬件上共享同一个物理核心。IRQ/vIRQ 和 FIQ/vFIQ 并非独立的物理导线,而是通过寄存器状态模拟出的逻辑信号。图中拆分绘图仅为在逻辑上区分"宿主环境"与"虚拟环境",便于理解。

2.1 ICC 寄存器(Physical CPU Interface)

Hypervisor 在 EL2 使用标准的 ICC_*_ELx 寄存器处理物理中断,与非虚拟化场景无异。在 EL3 访问 ICC 寄存器同样总是访问物理接口。

2.2 ICH 寄存器(Virtualization Control)

Hypervisor 通过 ICH_*_EL2 寄存器控制虚拟化相关特性:

  • 启用/禁用虚拟 CPU Interface
  • 访问虚拟寄存器状态以支持 vPE 上下文切换
  • 配置维护中断(Maintenance Interrupt)
  • 通过 List Register 控制当前 vPE 的虚拟中断

关键约束:这些寄存器控制的是当前物理 PE 上的虚拟化特性,运行在 PE X 上的软件无法访问 PE Y 的状态。

2.3 ICV 寄存器(Virtual CPU Interface)与访问重定向

在 Guest OS 看来,它运行在 EL1,使用和裸机相同的 ICC_*_EL1 指令读写中断寄存器。但实际上,当 HCR_EL2 的对应控制位开启后,EL1 对 ICC 寄存器的访问会被硬件自动重定向到 ICV 寄存器

  • HCR_EL2.IMO = 1 → EL1 访问 ICC_*_EL1 时被重定向到 ICV_*_EL1
  • HCR_EL2.FMO = 1 → 同上(针对 Group 0 相关寄存器)
  • 对于 Common 寄存器(如 ICC_DIR_EL1),任意一个为 1 即触发重定向

这一机制使得 Guest OS 无需修改任何代码即可在虚拟化环境中运行------它看到的"GIC CPU Interface"实际上是 Hypervisor 借助硬件虚拟化呈现的虚拟视图。

ICV 寄存器的分组:

分组 示例 重定向条件
Group 0 ICC_IAR0_EL1 / ICV_IAR0_EL1 HCR_EL2.FMO == 1
Group 1 ICC_IAR1_EL1 / ICV_IAR1_EL1 HCR_EL2.IMO == 1
Common ICC_DIR_EL1 / ICV_DIR_EL1 HCR_EL2.IMO == 1 HCR_EL2.FMO == 1

3、物理中断路由

3.1 基本路由机制

GICv3 支持三组中断分类:Secure Group 0、Secure Group 1、Non-secure Group 1。

物理 IRQ 和 FIQ 的路由由两级寄存器共同控制:

  • SCR_EL3.IRQ / SCR_EL3.FIQ:控制是否路由到 EL3(最高优先级)
  • HCR_EL2.IMO / HCR_EL2.FMO:控制是否路由到 EL2(次优先级)
  • 若以上均不生效,则默认路由到 EL1

3.2 HCR_EL2.IMO 的路由逻辑

HCR_EL2.IMO 是虚拟化场景下物理 IRQ 路由的核心控制位:





ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture profile

关键规则总结:

HCR_EL2.IMO 效果
0 物理 IRQ 不会路由到 EL2;在 EL2 执行时,除非 SCR_EL3.IRQ 将其路由到 EL3,否则物理 IRQ 被屏蔽;Virtual IRQ 被禁用
1 物理 IRQ 路由到 EL2(除非被 SCR_EL3 路由到 EL3);若 HCR_EL2.TGE == 0,启用 Virtual IRQ

这意味着典型虚拟化场景(Hypervisor 在 EL2,Guest 在 EL1)下,需要将 HCR_EL2.IMO 设为 1,使所有物理外设中断先到达 Hypervisor,由 Hypervisor 决定是否转发给当前运行的 Guest。

4、虚拟中断

4.1 虚拟中断的控制位

Hypervisor 通过 HCR_EL2 的三个 bit 控制是否向 Guest 投递虚拟异常:

控制位 控制对象 启用条件(HCR_EL2.{TGE, x}
IMO vIRQ {0, 1}
FMO vFIQ {0, 1}
AMO vSError {0, 1}

下图展示了 HCR_EL2 中虚拟中断相关位的完整定义:


ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture profile

4.2 虚拟中断的路由约束

虚拟中断有一个关键约束:只能投递到 Non-secure EL0 或 Non-secure EL1,且目标永远是 Non-secure EL1

具体行为:

  • Guest 用户态(NS-EL0)发生 vIRQ → 陷入 NS-EL1
  • Guest 内核态(NS-EL1)发生 vIRQ → 保持在 NS-EL1

这意味着:

  • 虚拟中断不能路由到 EL2 或 EL3
  • 当前核若正在 EL3 或 Secure EL1 执行,虚拟中断无法打断,必须等 CPU 返回到 Non-secure EL1 才能被响应

这是合理的------虚拟中断本质上是 Guest OS 的异常,Guest 只能运行在 NS-EL0/1,因此虚拟中断的目标也只能是 NS-EL1。这与物理中断不同,物理 IRQ/FIQ 可以通过 SCR_EL3HCR_EL2 灵活配置路由目标。

4.3 虚拟中断的两种产生方式

方式一:Hypervisor 软件直接置位

Hypervisor 直接写 HCR_EL2 的以下位即可向当前 vPE 注入虚拟异常:

Bit 虚拟异常
VI Virtual IRQ
VF Virtual FIQ
VSE Virtual SError

这种方式简单直接,但只能注入"有中断 pending"这一信号,无法携带中断号等详细信息。

方式二:GIC 硬件虚拟化自动产生

借助 GICv2 的 Virtual CPU Interface 或 GICv3 的 List Register 机制,硬件可以自动向 Guest 注入携带完整信息(中断号、优先级、Group 等)的虚拟中断。这是实际场景中的主要方式,下面详述。

5、GICv3 List Register 与虚拟中断管理

5.1 List Register 的结构

ICH_LR<n>_EL2(n = 0~15)是虚拟中断管理的核心寄存器。每个 LR 描述一个虚拟中断,包含以下字段:

字段 含义
vINTID 虚拟中断 ID,即 Guest OS 看到的中断号
State 虚拟中断状态:Pending / Active / Active and Pending / Inactive
Group Group 0(以 vFIQ 投递)或 Group 1(以 vIRQ 投递)
Priority 虚拟中断的优先级
pINTID (可选)关联的物理中断 ID,用于实现中断转发
HW 是否关联物理中断(设置后状态更新会同步到 pINTID)


关键特性:

  • 隐式绑定当前 vPE :List Register 不记录目标 vPE 字段,它隐式地以当前调度的 vPE 为目标。ICH_LR<n>_EL2 属于 CPU Interface,其中断直接作用于当前 CPU。切换 vPE 时,Hypervisor 负责保存/恢复所有 LR。
  • 状态机自动推进 :当 Guest 读取 ICV_IARn_EL1 应答中断时,硬件自动将对应 LR 的状态从 Pending 更新为 Active;当 Guest 写入 ICV_EOIRn_EL1 时,硬件自动完成去激活。这一切无须 Hypervisor 介入。

5.2 物理中断到虚拟中断的转发流程

这是虚拟化场景中最关键的路径------物理外设中断如何变成 Guest OS 看到的虚拟中断。以下时序图展示了完整流程:


步骤详解:

  1. 物理中断到达:Redistributor 将物理中断转发到 Physical CPU Interface。

  2. 物理 CPU Interface 检查:检查优先级、Group 使能等条件。检查通过后,置位物理异常信号。

  3. Hypervisor 处理(EL2)

    • CPU 陷入 EL2,Hypervisor 读取 ICC_IAR1_EL1 获取 pINTID,此时物理中断状态变为 Active
    • Hypervisor 判断该中断属于当前运行的 vPE,决定转发
    • Hypervisor 写入 ICC_EOIR1_EL1(当 ICC_CTLR_EL1.EOImode == 1 时,此操作仅执行优先级下降,不执行去激活。目的是保持物理中断的 Active 状态,以便后续与虚拟中断联动)
  4. 写入 List Register :Hypervisor 在某个空闲的 ICH_LR<n>_EL2 中填充 vINTID 和 pINTID,设置 HW=1(关联物理中断),状态设为 Pending。注意:中断不会立即触发------只有当 Hypervisor 执行异常返回(ERET)回到 Guest 后,虚拟中断才会被投递。

  5. 虚拟 CPU Interface 检查:检查的条件与物理中断相同,但使用的是 ICV 寄存器中的虚拟优先级和虚拟 Group 使能位。

  6. Guest 处理(EL1) :虚拟异常路由到 NS-EL1。Guest 读取 ICV_IAR1_EL1 获取 vINTID,虚拟中断状态变为 Active。

  7. 中断完成 :Guest 处理完毕,写入 ICV_EOIR1_EL1。由于 List Register 中 HW=1 且记录了 pINTID,硬件同时去激活 vINTID 和 pINTID。Hypervisor 无需额外干预。

优先级下降(Priority Drop)与去激活(Deactivate)分离 是 GICv3 的重要特性。通过 ICC_CTLR_EL1.EOImode 控制:当 EOI mode = 1 时,写 EOIR 只做优先级下降,需要再写 ICC_DIR_EL1 才能完成去激活。这种分离使得 Hypervisor 可以在第 3 步中降低优先级以便后续中断抢占,同时保持中断在 Active 状态以实现与虚拟中断的生命周期联动。

6、维护中断(Maintenance Interrupt)

6.1 什么是维护中断

虚拟 CPU Interface 在满足特定条件时,可以自动生成一个物理中断通知 Hypervisor。这类中断称为维护中断 ,使用 PPI INTID 25,通常配置为 Non-secure Group 1,由 EL2 的 Hypervisor 处理。


Arm® Generic Interrupt Controller Architecture Specification GIC architecture version 3 and version 4

6.2 控制与状态寄存器

  • ICH_HCR_EL2(Hypervisor Control Register):控制哪些条件触发维护中断
  • ICH_MISR_EL2(Maintenance Interrupt Status Register):报告当前触发了哪些维护中断条件



6.3 典型场景

考虑一个例子:Guest OS 禁用了某一 Group 的中断(通过写 ICC_IGRPENx_EL1,被重定向到 ICV_IGRPENx_EL1)。硬件检测到 Group 使能位被清零后,触发维护中断。Hypervisor 收到后,可以扫描 List Register,移除属于该禁用组且处于 Pending 状态的虚拟中断条目,避免这些中断在 Guest 重新开启该组时意外投递。

维护中断的本质是:让硬件自动通知 Hypervisor 那些"Guest 操作导致虚拟中断状态需要修正"的事件,避免 Hypervisor 需要频繁 Trap 并模拟 Guest 的每一次 GIC 访问。

7、vPE 上下文切换

7.1 需要保存/恢复的状态

切换 vPE 时,Hypervisor 需要保存当前 vPE 的虚拟中断状态,并加载目标 vPE 的状态。涉及三个层次:

  1. ICV 寄存器状态(虚拟 CPU Interface 寄存器)
  2. 虚拟活动优先级(Active Virtual Priorities)
  3. List Register 中的虚拟中断(Pending / Active / Active and Pending 状态的中断)

7.2 ICV 状态访问

Hypervisor 通过 ICH_*_EL2 寄存器来访存虚拟 CPU Interface 状态。核心寄存器是 ICH_VMCR_EL2(Virtual Machine Control Register),它映射了 ICV 寄存器中的关键控制字段:




ICH_VMCR_EL2 中包含的映射字段:

字段 对应 ICV 寄存器中的状态
VPMR 虚拟优先级掩码(Priority Mask)
VBPR0 / VBPR1 虚拟 Binary Point(Group 0 / Group 1)
VEng0 / VEng1 虚拟 Group 使能位(Group 0 / Group 1)
VCBPR 虚拟 Common Binary Point
VEOIM 虚拟 EOI Mode
VGrp0E / VGrp1E 虚拟 Group 使能

通过读写 ICH_VMCR_EL2,Hypervisor 即可统一保存和恢复大部分虚拟 CPU Interface 配置。

7.3 活动优先级

当前 vPE 的活动优先级通过 ICH_APxRn_EL2(Active Priority Register)访问。这些寄存器记录了当前 vPE 上所有 Active 状态中断的优先级,必须在上下文切换时保存和恢复,以确保切换回该 vPE 时优先级抢占逻辑正确。

7.4 List Register 上下文切换

List Register 的状态完全属于当前 vPE。切换时:

  • 保存 :将当前 vPE 的所有 ICH_LR<n>_EL2 读取并保存到内存
  • 恢复 :将目标 vPE 的所有 ICH_LR<n>_EL2 从内存加载到寄存器

典型实现中,Hypervisor(如 KVM)会在 struct kvm_vcpu 中维护 LR 的软件副本,vCPU load 时写回硬件。

8、跨 CPU 虚拟中断注入

8.1 问题分析

ICH_LR<n>_EL2 属于 PE 私有寄存器,写它只能向当前 PE 上运行的 vPE 注入虚拟中断。如果 Hypervisor 需要向运行在其他 CPU 上的 vCPU 注入中断(例如 virtio 设备后端通知前端),该怎么办?

8.2 解决思路

GICv3 中跨核通信的唯一硬件通道是 SGI(Software Generated Interrupt)。流程如下:

  1. Hypervisor(源 CPU)判断目标 vCPU 不在当前核
  2. 通过 ICC_SGI1R_EL1 向目标 CPU 发送一个 SGI(通常是一个预留的 IPI)
  3. 目标 CPU 收到 SGI 后陷入 EL2,在 SGI 处理函数中写入 ICH_LR<n>_EL2,将中断注入到该 CPU 上运行的 Guest

8.3 补充说明

这种方式构成了 KVM 等 Hypervisor 中跨核中断注入的基础:利用 IPI 机制将注入动作从源 CPU"搬运"到目标 CPU,由目标 CPU 的 Hypervisor 上下文完成实际的 List Register 写入。对于目标 vCPU 正在物理 CPU 上运行的情况(running),可以直接写该 CPU 的 LR;对于 vCPU 不在运行(runnable/preempted)的情况,则在 vCPU 数据结构中记录 pending 中断,在 vCPU 下次被调度时再写入 LR。

9、总结

下图从宏观视角总结了虚拟化中断处理的完整数据流:

  • 物理中断到来 → 路由到 EL2 → Hypervisor 获取 pINTID → 在 List Register 中建立 vINTID 到 pINTID 的映射 → ERET 到 Guest → 虚拟中断投递
  • Guest 处理中断 → 读写 ICV 寄存器(被硬件重定向)→ List Register 状态自动更新
  • Guest 操作引发维护条件 → 硬件生成维护中断 → Hypervisor 介入调整 List Register
  • 跨核注入 → SGI → 目标核 Hypervisor → 写 ICH_LR

理解这条路径是理解 KVM/arm64 中断虚拟化实现的基础。

相关推荐
智者知已应修善业2 小时前
【ICL8038芯片正弦波三角波方波发生器电路】2024-1-5
驱动开发·经验分享·笔记·硬件架构·硬件工程
JoneBB2 小时前
ABAP Webservice连接
运维·开发语言·数据库·学习
探序基因3 小时前
身高与基因的关系
笔记
嵌入式小企鹅3 小时前
UiPath推出AI编程“总指挥台”,SiFive发布RISC-V第三代猛兽
人工智能·学习·google·程序员·ai编程·risc-v·开源工具
Ada大侦探3 小时前
新手小白学习数据分析03----Excel 报表之大厂周报(2026最新版实操,包教包会!)
学习·数据分析·excel
-To be number.wan5 小时前
进程与线程的区别
学习·操作系统
llhm5 小时前
tsp学习笔记——LINUX SDK编译2(2)Kernel6.1 Linux
linux·笔记·学习
李白不吃坚果7 小时前
沟道电荷的思考
学习·cmos·集成电路·模拟集成电路设计·沟道电荷
学会870上岸华师7 小时前
C 语言程序设计——第一章课后编程题
c语言·开发语言·学习·算法