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)的硬件基础。
APIC/IOAPIC 中断路由架构 APIC/IOAPIC 中断路由架构 外设 A (网卡) IRQ 17 外设 B (磁盘) IRQ 19 I/O APIC 中断重定向表 Entry 0: CPU0 Entry 1: CPU1 Entry 2: CPU2 Entry 3: CPU3 系统总线 (FSB/QPI) CPU 核心 0 Local APIC ID: 0x00 CPU 核心 1 Local APIC ID: 0x01 说明: 1. 外设中断请求发送至 I/O APIC,I/O APIC 根据重定向表选择目标 CPU。 2. 中断通过系统总线以中断消息(Interrupt Message)形式发送到目标 CPU 的 Local APIC。 3. Local APIC 接收后,向 CPU 核心触发中断信号,CPU 开始执行中断处理。

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;
}
中断处理函数执行流程 中断处理函数执行流程(上半部 + 下半部) 硬件中断触发 上半部 (Hard IRQ Handler) 关中断 → 保存状态 → 调度下半部 → 开中断 软中断 (SoftIRQ) 如 NET_RX_SOFTIRQ Tasklet 基于软中断实现 工作队列 进程上下文执行 关键原则: 上半部必须快速返回,避免长时间关中断;下半部可延迟执行,允许被其他中断抢占(软中断和 tasklet 除外)。

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 核心,实现多核网络负载均衡。