4、异步异常详解:IRQ、FIQ、SError、物理中断与虚拟中断的区别、GICv3/4与异常路由

异步异常,说白了就是“不知道什么时候会来的异常”。

它不像同步异常那样,执行某条指令就一定会触发。异步异常完全由外部事件驱动——可能是外设发了个中断,可能是内存出了个错误,也可能是另一个核发了个信号。你没法预测它什么时候来,只能保证它来了之后系统能正确处理。

这一章,我们就来拆解 ARMv9 里的四种异步异常:IRQ、FIQ、SError,以及虚拟化场景下的物理中断和虚拟中断。我还会结合 GICv3/v4 的架构,讲讲中断是怎么路由到具体 PE 的。

4.1 四种异步异常的本质

ARMv9 把异步异常分成四类,每一类的优先级、触发源、处理方式都不一样。我们先看个总览:

异常类型 优先级 典型触发源 路由控制
SError 最高 内存错误、总线错误、ECC 校验失败 SCR_EL3.EA
FIQ 次高 安全中断、快速外设、虚拟化事件 SCR_EL3.FIQ
IRQ 普通 普通外设中断、定时器、DMA SCR_EL3.IRQ
vIRQ/vFIQ 虚拟 虚拟机注入的中断 HCR_EL2.IMO/FMO

这里有个关键点:优先级是硬件决定的,你改不了。SError 永远最高,FIQ 永远高于 IRQ。但你可以通过路由控制,决定某个异常到底由哪个异常级别处理。

核心原则:异步异常的路由由两个寄存器控制——SCR_EL3 控制路由到 EL3 还是 lower EL,HCR_EL2 控制路由到 EL2 还是 EL1。这两个寄存器配合使用,就能实现灵活的中断分发。

4.2 SError:最容易被忽略的“隐形杀手”

SError 在项目中是最容易被忽略的。我见过不少团队,中断处理写得很好,但 SError 处理基本是空的——要么直接 panic,要么干脆不注册 handler。

为什么会这样?因为 SError 不像 IRQ 那样频繁触发。它通常只在硬件出问题时才出现,比如内存 ECC 错误、总线超时、或者外设返回了异常响应。平时不触发,不代表它不重要。

我曾经在一个服务器项目上遇到过:系统跑着跑着突然挂掉,没有任何日志。查了三天,最后发现是内存控制器报了个 SError,但 EL3 的 SError handler 直接调了 wfi 挂起了。嗯,从那以后我养成了一个习惯——SError handler 里至少要把错误信息存到某个非易失区域,然后触发 warm reset。

避坑指南:SError 是 asynchronous 的,这意味着它可能和当前执行的指令完全无关。你收到 SError 时,PC 指向的指令可能根本不是导致错误的指令。所以,SError handler 里不要尝试“恢复执行”,除非你明确知道错误源并能修复它。

4.3 IRQ 与 FIQ:不只是优先级不同

很多人觉得 IRQ 和 FIQ 的区别就是优先级——FIQ 更高。其实不止。

在 ARMv7 及之前,FIQ 有独立的寄存器组(r8-r14),这意味着 FIQ handler 不需要保存和恢复上下文,响应速度更快。但在 ARMv8/v9 里,这个优势已经不存在了——所有异常级别都使用相同的通用寄存器组,FIQ 和 IRQ 在上下文切换上的开销是一样的。

那为什么还要保留 FIQ?

  • 安全隔离:FIQ 通常用于安全世界的中断。在 TrustZone 架构下,安全世界的中断必须用 FIQ,普通世界的中断用 IRQ。这样,安全世界的代码不会被普通世界的中断打断。
  • 虚拟化:在虚拟化场景下,FIQ 可以用来处理来自 hypervisor 的事件,IRQ 处理来自 guest OS 的中断。通过 HCR_EL2 的 IMO/FMO 位,可以独立控制 IRQ 和 FIQ 的路由。
  • 优先级分组:GICv3 支持优先级分组,你可以把高优先级的中断配置为 FIQ,低优先级的配置为 IRQ。这样,FIQ handler 可以抢占 IRQ handler。

我的建议:除非你有明确的安全或虚拟化需求,否则把 FIQ 和 IRQ 都路由到同一个异常级别处理。不要为了“用 FIQ 而用 FIQ”,那样只会增加代码复杂度。

4.4 物理中断 vs 虚拟中断

虚拟化场景下,中断变得复杂了。一个外设产生的中断,到底是给 host 还是给 guest?

ARM 的解决方案是:物理中断直接由硬件产生,送到 GIC,然后路由到对应的 PE。虚拟中断是由 hypervisor 通过 GIC 的虚拟化接口(GICv3 的 LR 寄存器,GICv4 的 vLPI)注入给 guest 的。

举个例子:

  1. 一个网卡产生物理中断,GIC 把它路由到 EL2 的 hypervisor。
  2. Hypervisor 收到中断后,发现这个中断属于某个 guest 的虚拟网卡。
  3. Hypervisor 通过 GIC 的虚拟化接口,向目标 guest 注入一个虚拟中断。
  4. Guest 的 OS 在 EL1 收到这个虚拟中断,就像收到普通物理中断一样处理。

整个过程对 guest 是透明的。guest 根本不知道它处理的是虚拟中断还是物理中断——它只看到 IRQ 或 FIQ 被触发。

特性 物理中断 虚拟中断
产生源 硬件外设 Hypervisor 注入
路由目标 物理 PE 虚拟 PE(vCPU)
处理方式 EL3/EL2/EL1 的 handler EL1 的 handler(guest OS)
GIC 接口 SPI/PPI/LPI vSGI/vPPI/vLPI(通过 LR/vLPI 表)
性能开销 低(直接硬件路由) 较高(需要 hypervisor 介入)

4.5 GICv3/v4 与异常路由

GIC(Generic Interrupt Controller)是 ARM 体系里负责中断管理的硬件单元。GICv3 和 GICv4 是目前主流的两代架构。

4.5.1 GICv3 的核心组件

  • Distributor:负责中断的配置、使能、优先级设置。每个中断源在 Distributor 里都有一个对应的寄存器。
  • Redistributor:每个 PE 有一个 Redistributor,负责处理该 PE 的 PPI(私有外设中断)和 SGI(软件生成中断)。
  • ITS(Interrupt Translation Service):负责将设备产生的 MSI(Message Signaled Interrupt)翻译成 LPI(Locality-specific Peripheral Interrupt),并路由到目标 PE。
  • CPU Interface:PE 内部的接口,负责向 PE 核心发送中断信号。

GICv3 最大的变化是:中断路由不再依赖中断号,而是依赖 affinity 路由表。每个中断可以配置目标 PE 的 affinity 层级(cluster、core、thread),GIC 根据这个路由表把中断送到正确的 PE。

4.5.2 GICv4 的虚拟化增强

GICv4 在 GICv3 的基础上,增加了对虚拟中断的硬件加速支持。核心是 vLPI(虚拟 LPI) 和 Doorbell 机制。

  • vLPI:允许设备直接向 guest 注入中断,不需要 hypervisor 介入。GICv4 的 Redistributor 里有一个 vLPI 表,设备产生的 MSI 可以直接映射到 vLPI,然后 GIC 直接把中断送到 vCPU。
  • Doorbell:当 vCPU 不在线时,GICv4 会通过 Doorbell 中断通知 hypervisor,让 hypervisor 调度 vCPU 上线处理中断。

GICv4 的好处是:减少了 hypervisor 的介入次数,降低了虚拟中断的延迟。我在一个 NFV(网络功能虚拟化)项目上测试过,GICv4 的虚拟中断延迟比 GICv3 降低了约 30%。

关键点:GICv4 的 vLPI 需要硬件支持,不是所有 SoC 都实现了。如果你的项目需要低延迟的虚拟中断,选型时一定要确认 GIC 版本。

4.6 异常路由的完整流程

我们用一个实际例子来串一下整个流程。假设一个网卡产生了中断,中断号是 100,目标是一个运行在 EL1 的 guest OS。

  1. 中断产生:网卡通过 MSI 向 GIC 发送中断信号。
  2. ITS 翻译:GIC 的 ITS 根据中断号 100 查找翻译表,找到目标 PE 的 affinity 和中断类型(这里是 vLPI)。
  3. 路由决策:GIC 检查目标 PE 的当前异常级别和路由配置:
    • 如果 PE 当前在 EL2(hypervisor),GIC 直接发送物理中断到 EL2。
    • 如果 PE 当前在 EL1(guest),GIC 检查 HCR_EL2.IMO 位:如果 IMO=1,中断被路由到 EL2;如果 IMO=0,中断直接送到 EL1。
  4. 中断注入:如果是 vLPI,GICv4 直接把中断写入目标 vCPU 的 vLPI 表,然后通过 vCPU 的 CPU interface 触发虚拟中断。
  5. 异常处理:PE 收到中断后,根据当前的 PSTATE 和路由配置,跳转到对应的异常向量表条目,执行 handler。

你看,一个中断从产生到处理,中间经过了多个决策点。每个决策点都由硬件自动完成,不需要软件干预。这就是 ARM 异常处理机制的高效之处。

调试技巧:如果你发现某个中断没有被正确路由,先检查 SCR_EL3 和 HCR_EL2 的路由位。我遇到过好几次,问题都是因为 HCR_EL2.IMO 被意外清零了。

4.7 本章知识结构图

下面这张图展示了异步异常的分类、路由路径和 GIC 组件的关系:

异步异常分类与路由架构 SError FIQ IRQ vIRQ/vFIQ GICv3/v4 中断控制器 Distributor | Redistributor | ITS | CPU Interface 路由决策:Affinity 路由表 + SCR_EL3 / HCR_EL2 异常路由决策 SCR_EL3.EA/FIQ/IRQ → 路由到 EL3 或 lower EL HCR_EL2.IMO/FMO → 路由到 EL2 或 EL1 EL3 Secure Monitor SError/FIQ EL2 Hypervisor IRQ/FIQ EL1 Guest OS vIRQ/vFIQ EL0 Application (通常不处理) 关键说明 SError 通常路由到 EL3,FIQ 用于安全中断,IRQ 用于普通中断 虚拟中断(vIRQ/vFIQ)由 GICv4 硬件加速注入,减少 hypervisor 介入

这张图把整个异步异常的处理链路串起来了。从异常源到 GIC,再到路由决策,最后到具体的异常级别处理。你写代码的时候,脑子里要有这张图——知道中断从哪来,经过哪些决策点,最终落到哪个 handler。

4.8 小结

异步异常是 ARM 异常处理里最复杂的一块,因为它涉及外部事件、硬件路由、虚拟化等多个维度。这一章我们讲了:

  • 四种异步异常的本质和优先级
  • SError 的“隐形杀手”特性
  • IRQ 和 FIQ 在安全与虚拟化场景下的区别
  • 物理中断与虚拟中断的完整流程
  • GICv3/v4 的架构和路由机制