参考:《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_*_EL1HCR_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_EL3和HCR_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 看到的虚拟中断。以下时序图展示了完整流程:

步骤详解:
-
物理中断到达:Redistributor 将物理中断转发到 Physical CPU Interface。
-
物理 CPU Interface 检查:检查优先级、Group 使能等条件。检查通过后,置位物理异常信号。
-
Hypervisor 处理(EL2):
- CPU 陷入 EL2,Hypervisor 读取
ICC_IAR1_EL1获取 pINTID,此时物理中断状态变为 Active - Hypervisor 判断该中断属于当前运行的 vPE,决定转发
- Hypervisor 写入
ICC_EOIR1_EL1(当ICC_CTLR_EL1.EOImode == 1时,此操作仅执行优先级下降,不执行去激活。目的是保持物理中断的 Active 状态,以便后续与虚拟中断联动)
- CPU 陷入 EL2,Hypervisor 读取
-
写入 List Register :Hypervisor 在某个空闲的
ICH_LR<n>_EL2中填充 vINTID 和 pINTID,设置 HW=1(关联物理中断),状态设为 Pending。注意:中断不会立即触发------只有当 Hypervisor 执行异常返回(ERET)回到 Guest 后,虚拟中断才会被投递。 -
虚拟 CPU Interface 检查:检查的条件与物理中断相同,但使用的是 ICV 寄存器中的虚拟优先级和虚拟 Group 使能位。
-
Guest 处理(EL1) :虚拟异常路由到 NS-EL1。Guest 读取
ICV_IAR1_EL1获取 vINTID,虚拟中断状态变为 Active。 -
中断完成 :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 的状态。涉及三个层次:
- ICV 寄存器状态(虚拟 CPU Interface 寄存器)
- 虚拟活动优先级(Active Virtual Priorities)
- 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)。流程如下:
- Hypervisor(源 CPU)判断目标 vCPU 不在当前核
- 通过
ICC_SGI1R_EL1向目标 CPU 发送一个 SGI(通常是一个预留的 IPI) - 目标 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 中断虚拟化实现的基础。