3. 中断机制概述:硬件中断与异常、中断控制器(APIC/GIC)、中断向量表、中断处理流程
中断是嵌入式实时系统与通用操作系统响应外部事件的核心机制。在 Linux 内核实时化改造中,中断处理路径的延迟直接决定了系统的确定性上限。本章从硬件中断与异常的区分出发,逐层拆解中断控制器(x86 APIC 与 ARM GIC)、中断向量表结构以及完整的中断处理流程,为后续中断线程化奠定硬件与内核基础。
3.1 硬件中断与异常
中断与异常都是 CPU 对异步或同步事件的响应,但二者在触发源、同步性、可屏蔽性上存在本质差异。下表总结了二者的核心区别:
| 特性 | 硬件中断(Interrupt) | 异常(Exception) |
|---|---|---|
| 触发源 | 外部设备(定时器、网卡、磁盘等) | CPU 内部指令执行(除零、缺页、系统调用) |
| 同步性 | 异步:与当前指令流无关 | 同步:由当前指令直接引发 |
| 可屏蔽性 | 可屏蔽中断(INTR)可通过 EFLAGS.IF 关闭;不可屏蔽中断(NMI)不可关闭 | 不可屏蔽(除软中断指令外) |
| 返回行为 | 返回被中断指令的下一条 | 返回引发异常的指令(可恢复)或终止进程(不可恢复) |
| 典型例子 | IRQ 0(定时器)、IRQ 1(键盘) | #PF(缺页)、#GP(通用保护)、#DB(调试) |
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。
ICR(Interrupt Command Register) 用于发送 IPI(处理器间中断),TPR(Task Priority Register) 和 PPR(Processor Priority Register) 用于中断优先级屏蔽,直接影响实时任务的抢占延迟。
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 退出 |
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 异常等级) |
3.4 中断处理流程
从外设触发中断信号到 Linux 内核执行完中断服务例程(ISR),完整的硬件与软件协同流程可分为以下阶段:
- 硬件阶段: 外设拉高中断请求线 → 中断控制器仲裁优先级 → 向 CPU 发送中断信号(IRQ/FIQ)。
- CPU 响应阶段: CPU 保存当前上下文(PC、PSR、寄存器)→ 查询向量表 → 跳转到中断向量入口。
- 内核入口阶段: 保存通用寄存器 → 切换堆栈(从用户栈到内核栈)→ 调用
handle_arch_irq()或do_IRQ()。 - 中断分发阶段: 读取中断控制器寄存器获取中断号 → 查找
irq_desc数组 → 调用注册的handle_irq()高层处理函数。 - ISR 执行阶段: 执行设备驱动的
request_irq()注册的中断处理函数(顶半部)。 - 退出阶段: 恢复上下文 → 执行
iret(x86)或eret(ARM)→ 返回被中断点。
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 | 实时系统最大变量:其他代码关中断导致中断被延迟 |