中断机制深度解析:APIC、GIC、中断向量与处理流程
大家好,我是你们的老朋友。今天我们聊聊中断机制。这东西,说白了就是CPU怎么响应外部事件的“通讯协议”。我在调优DMA中断亲和性的时候,发现好多问题根子就在这层没搞透。你想想看,连中断是怎么从设备走到CPU核心的都不知道,谈何优化它被哪个核处理呢?
我个人习惯,把中断机制拆成三块来看:谁管的(中断控制器)、管什么的(中断向量)、怎么管的(处理流程)。我们一个一个过。
3.1 中断控制器:APIC 与 GIC 的差异
中断控制器,就是CPU外围的“总调度员”。x86体系用APIC,ARM体系用GIC。它们功能类似,但设计哲学和配置方式差别挺大。我当年从x86转去调ARM平台时,在GIC路由配置上踩过坑,嗯,后面细说。
3.1.1 x86 的 APIC(高级可编程中断控制器)
APIC分两层:一个全局的I/O APIC(通常集成在南桥或CPU内部),和每个CPU核心私有的Local APIC。设备的中断信号先到达I/O APIC,I/O APIC根据路由表(Redirection Table)把中断发往某个或某几个Local APIC,最终由具体的CPU核心响应。
- I/O APIC:接收外设中断,可编程路由到任意一个CPU核心。
- Local APIC:每个核一个,管理本地中断(如时钟中断、IPI核间中断),并接收来自I/O APIC的中断消息。
3.1.2 ARM 的 GIC(通用中断控制器)
GIC版本很多,从GIC-400到GIC-500、GIC-600。核心架构分为三部分:Distributor(分发器)、Redistributor(重分发器)、CPU Interface(CPU接口)。Distributor负责全局中断配置,每个CPU核心有自己的Redistributor和CPU Interface。
- Distributor:配置中断优先级、触发方式(边沿/电平)、目标CPU列表。
- Redistributor:处理每个核私有的PPI(私有外设中断)和SGI(软件触发中断)。
- CPU Interface:向核心发送中断信号,控制中断屏蔽和应答。
GIC的亲和性配置更灵活,因为每个中断的affinity可以在运行时动态调整到任意核心组合。我在调一个DPDK网卡时,就用GIC的亲和性子系统把网卡中断固定到了前两个物理核,效果立竿见影。
| 特性 | APIC (x86) | GIC (ARM) |
|---|---|---|
| 路由颗粒度 | 基于Redirection Table条目 | 基于每个中断ID的亲和性子域 |
| 私有中断 | Local APIC处理时钟、IPI | Redistributor处理PPI、SGI |
| 亲和性配置 | 通过IOREDTBL寄存器的destination字段 | 通过GICD_IROUTER寄存器 |
| 中断优先级 | 支持,但现代OS用得不多 | 更灵活的优先级分组模型 |
3.2 中断向量:中断号与处理函数映射
每个硬件中断都有一个唯一的编号,就是我们常说的中断向量(Interrupt Vector)。x86上中断向量范围0~255,ARM GIC中是每个SPI/PPI/SGI有唯一的Interrupt ID。内核拿到这个向量号,去查一张全局表——中断描述符表(IDT on x86),找到对应的处理函数(ISR)。
嗯,这里要注意:中断向量不是随便分的。Linux内核把x86的中断向量划成了几段:
- 0x00–0x1F:CPU异常和陷阱(如缺页、除零)。
- 0x20–0x2F:IRQ 0–15(历史上保留给旧PIC)。
- 0x30–0xFF:动态分配,用于MSI-X、IOAPIC中断。
ARM GIC的Interrupt ID则从0开始:ID0-ID15是SGI;ID16-ID31是PPI;ID32往后是共享的SPI。我在项目中遇到过一个问题:某个外设注册的中断号是GIC SPI 168,结果因为设备树写错了,导致中断路由到一个离线核心上,整个系统响应变得奇慢。
3.3 中断处理流程:从硬件到软件
有了前面的控制器和向量,我们来看中断从触发到完成的全流程。我把它归纳为6步,画了张SVG图,你对照着看会更清楚。
结合上面的图,我把第⑤和第⑥步再展开说说:
- 上半部(ISR):CPU收到中断后,自动关中断(或硬件屏蔽该中断),执行注册的ISR。ISR里通常干三件事:读中断状态寄存器确认原因;写中断结束寄存器(EOI)通知控制器;调度下半部(如raise_softirq)。
- 下半部(SoftIRQ/Tasklet/Workqueue):核心再次开中断后,在合适时机(如从ISR返回前、调度点)执行。下半部可以长时间运行,也允许其他中断发生。
- 中断优先级与抢占:高优先级中断可以打断低优先级中断的上半部。我在一次线上问题时发现,某块网卡的RX队列中断优先级设得比时钟中断还高,结果时钟漂移严重,系统挂死。
实战要点:优化DMA中断亲和性,本质就是优化步骤②(中断控制器路由)和步骤③(CPU仲裁)的分配策略。我建议你在/proc/interrupts里先看清每个IRQ当前被哪个核心处理了,然后用echo配合smp_affinity手动改,这是最快验证中断分布是否合理的方法。
好,中断机制这块东西比较多,但你只要记住:控制器管路由,向量管映射,流程管先后顺序。做到这三点,后面谈亲和性优化和动态均衡时,就不会犯低级错误了。