2. Linux中断子系统架构:从硬件到软件的中断传递路径,IRQ Domain与中断控制器
理解Linux中断子系统,需要从硬件外设触发中断信号开始,追踪其经过多个中断控制器、CPU接口,最终到达内核处理函数的完整路径。本章将拆解这一“中断传递链”,并重点介绍IRQ Domain——连接硬件中断号与Linux IRQ号的抽象层。
2.1 中断传递路径:硬件→软件全景
一个典型的中断从外设到CPU处理函数的路径,可以划分为四个阶段:
- 外设触发:设备(如网卡、磁盘控制器)通过其中断引脚(INTx/MSI/MSI-X)向中断控制器发送电气信号。
- 中断控制器级联:多个中断控制器(如GIC、IO-APIC)对中断进行优先级仲裁、屏蔽、路由,最终将中断信号送达CPU的IRQ引脚。
- CPU异常处理:CPU检测到IRQ引脚信号后,保存上下文,跳转到内核的异常向量表(entry.S)中的中断入口。
- 内核通用中断处理:从汇编入口进入
do_IRQ()(x86)或handle_arch_irq()(ARM64),经过IRQ Domain映射,调用注册的中断处理函数(handler)。
关键概念:硬件中断号(hwirq)是中断控制器视角的编号;Linux IRQ号是内核软件视角的虚拟中断号。IRQ Domain负责两者之间的翻译。
2.2 中断控制器与级联拓扑
现代SoC和x86平台通常包含多级中断控制器。以ARM GICv3为例,其拓扑结构如下:
| 层级 | 控制器 | 功能 | 输出目标 |
|---|---|---|---|
| L1 | GIC Distributor (GICD) | 全局中断使能/屏蔽、优先级分组、SPI路由 | Redistributor |
| L2 | GIC Redistributor (GICR) | 每CPU本地中断(PPI/SGI)管理、唤醒逻辑 | CPU Interface |
| L3 | CPU Interface (GICC) | 与CPU核直接交互,提供IRQ/FIQ信号 | CPU Core |
x86对比:x86平台使用IO-APIC(负责I/O设备中断)和Local APIC(每CPU本地中断管理),两者通过FSB(前端总线)或系统总线通信。IO-APIC将中断路由到指定CPU的Local APIC。
2.3 IRQ Domain:硬件中断号到Linux IRQ号的桥梁
IRQ Domain(struct irq_domain)是内核中断子系统最核心的抽象层之一。它解决了以下问题:
- 硬件中断号(hwirq)由中断控制器定义,通常是线性或树状编号。
- Linux IRQ号是全局唯一的整数,用于在
/proc/interrupts和驱动中标识中断。 - IRQ Domain提供
map()/unmap()/translate()回调,实现hwirq → Linux IRQ的映射。
2.4 IRQ Domain的三种映射方式
内核支持三种映射模型,适用于不同的中断控制器硬件特性:
| 映射类型 | 适用场景 | 映射方式 | 示例 |
|---|---|---|---|
| 线性映射 (Linear) | hwirq范围连续且较小 | hwirq直接作为数组索引 | GICv2 SPI (32~1019) |
| 树形映射 (Tree) | hwirq稀疏或范围很大 | 红黑树查找 | GPIO中断控制器 |
| 不映射 (No Map) | hwirq与Linux IRQ一一对应 | 直接使用hwirq作为IRQ号 | 某些嵌入式平台 |
性能注意:线性映射是O(1)查找,性能最优;树形映射在中断频繁发生时有红黑树遍历开销。在中断密集型场景(如高速网卡)中,应优先选择线性映射的中断控制器。
2.5 中断描述符与中断流处理
每个Linux IRQ号对应一个struct irq_desc,其中包含:
- irq_chip:指向中断控制器的底层操作函数(mask/unmask/ack/eoi)。
- handle_irq:高层中断流处理函数(如
handle_level_irq、handle_edge_irq)。 - action链表:挂载该中断的所有注册处理函数(支持共享中断)。
- irq_data:包含hwirq、domain指针等映射信息。
调试技巧:查看
/proc/interrupts 可以观察每个IRQ号的触发次数和亲和性(CPU Affinity)。/proc/irq/{IRQ}/smp_affinity 可手动设置中断绑核。
2.6 中断传递路径的完整代码流程
以ARM64为例,中断从硬件到软件的关键代码路径如下:
// 1. 硬件触发 → CPU 异常向量
// arch/arm64/kernel/entry.S
el1_irq:
// 保存上下文,读取 ESR_EL1 确认中断类型
// 跳转到 handle_arch_irq
// 2. 架构相关入口
// arch/arm64/kernel/irq.c
void handle_arch_irq(struct pt_regs *regs)
{
// 调用 GIC 驱动注册的 handle_irq
gic_handle_irq(regs);
}
// 3. GIC 驱动读取中断号
// drivers/irqchip/irq-gic-v3.c
static void gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr = gic_read_iar(); // 读取硬件中断号
// 通过 irq_find_mapping 转换为 Linux IRQ 号
unsigned int irq = irq_find_mapping(gic->domain, irqnr);
generic_handle_irq(irq); // 调用中断流处理函数
}
// 4. 通用中断层
// kernel/irq/irqdesc.c
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
desc->handle_irq(desc); // 调用 handle_level_irq / handle_edge_irq
}
// 5. 中断流处理 → 调用 action 链表中的 handler
// kernel/irq/chip.c
void handle_level_irq(struct irq_desc *desc)
{
// mask 中断 → 遍历 action 链表 → 调用 handler → unmask
desc->irq_data.chip->irq_mask(desc->irq_data);
handle_irq_event(desc);
desc->irq_data.chip->irq_unmask(desc->irq_data);
}
2.7 本章小结
| 知识点 | 核心要点 |
|---|---|
| 中断传递路径 | 外设 → 中断控制器 → CPU异常向量 → do_IRQ() → IRQ Domain → handler |
| 中断控制器级联 | GIC Distributor → Redistributor → CPU Interface(ARM);IO-APIC → Local APIC(x86) |
| IRQ Domain | 将硬件中断号(hwirq)映射为Linux IRQ号,支持线性/树形/不映射三种方式 |
| irq_desc | 每个IRQ号对应的描述符,包含chip、handle_irq、action链表 |
| 性能关键 | 线性映射O(1)最优;中断绑核通过smp_affinity控制 |