4. 中断子系统:中断控制器(APIC/IOAPIC)、中断描述符、中断处理函数
中断是现代操作系统响应硬件事件的基石。在多核 CPU 环境下,中断子系统必须高效地将外设中断路由到正确的 CPU 核心,并快速完成处理。本章深入剖析 Linux 中断子系统的三大核心组件:中断控制器(APIC/IOAPIC)、中断描述符(irq_desc) 和 中断处理函数(handler),为后续的软中断亲和性调优打下坚实的理论基础。
4.1 中断控制器体系:从 8259A 到 APIC/IOAPIC
传统单核系统使用 8259A 可编程中断控制器(PIC),但其固定中断路由和有限中断源无法满足 SMP(对称多处理)系统的需求。现代 x86_64 架构采用 APIC(高级可编程中断控制器) 体系,分为两部分:
- Local APIC(LAPIC):集成在每个 CPU 核心内部,负责接收来自本地中断源(如时钟、热敏)和外部 I/O 设备的中断,并管理核间中断(IPI)。
- I/O APIC:通常位于芯片组(如南桥)中,作为外设中断的汇聚点,负责将设备中断请求重定向到指定 CPU 的 LAPIC。
关键概念: I/O APIC 支持中断重定向表(Redirection Table),允许软件为每个中断源独立配置目标 CPU 核心和触发模式(边沿/电平),这是实现中断亲和性(IRQ Affinity)的硬件基础。
4.2 中断描述符(irq_desc):中断的“身份证”
在 Linux 内核中,每个中断源(IRQ)都对应一个 struct irq_desc 结构体,它包含了该中断的所有元数据和控制信息。内核维护一个全局的 irq_desc 数组,索引即为 IRQ 号。
| 字段 | 类型 | 描述 |
|---|---|---|
irq_data |
struct irq_data |
包含中断控制器相关的硬件信息(如芯片、中断号、亲和性掩码) |
action |
struct irqaction * |
指向中断处理函数链表的头指针 |
status_use_accessors |
unsigned int |
中断状态标志(如 IRQ_INPROGRESS、IRQ_DISABLED) |
depth |
unsigned int |
中断禁用嵌套深度 |
lock |
raw_spinlock_t |
保护该描述符的自旋锁 |
kstat_irqs |
struct irqstat |
中断统计信息(发生次数、时间戳等) |
调优视角:
irq_desc->irq_data->affinity 字段(类型 struct cpumask)直接控制该中断允许在哪些 CPU 核心上触发。修改 /proc/irq/{IRQ_NUM}/smp_affinity 实际上就是在操作这个掩码。
4.3 中断处理函数:上半部与下半部
为了在中断上下文中快速响应硬件,同时避免长时间关闭中断导致系统延迟,Linux 将中断处理分为两个阶段:
- 上半部(Hard IRQ Handler):在中断上下文中立即执行,必须快速完成(通常微秒级)。负责确认中断、保存硬件状态、调度下半部。通过
request_irq()或request_threaded_irq()注册。 - 下半部(Bottom Half):在稍后的时间点,在使能中断的上下文中执行。用于完成耗时的处理逻辑。常见机制包括软中断(SoftIRQ)、tasklet、工作队列(workqueue)。
中断处理函数的注册与执行流程如下:
// 驱动程序注册中断处理函数示例
static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
// 上半部:快速处理硬件状态
// 例如:读取网卡状态寄存器,清中断标志
// 调度下半部(如 tasklet 或 napi_schedule)
return IRQ_HANDLED;
}
// 在驱动初始化时注册
int ret = request_irq(irq_number, my_interrupt_handler,
IRQF_SHARED, "my_driver", dev_id);
if (ret) {
pr_err("Failed to register IRQ %d\n", irq_number);
return ret;
}
4.4 中断亲和性(IRQ Affinity)的实现机制
中断亲和性通过设置 irq_desc 中的 affinity 掩码,并通知 I/O APIC 更新其重定向表来实现。当外设触发中断时,I/O APIC 根据重定向表将中断消息直接发送到掩码中指定的 CPU 核心。
性能陷阱: 如果多个高吞吐量的中断(如多个网卡队列)被绑定到同一个 CPU 核心,会导致该核心的软中断负载过重,出现“中断风暴”,而其他核心空闲。这是网络性能瓶颈的常见原因。
查看和修改中断亲和性的常用命令:
# 查看中断 17 的当前亲和性掩码(十六进制)
cat /proc/irq/17/smp_affinity
# 输出示例:00000000,00000000,00000000,00000001 (仅 CPU0)
# 将中断 17 的亲和性设置为 CPU2 和 CPU3(假设 CPU 编号从 0 开始)
echo "0c" > /proc/irq/17/smp_affinity
# 0x0c = 二进制 1100,表示 CPU2 和 CPU3
# 查看中断 17 在各个 CPU 上的分布统计
cat /proc/irq/17/smp_affinity_hint
cat /proc/interrupts | grep " 17:"
下表总结了中断子系统各组件在亲和性调优中的角色:
| 组件 | 角色 | 调优接口 |
|---|---|---|
| I/O APIC | 硬件路由决策,根据重定向表发送中断消息 | 芯片组固件 / ACPI |
| Local APIC | 接收中断消息,向 CPU 核心提交中断 | MSR 寄存器(通常不直接操作) |
| irq_desc | 内核软件抽象,存储亲和性掩码 | /proc/irq/{N}/smp_affinity |
| 中断处理函数 | 执行具体的硬件响应和数据处理 | 驱动程序注册(request_irq) |
核心结论: 中断亲和性调优的本质是操作
irq_desc->irq_data->affinity 掩码,通过 /proc 文件系统或 irqbalance 服务,将高负载中断分散到不同 CPU 核心,实现多核网络负载均衡。