引言:中断处理的挑战与抽象
在复杂的现代计算系统中,硬件设备(如网卡、磁盘、键盘)通过中断信号来通知 CPU 有事件需要处理。然而,不同架构(x86, ARM)、不同总线(PCIe, USB)和不同控制器(GIC, APIC, 8259)的中断机制千差万别。如果每个驱动都直接与底层硬件打交道,内核将变得极其臃肿且难以维护。
Linux IRQ 子系统的诞生就是为了解决这一复杂性。它通过一套精巧的、分层的数据结构和接口,向上为设备驱动提供统一、简单的中断注册和管理 API(如 request_irq),向下则通过可插拔的“中断控制器驱动”来适配各种硬件。这套系统的核心就是我们今天要深入剖析的几大数据结构。
1. irq_desc – 中断描述符:中断世界的“户口本”
定义位置: include/linux/irq.h, kernel/irq/irqdesc.c
核心作用: irq_desc 是整个 IRQ 子系统的基石。每一个软件中断号(IRQ number)在内核中都有一个唯一的 irq_desc 实例与之对应。你可以把它想象成这个中断号的“户口本”或“档案袋”,里面存放了关于这个中断的所有信息和状态。
struct irq_desc {
struct irq_common_data irq_common_data; // 公共数据
struct irq_data irq_data; // 中断数据 (关键!)
unsigned int __percpu *kstat_irqs; // 每CPU中断计数统计
irq_flow_handler_t handle_irq; // 中断流处理函数 (关键!)
struct irqaction *action; // 中断处理动作链表 (关键!)
unsigned int status_use_accessors; // 状态标志
unsigned int depth; // 嵌套深度
raw_spinlock_t lock; // 自旋锁 (关键!)
const char *name; // 中断名称
// … 其他字段
};
背后原理详解:
- IRQ_DISABLED: 中断被禁用,即使硬件触发,CPU 也不会响应。
- IRQ_PENDING: 硬件已触发中断,但因为被禁用等原因,尚未被处理。
- IRQ_INPROGRESS: 中断正在被处理中,防止重入。
这些状态由内核内部严格管理,驱动通常通过 enable_irq() / disable_irq() 等 API 间接影响它们。
总结: irq_desc 是内核管理和跟踪单个中断号全生命周期的核心容器,它将硬件细节、处理逻辑和运行状态完美地封装在一起。
2. irq_data – 中断数据:连接软件与硬件的桥梁
定义位置: include/linux/irq.h
核心作用: irq_data 是 irq_desc 结构体中的一个关键成员。如果说 irq_desc 是“户口本”,那么 irq_data 就是其中专门记录“与硬件交互相关”信息的一页。它是软件中断号(irq)和硬件中断号(hwirq)之间的映射载体,并持有指向具体中断控制器(irq_chip)和中断域(irq_domain)的指针。
struct irq_data {
u32 mask;
unsigned int irq; // Linux 软件中断号
unsigned long hwirq; // 硬件中断号
struct irq_common_data *common; // 指向公共数据
struct irq_chip *chip; // 指向中断控制器 (关键!)
struct irq_domain *domain; // 指向中断域名 (关键!)
struct irq_data *parent_data; // 用于级联控制器
void *chip_data; // 控制器私有数据 (关键!)
};
背后原理详解:
- irq: 这是 Linux 内核全局使用的、连续的软件中断号。驱动通过它来请求中断(request_irq(irq, …))。
- hwirq: 这是特定硬件控制器(如 GIC)内部使用的、可能不连续的物理中断号。
- 映射关系: irq_domain 负责建立并维护 hwirq 到 irq 的映射。irq_data 正是这个映射关系的具体体现。当你调用 irq_create_mapping(domain, hwirq) 时,内核会分配一个 irq,并在对应的 irq_desc->irq_data 中填入这对 (irq, hwirq)。
总结: irq_data 是 IRQ 子系统分层设计思想的集中体现。它解耦了上层通用逻辑和底层硬件驱动,使得整个中断框架具有极强的可扩展性和可移植性。
3. irq_chip – 中断控制器操作接口:硬件的“遥控器”
定义位置: include/linux/irq.h
核心作用: irq_chip 是一个函数指针集合,它定义了一套标准的操作接口,用于控制具体的中断控制器硬件。每个中断控制器驱动(如 gic-v3.c, i8259.c)都需要实现一个 irq_chip 结构体,并填充它支持的操作。内核通用代码通过 irq_data->chip 来调用这些操作,从而实现了对硬件的统一控制。
struct irq_chip {
const char *name;
// 中断使能/禁用
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
// 中断流程控制
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data); // End Of Interrupt
// 高级特性
int (*irq_set_affinity)(…); // 设置CPU亲和性
int (*irq_set_type)(…); // 设置触发类型 (边沿/电平)
int (*irq_set_wake)(…); // 设置唤醒能力
// … 其他操作
};
背后原理详解:
- 电平触发: 通常需要先 mask 掉中断,处理完后再 unmask,否则只要电平有效,中断会一直产生。
- 边沿触发: 通常只需要 ack 或 eoi 来清除中断状态位。
总结: irq_chip 是 IRQ 子系统面向硬件的接口。它使得内核能够以一种统一、优雅的方式驾驭各种复杂的中断控制器硬件,是硬件抽象层(HAL)思想的完美实践。
4. irq_domain – 中断域名:中断号的“翻译官”
定义位置: include/linux/irqdomain.h
核心作用: 在现代系统中,尤其是使用设备树(Device Tree)的 ARM 系统,存在多个中断控制器,形成了一个层次化的中断拓扑。irq_domain 就是用来管理这种层次化结构,并负责在局部的硬件中断号(hwirq)和全局的 Linux 软件中断号(irq)之间进行翻译。
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops; // 操作函数集 (关键!)
void *host_data; // 私有数据
fwnode_handle_t *fwnode; // 设备树节点
// 映射数据结构
struct radix_tree_root revmap_tree; // 树映射
unsigned int linear_revmap[]; // 线性映射
};
背后原理详解:
- 线性映射 (IRQ_DOMAIN_MAP_LINEAR): 适用于 hwirq 连续且数量不多的情况。使用一个数组,hwirq 直接作为数组下标,查找速度 O(1)。
- 树映射 (IRQ_DOMAIN_MAP_TREE): 适用于 hwirq 稀疏或范围很大的情况。使用 Radix 树存储映射关系,内存效率高。
- 无映射 (IRQ_DOMAIN_MAP_NOMAP): 仅用于非常老的 Legacy IRQ,irq == hwirq。
- map(): 当一个新的 hwirq 需要映射到 irq 时被调用。在这里,驱动通常会设置好该中断对应的 irq_chip 和流处理函数 (irq_set_chip_and_handler)。
- xlate(): 用于解析设备树中的中断属性。设备树会描述一个设备连接到哪个中断控制器的哪个 hwirq 上以及触发类型。xlate 函数负责将这些信息提取出来,转换成 hwirq 和 type。
典型工作流程:
总结: irq_domain 是现代 Linux 内核支持复杂、层次化中断硬件的关键。它不仅解决了中断号冲突问题,还通过与设备树的紧密结合,实现了硬件资源的自动发现和配置。
5. irq_common_data – 中断公共数据:共享的“便签条”
定义位置: include/linux/irq.h
核心作用: 这个结构体被 irq_desc 和 irq_data 共同包含或引用,用于存储那些所有中断类型都可能用到的公共信息。它的设计体现了数据复用的思想。
struct irq_common_data {
void *handler_data; // 处理函数的私有数据 (关键!)
void *msi_desc; // MSI/MSI-X 中断描述符
struct cpumask *affinity; // CPU 亲和性掩码 (关键!)
};
背后原理详解:
总结: irq_common_data 虽然结构简单,但它承载了中断处理中最常用、最通用的信息,是连接驱动逻辑和内核中断框架的重要纽带。
结语:协同工作的艺术
Linux IRQ 子系统的强大之处不在于单个数据结构的复杂,而在于这些结构之间清晰的职责划分和精妙的协作。
- irq_domain 负责全局的中断号管理和硬件拓扑建模。
- irq_desc 作为每个中断号的管理中心,持有其全部状态和处理逻辑。
- irq_data 作为 irq_desc 的一部分,桥接了软件世界(irq)和硬件世界(hwirq, chip)。
- irq_chip 提供了操作具体硬件的标准接口。
- irq_common_data 则高效地复用了公共信息。
通过这套设计,Linux 内核成功地将中断处理这一底层、复杂且与硬件紧密耦合的功能,抽象成了一个稳定、高效、可扩展的子系统,为上层驱动开发者提供了简洁一致的编程体验。理解这些核心数据结构,是深入掌握 Linux 内核中断机制的第一步。






