3. 中断机制概述

中断是嵌入式实时系统与通用操作系统响应外部事件的核心机制。在 Linux 内核实时化改造中,中断处理路径的延迟直接决定了系统的确定性上限。本章从硬件中断与异常的区分出发,逐层拆解中断控制器(x86 APIC 与 ARM GIC)、中断向量表结构以及完整的中断处理流程,为后续中断线程化奠定硬件与内核基础。

3.1 硬件中断与异常

中断与异常都是 CPU 对异步或同步事件的响应,但二者在触发源、同步性、可屏蔽性上存在本质差异。下表总结了二者的核心区别:

特性 硬件中断(Interrupt) 异常(Exception)
触发源 外部设备(定时器、网卡、磁盘等) CPU 内部指令执行(除零、缺页、系统调用)
同步性 异步:与当前指令流无关 同步:由当前指令直接引发
可屏蔽性 可屏蔽中断(INTR)可通过 EFLAGS.IF 关闭;不可屏蔽中断(NMI)不可关闭 不可屏蔽(除软中断指令外)
返回行为 返回被中断指令的下一条 返回引发异常的指令(可恢复)或终止进程(不可恢复)
典型例子 IRQ 0(定时器)、IRQ 1(键盘) #PF(缺页)、#GP(通用保护)、#DB(调试)
实时性要点: 在实时系统中,不可屏蔽中断(NMI)和快速中断(FIQ)通常用于最高优先级的紧急事件(如看门狗、电源故障),它们无法被 Linux 内核的关中断操作屏蔽,因此中断线程化策略对 NMI/FIQ 无效,必须单独处理。

3.2 中断控制器:APIC 与 GIC

中断控制器是 CPU 与外部中断源之间的桥梁,负责中断优先级裁决、路由、屏蔽与分发。x86 架构使用 APIC(Advanced Programmable Interrupt Controller),ARM 架构使用 GIC(Generic Interrupt Controller)。二者在实时系统中的行为差异直接影响中断延迟。

3.2.1 x86 APIC 架构

APIC 系统由 Local APIC(LAPIC) 和 I/O APIC 组成。LAPIC 集成在 CPU 内部,处理本地中断源(定时器、性能计数器、错误中断)以及外部中断的最终分发;I/O APIC 负责收集外设中断信号,通过总线发送给 LAPIC。

关键寄存器: LAPIC 的 ICR(Interrupt Command Register) 用于发送 IPI(处理器间中断),TPR(Task Priority Register) 和 PPR(Processor Priority Register) 用于中断优先级屏蔽,直接影响实时任务的抢占延迟。
x86 APIC 中断控制器架构 CPU 0 LAPIC 0 TPR/PPR/ICR CPU 1 LAPIC 1 TPR/PPR/ICR CPU 2 LAPIC 2 TPR/PPR/ICR CPU 3 LAPIC 3 I/O APIC 中断重定向表(24 路) 外设中断源 定时器/网卡/磁盘 说明:I/O APIC 通过中断重定向表将外设中断路由到指定 CPU 的 LAPIC,LAPIC 根据 TPR/PPR 决定是否立即响应。

3.2.2 ARM GIC 架构

ARM GIC(Generic Interrupt Controller)是 ARM 体系的标准中断控制器,当前主流版本为 GICv2(Cortex-A7/A15)和 GICv3(Cortex-A53/A72/A76)。GIC 将中断分为三类:

  • SPI(Shared Peripheral Interrupt): 共享外设中断,可路由到任意 PE(Processing Element)。
  • PPI(Private Peripheral Interrupt): 私有外设中断,仅发送给特定 PE,如本地定时器。
  • SGI(Software Generated Interrupt): 软件生成中断,用于核间通信(IPI)。
特性 GICv2 GICv3
中断标识范围 ID 0-1019 ID 0-1023(扩展 SPI 支持)
亲和性路由 仅支持 SPI 路由到指定 CPU 接口 支持 SPI/PPI/SGI 基于 Affinity Routing Table 路由
安全扩展 TrustZone 安全中断(FIQ) 支持 Group 0(安全 FIQ)、Group 1(安全 IRQ)、Group 1(非安全 IRQ)
虚拟化支持 基本虚拟中断(vGIC) 硬件加速虚拟中断(List Register)
实时影响 中断延迟受 CPU 接口仲裁影响 支持 Direct Injection,减少 VM 退出
ARM GICv3 中断控制器架构 GIC Distributor 中断优先级/使能/路由配置 Redistributor 0 PPI/SGI 管理 Redistributor 1 PPI/SGI 管理 Redistributor 2 PPI/SGI 管理 Redistributor 3 PPI/SGI PE 0 (CPU 0) PE 1 (CPU 1) PE 2 (CPU 2) PE 3 (CPU 3) 说明:Distributor 将 SPI 路由到对应 Redistributor,Redistributor 管理本地 PPI/SGI 并直接送达 PE。

3.3 中断向量表

中断向量表(Interrupt Vector Table, IVT)是 CPU 在响应中断时查找处理程序入口地址的数据结构。x86 与 ARM 的实现方式不同:

  • x86(IDT): 中断描述符表(Interrupt Descriptor Table),每个描述符 8 字节,包含段选择子、偏移量、门类型(中断门/陷阱门/任务门)、DPL 等。CPU 通过 IDTR 寄存器定位 IDT 基址。
  • ARM(异常向量表): 固定基址(VBAR_ELx 寄存器),每个向量 16 字节(AArch64),支持同步异常、IRQ、FIQ、SError 等入口。
架构 向量表名称 基址寄存器 每个向量大小 最大向量数
x86-64 IDT IDTR 16 字节(8 字节描述符 × 2) 256
ARMv8-A 异常向量表 VBAR_EL1/EL2/EL3 16 字节(AArch64) 16(4 类 × 4 异常等级)
实时陷阱: x86 的 IDT 支持 任务门,但 Linux 内核从不使用任务门处理中断,因为任务门会触发完整的任务切换(TSS),导致数百周期的额外开销。实时系统必须确保所有中断门使用 中断门(DPL=0,IF 自动清除),避免嵌套中断不可控。

3.4 中断处理流程

从外设触发中断信号到 Linux 内核执行完中断服务例程(ISR),完整的硬件与软件协同流程可分为以下阶段:

  1. 硬件阶段: 外设拉高中断请求线 → 中断控制器仲裁优先级 → 向 CPU 发送中断信号(IRQ/FIQ)。
  2. CPU 响应阶段: CPU 保存当前上下文(PC、PSR、寄存器)→ 查询向量表 → 跳转到中断向量入口。
  3. 内核入口阶段: 保存通用寄存器 → 切换堆栈(从用户栈到内核栈)→ 调用 handle_arch_irq() 或 do_IRQ()。
  4. 中断分发阶段: 读取中断控制器寄存器获取中断号 → 查找 irq_desc 数组 → 调用注册的 handle_irq() 高层处理函数。
  5. ISR 执行阶段: 执行设备驱动的 request_irq() 注册的中断处理函数(顶半部)。
  6. 退出阶段: 恢复上下文 → 执行 iret(x86)或 eret(ARM)→ 返回被中断点。
Linux 内核中断处理完整流程 ① 外设触发中断 设备拉高中断请求线 ② 中断控制器仲裁 APIC/GIC 优先级裁决 ③ CPU 响应中断 保存上下文/查向量表 ④ 内核入口 保存寄存器/切换堆栈 ⑤ 中断分发 读中断号/查 irq_desc ⑥ ISR 执行(顶半部) 设备驱动中断处理函数 ⑦ 退出中断 恢复上下文 / iret / eret ⑧ 底半部处理(可选) SoftIRQ / Tasklet / Workqueue 说明:阶段①-③为硬件处理,阶段④-⑥为内核顶半部(关中断执行),阶段⑧为底半部(开中断执行)。 中断线程化将阶段⑥的 ISR 移至进程上下文执行,大幅降低关中断时间。

3.5 中断处理的关键数据结构

Linux 内核使用 irq_desc 结构体管理每个中断号的状态与处理函数链。该结构体是中断线程化的核心操作对象。

struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data         irq_data;
    unsigned int __percpu   *kstat_irqs;
    irq_flow_handler_t      handle_irq;      // 高层中断处理函数
    struct irqaction        *action;          // 中断动作链表
    unsigned int            status_use_accessors;
    struct list_head        secondary_list;   // 用于中断线程化
    raw_spinlock_t          lock;
    struct cpumask          *percpu_enabled;
    const char              *name;
} ____cacheline_internodealigned_in_smp;
实时关注点: irq_desc.action 链表中的每个 irqaction 包含 thread_fn 和 thread 字段。当中断线程化启用时,request_threaded_irq() 会为中断创建一个内核线程,handle_irq_event_percpu() 在顶半部唤醒该线程,由线程执行 thread_fn,从而将中断处理从原子上下文迁移到可睡眠的进程上下文。

3.6 中断延迟的组成

理解中断延迟的构成是实时优化的前提。中断延迟(Interrupt Latency)指从硬件中断信号到达 CPU 引脚到 ISR 第一条指令开始执行的时间间隔,由以下部分组成:

延迟分量 典型耗时(x86-64 @ 2GHz) 说明
中断控制器仲裁 50-200 ns APIC/GIC 优先级比较与路由
CPU 响应延迟 10-50 ns 当前指令完成、流水线排空
上下文保存 100-300 ns 寄存器压栈、PSR 保存
向量表查找 5-10 ns IDT/异常向量表索引
内核入口代码 200-500 ns 堆栈切换、通用寄存器保存
中断分发 100-300 ns 读中断控制器寄存器、查 irq_desc
关中断阻塞 1-100 μs 实时系统最大变量:其他代码关中断导致中断被延迟
核心结论: 硬件延迟(前 6 项)通常在微秒级以下且固定,而 关中断阻塞 是实时系统中中断延迟的主要来源。中断线程化的核心目标就是最小化关中断时间,将非紧急处理移出原子上下文。