3. 中断描述符与 irq_chip:中断控制器抽象
中断子系统是 Linux 内核响应外设事件的枢纽。在 SMP(对称多处理)系统中,中断处理路径上的每一个数据结构都直接影响中断延迟和 CPU 间的负载分布。本章深入剖析中断描述符 struct irq_desc 与中断控制器抽象 struct irq_chip,为后续中断亲和性设置与负载均衡调优奠定基础。
3.1 struct irq_desc:中断源的完整档案
struct irq_desc 是内核为每一个中断号(IRQ number)维护的核心数据结构。它串联了中断的硬件状态、软件处理函数、亲和性掩码以及统计信息。在 include/linux/irqdesc.h 中定义,其关键字段如下:
| 字段 | 类型 | 作用说明 |
|---|---|---|
irq_data |
struct irq_data |
关联硬件中断控制器和中断号,包含 chip 指针和 hwirq |
handle_irq |
irq_flow_handler_t |
高层的流控处理函数(如 handle_edge_irq、handle_level_irq) |
action |
struct irqaction * |
指向中断服务例程链表的指针 |
irq_count |
unsigned int |
中断发生次数统计,用于检测“失控中断” |
irqs_unhandled |
unsigned int |
未被处理的中断计数,用于中断迁移决策 |
affinity_hint |
const struct cpumask * |
驱动或硬件建议的亲和性掩码 |
pending_mask |
struct cpumask |
待处理的 CPU 掩码,用于中断迁移时的亲和性更新 |
lock |
raw_spinlock_t |
保护该描述符的自旋锁 |
affinity_hint 字段是驱动向内核建议中断亲和性的接口。网卡驱动(如 igb、mlx5)常通过此字段将 RX 队列中断绑定到指定 CPU,实现流式负载均衡。
以下代码展示了如何通过 irq_desc 访问中断的亲和性掩码:
#include <linux/irqdesc.h>
#include <linux/irq.h>
/* 获取中断号 irq 对应的描述符 */
struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
/* 读取当前亲和性掩码 */
const struct cpumask *affinity = desc->irq_data.common->affinity;
if (cpumask_test_cpu(smp_processor_id(), affinity))
pr_info("IRQ %d 允许在当前 CPU 上处理\n", irq);
3.2 struct irq_chip:中断控制器的硬件抽象
struct irq_chip 是内核与具体中断控制器硬件之间的适配层。它定义了一组回调函数,用于操作中断控制器的寄存器。无论是 GIC(通用中断控制器)、IO-APIC 还是 MSI-X,都通过 irq_chip 统一接口与内核交互。
核心回调函数如下表所示:
| 回调函数 | 用途 | 典型实现动作 |
|---|---|---|
irq_startup |
启动中断 | 使能中断控制器对应的中断线 |
irq_shutdown |
关闭中断 | 屏蔽中断线并清理待处理状态 |
irq_enable |
使能中断 | 写寄存器置位中断使能位 |
irq_disable |
禁用中断 | 写寄存器清除中断使能位 |
irq_ack |
应答中断 | 向控制器写入 EOI(End Of Interrupt)命令 |
irq_mask |
屏蔽中断 | 设置屏蔽寄存器,阻止中断传递到 CPU |
irq_unmask |
解除屏蔽 | 清除屏蔽寄存器,允许中断传递 |
irq_set_affinity |
设置亲和性 | 写中断控制器的路由寄存器,将中断定向到指定 CPU |
irq_retrigger |
重新触发中断 | 模拟一次硬件中断触发,用于软件重试 |
irq_mask 与 irq_disable 语义不同。mask 通常用于流控处理中的临时屏蔽,而 disable 用于永久关闭中断。在中断亲和性调整过程中,内核会先 mask 中断,更新路由寄存器,再 unmask,确保不会丢失中断。
以下是一个简化的自定义 irq_chip 实现示例:
static void my_chip_mask(struct irq_data *data) {
void __iomem *base = irq_data_get_irq_chip_data(data);
writel(0, base + MASK_REG_OFFSET);
}
static void my_chip_unmask(struct irq_data *data) {
void __iomem *base = irq_data_get_irq_chip_data(data);
writel(1, base + MASK_REG_OFFSET);
}
static int my_chip_set_affinity(struct irq_data *data,
const struct cpumask *dest, bool force) {
unsigned int cpu = cpumask_first(dest);
void __iomem *base = irq_data_get_irq_chip_data(data);
/* 将中断路由到指定 CPU 的寄存器 */
writel(cpu, base + AFFINITY_REG_OFFSET);
return IRQ_SET_MASK_OK;
}
static struct irq_chip my_irq_chip = {
.name = "my_controller",
.irq_mask = my_chip_mask,
.irq_unmask = my_chip_unmask,
.irq_set_affinity = my_chip_set_affinity,
};
3.3 中断控制器抽象与层级关系
现代 SoC 通常包含多个中断控制器,形成树形层级。内核通过 irq_domain 将硬件中断号(hwirq)映射到 Linux IRQ 号,而每个 irq_chip 负责操作其对应的控制器硬件。
下图展示了典型的中断控制器层级与数据结构关系:
3.4 irq_data:连接描述符与控制器的桥梁
struct irq_data 是 irq_desc 与 irq_chip 之间的纽带。它包含了中断控制器相关的所有硬件信息:
- irq:Linux IRQ 号。
- hwirq:硬件中断号,用于控制器内部寻址。
- chip:指向
irq_chip结构体的指针。 - chip_data:芯片私有数据,通常指向控制器的 MMIO 基地址。
- domain:指向所属的
irq_domain,用于反向查找映射关系。 - affinity:当前中断的 CPU 亲和性掩码。
chip->irq_set_affinity(data, mask, force)。对于不支持动态亲和性的老旧控制器,此回调可能为空,此时亲和性设置会失败。使用 /proc/irq/<irq>/smp_affinity 写入前,建议先检查芯片能力。
3.5 中断流控处理函数
irq_desc->handle_irq 指向流控处理函数,它根据中断类型(边沿触发、电平触发)调用不同的处理逻辑。常见的流控函数包括:
| 流控函数 | 适用场景 | 行为特点 |
|---|---|---|
handle_level_irq |
电平触发中断 | 进入 handler 前 mask 中断,处理完成后 unmask |
handle_edge_irq |
边沿触发中断 | 处理期间不 mask,但需要 ack 清除边沿锁存 |
handle_fasteoi_irq |
GIC 等支持 EOI 的控制器 | 不 mask,直接调用 chip->irq_eoi 结束中断 |
handle_percpu_devid_irq |
Per-CPU 中断(如本地定时器) | 不需要锁,直接在当前 CPU 上执行 |
流控函数的选择直接影响中断延迟和亲和性调整的时机。例如,handle_level_irq 在 mask 期间会丢失中断,因此亲和性迁移必须等待处理完成。
3.6 中断描述符的初始化流程
从硬件中断触发到软件处理的完整链路如下:
- 硬件触发:外设拉高中断线,中断控制器检测到有效信号。
- 控制器仲裁:GIC/IO-APIC 根据优先级和亲和性设置,将中断路由到目标 CPU。
- CPU 异常入口:CPU 保存上下文,跳转到
handle_arch_irq或do_IRQ。 - 查找 irq_desc:根据硬件中断号通过
irq_domain找到对应的irq_desc。 - 调用流控函数:执行
desc->handle_irq,进行 mask/ack/eoi 操作。 - 执行 action 链表:遍历
desc->action,调用注册的中断服务例程。 - 清理与返回:unmask 中断(如果需要),恢复 CPU 上下文。
irq_domain 查找和步骤 5 的流控函数调用是中断延迟的主要开销。使用 CONFIG_IRQ_DOMAIN_HIERARCHY 和线性映射域可以降低查找开销。
3.7 本章小结
- struct irq_desc 是每个中断的“档案袋”,包含状态、处理函数、亲和性掩码和统计信息。
- struct irq_chip 是中断控制器的软件抽象,通过回调函数统一操作硬件寄存器。
- irq_data 作为桥梁,将 irq_desc 与 irq_chip 关联,并携带硬件中断号和芯片私有数据。
- 流控处理函数(
handle_level_irq、handle_edge_irq等)决定了中断处理过程中的 mask/ack 策略,直接影响亲和性迁移的时机。 - 理解这些数据结构是进行中断亲和性设置和负载均衡调优的前提,后续章节将基于此深入
/proc/irq接口和动态平衡算法。