Linux中断处理流程
在Linux内核中,中断控制器管理硬件中断号到Linux中断号的映射,并通过中断描述符(struct irq_desc
)进行管理。存储这种映射关系的方式取决于中断编号的连续性,具体实现如下:
1. 数组存储(连续中断号)
适用场景:当中断号是连续的且数量有限时,内核使用静态数组存储映射关系。
实现方式:
定义一个全局数组
irq_desc[NR_IRQS]
,其中NR_IRQS
是内核支持的最大中断号数量。每个中断号直接对应数组的索引。例如,中断号
n
的描述符为irq_desc[n]
。初始化时,所有描述符预分配并填充默认值(如
handle_bad_irq
、depth=1
等),确保未使用的中断号也有有效描述符。优点:访问速度快(O(1)时间复杂度),内存连续,适合密集且连续的编号。
缺点:若中断号稀疏或范围较大(如存在空洞或超大编号),会导致内存浪费。
2. 基数树存储(稀疏中断号)
适用场景:当中断号不连续或范围较大时,使用基数树(Radix Tree)动态管理映射。
实现方式:
内核配置需开启
CONFIG_SPARSE_IRQ
。基数树按需分配节点,仅存储实际使用的中断号及其对应的
irq_desc
。Linux中断号由内核动态分配,与硬件中断号解耦,避免静态数组的限制。
优点:节省内存,支持稀疏或超大中断号。
缺点:访问复杂度略高(接近 O(log n)),动态分配需要额外开销。
1.中断描述符
1. 作用
struct irq_desc
是Linux内核中用于管理单个中断源的核心数据结构。每个硬件中断号(或虚拟中断号)在内核中对应一个irq_desc
实例,负责记录中断的状态、处理函数、锁机制以及关联的设备处理动作。它是中断子系统的基础,确保中断能够被正确路由和处理。
2. 核心字段
以下是
struct irq_desc
中最重要的字段及其作用:
字段名 类型 说明 irq_data
struct irq_data
存储与中断控制器相关的底层信息,如硬件中断号、触发方式(边沿/电平)、中断芯片等。 handle_irq
irq_flow_handler_t
指向中断流处理函数(如 handle_edge_irq
或handle_level_irq
),负责处理中断的上下半部。action
struct irqaction *
指向 irqaction
链表的头节点,表示注册到该中断的所有设备处理函数。lock
spinlock_t
自旋锁,保护中断描述符的并发访问。 depth
unsigned int
中断禁用计数器: depth > 0
表示中断被禁用。status
unsigned int
中断状态标志(如 IRQ_DISABLED
、IRQ_INPROGRESS
)。
2.irq_desc、irq_chip和irq_domain的区别
1. 核心区别
组件 作用 层级 核心职责 irq_desc
管理单个中断源的处理逻辑和状态,包括中断处理函数、设备注册信息、锁机制等。 高层抽象 中断的软件管理(状态、处理流程) irq_chip
定义中断控制器的硬件操作接口(如使能、禁用、确认中断),屏蔽不同中断控制器的差异。 底层硬件抽象 直接操作中断控制器的硬件行为 irq_domain
管理硬件中断号(HW IRQ)到Linux虚拟中断号(IRQ)的映射关系,支持复杂中断拓扑结构。 中间抽象层 中断号映射与多中断控制器的协调
2. 关键结构与功能
(1)
irq_desc
:中断描述符
核心字段:
irq_data
:关联底层硬件信息(如irq_chip
和irq_domain
)。
handle_irq
:中断流处理函数(如handle_edge_irq
)。
action
:设备驱动注册的中断处理函数链表(irqaction
)。
lock
:保护并发访问的自旋锁。作用:记录中断状态、处理流程和设备注册信息,是中断管理的核心单元。
(2)
irq_chip
:中断控制器抽象
核心操作函数:
struct irq_chip {void (*enable)(struct irq_data *data); // 使能中断void (*disable)(struct irq_data *data); // 禁用中断void (*ack)(struct irq_data *data); // 确认中断处理完成int (*set_type)(struct irq_data *data, unsigned int type); // 设置触发类型(边沿/电平) };
作用:为不同中断控制器(如PIC、APIC、GIC)提供统一硬件操作接口,实现硬件无关性。
(3)
irq_domain
:中断域
核心功能:
将硬件中断号(HW IRQ)映射到Linux虚拟中断号(IRQ)。
支持多种映射方式(线性映射、树形映射、动态分配)。
管理多级中断控制器的级联关系(如GPIO控制器级联到主中断控制器)。
关键方法:
struct irq_domain_ops {int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hwirq);void (*unmap)(struct irq_domain *d, unsigned int virq);// 其他方法(如分配、释放中断号) };
3. 层级关系与协作流程
层级关系:
irq_domain → irq_desc → irq_chip
irq_domain
:负责硬件中断号到Linux中断号的映射,生成对应的irq_desc
。
irq_desc
:通过irq_data
关联到irq_domain
和irq_chip
。
irq_chip
:通过irq_data
获取硬件上下文,执行具体的中断控制器操作。协作流程示例(以中断触发为例):
硬件中断触发:设备产生硬件中断号
hwirq
。映射到Linux中断号:
irq_domain
将hwirq
转换为Linux虚拟中断号virq
。获取中断描述符:通过
virq
找到对应的irq_desc
。处理中断:
调用
irq_desc->handle_irq
执行中断流处理。遍历
irq_desc->action
链表,执行设备驱动注册的中断处理函数。操作硬件:通过
irq_desc->irq_data->irq_chip
调用ack()
或disable()
等函数,操作中断控制器。
4. 设计意义与应用场景
组件 设计目标 典型应用场景 irq_desc
统一管理中断状态和流程 所有中断处理场景(如设备驱动注册中断) irq_chip
抽象不同中断控制器的硬件操作 适配不同硬件平台(如ARM GIC、x86 APIC) irq_domain
解决复杂中断号映射问题 多级中断控制器、动态中断分配(如PCI设备)
多级中断控制器:
例如,GPIO控制器级联到主中断控制器时,每个控制器有自己的irq_domain
,irq_domain
负责级联中断号的映射。动态中断分配:
PCI设备的中断号可能动态变化,irq_domain
使用基数树动态分配Linux中断号。
5. 总结对比
特性 irq_desc
irq_chip
irq_domain
核心职责 中断状态管理与处理流程 中断控制器的硬件操作 硬件中断号到Linux中断号的映射 依赖关系 通过 irq_data
关联irq_chip
和irq_domain
由 irq_data
提供硬件上下文独立管理中断号映射,生成 irq_desc
典型操作 注册中断处理函数、管理中断禁用计数 使能/禁用中断、确认中断 分配/释放中断号、处理多级中断控制器级联 设计目标 统一中断处理逻辑 屏蔽硬件差异 解决复杂中断拓扑的映射问题
6. 关联示意图
硬件中断 (hwirq)│▼ irq_domain(映射 hwirq → virq)│▼ irq_desc(virq对应的描述符)│▼ irq_chip(操作中断控制器硬件)
通过
irq_domain
、irq_desc
和irq_chip
的分工协作,Linux内核实现了对中断的全面管理:
irq_domain
解决中断号的动态映射问题,支持复杂硬件拓扑。
irq_chip
屏蔽硬件差异,使上层代码无需关心具体中断控制器。
irq_desc
统一管理中断状态和处理流程,为设备驱动提供简洁接口。
3.处理流程详解
在 ARM64 架构下,在异常级别 1 的异常向量表中,中断的入口有 3 个:
- 如果处理器处在内核模式(异常级别 1),中断的入口是 el1_ira;
- 如果处理器处在用户模式(异常级别 0)下执行 64 位应用程序,中断的入口函数是 el0_irq;
- 如果处理器正在用户模式(异常级别 0)下执行 32 位应用程序,中断的入口函数是 el0_irq_compat。
以网卡接收数据触发中断的处理流程为例详解:
一、硬件中断触发:网卡 → GIC → CPU
网卡硬件动作
网卡接收到网络数据后,通过内部电路触发物理中断引脚(该引脚连接到 GIC 的输入线)。例如,网卡中断引脚连接到 GIC 的
SPI 99
(硬件中断号hwirq=99
)。此时,网卡通过硬件信号告知 GIC 有中断事件发生。GIC 处理
GIC 的
Distributor
模块检测到中断请求后,借助irq_chip
提供的操作方法(如ack
确认中断),完成对中断的初步处理:
- 通过
irq_chip
的ack
方法(如 GIC 的gic_ack
函数),将中断号写入 GIC 特定寄存器,标记中断已被 CPU 接收。- 将中断路由到目标 CPU 核。此时调用
handle_arch_irq
函数,进入 GIC 的中断处理函数(如gic_handle_irq
),正式启动内核中断处理流程。二、中断号映射与分发
获取硬件中断号
GIC 处理函数从寄存器读取硬件中断号(如
hwirq=99
),并判断其范围:
- 若
16 ≤ hwirq < 1020
(属于外部设备中断范围),执行以下操作:
- 将中断号写入 GIC 的 “中断结束寄存器”,标记中断处理开始。
- 调用
handle_domain_irq
,进入中断号映射流程。
irq_domain
映射中断号
handle_domain_irq
通过irq_domain
结构体(负责管理硬件中断号到 Linux 中断号的映射),执行irq_find_mapping
函数:
irq_domain
根据硬件中断号hwirq=99
,查找预定义的映射关系,得到对应的 Linux 虚拟中断号(假设映射结果为linux_irq=128
)。这一步确保内核用统一的 Linux 中断号管理硬件中断。三、定位中断描述符
irq_desc
内核根据映射得到的 Linux 虚拟中断号
linux_irq=128
,索引到全局数组irq_desc[128]
。irq_desc
存储该中断的核心信息:
- 1.中断处理函数链表(
irqaction
链表):用于管理共享此中断号的所有设备的中断处理函数。若多个设备共享linux_irq=128
,每个设备的irqaction
会通过链表组织。
链表的构成与用途
irqaction
链表是存储在irq_desc
结构体中的一个链表,用于管理共享同一中断号的多个设备的中断处理函数。在系统中,多个设备可能会共享同一个中断号,每个设备都会有自己对应的irqaction
结构体,这些结构体通过链表的形式组织起来。当该中断号对应的中断发生时,内核会依次遍历这个链表,调用每个irqaction
结构体中的中断处理函数。链表的管理与操作
注册:当设备驱动程序初始化时,会调用
request_irq
函数来注册中断处理函数。该函数会创建一个irqaction
结构体,并将其添加到对应irq_desc
的irqaction
链表中。例如,网卡驱动在初始化时会注册自己的irqaction
结构体:static irqreturn_t nic_irq_handler(int irq, void *dev_id) {// 中断处理逻辑return IRQ_HANDLED; }struct irqaction nic_irqaction = {.handler = nic_irq_handler,.flags = IRQF_SHARED, // 表示该中断是共享的.name = "nic_irq",.dev_id = &nic_device, };request_irq(nic_irq, &nic_irqaction);
当中断发生时,内核会调用
generic_handle_irq_desc
函数,该函数会遍历irqaction
链表,依次调用每个irqaction
结构体中的handler
函数。每个handler
函数需要通过dev_id
来判断是否是自己设备产生的中断,并进行相应的处理。- 2.中断控制器操作方法(通过
irq_chip
关联):irq_desc
中包含指向irq_chip
的指针,通过irq_chip
定义的方法(如mask
、unmask
、eoi
等),实现对 GIC 的底层控制。
irq_chip
结构体的作用
irq_chip
结构体定义了一系列用于操作中断控制器的方法,这些方法封装了对中断控制器硬件的底层操作,如中断的确认、使能、屏蔽、结束等。irq_desc
结构体中包含一个指向irq_chip
的指针,通过这个指针可以调用irq_chip
中定义的方法来控制中断控制器。常见的操作方法
ack
方法:用于确认中断。当中断控制器接收到中断请求并通知 CPU 后,CPU 需要通过调用ack
方法来告诉中断控制器已经开始处理该中断。例如,GIC 的ack
方法会将中断号写入特定的寄存器,标记中断处理开始。static void gic_ack(struct irq_data *d) {// 将中断号写入GIC的中断确认寄存器writel_relaxed(d->irq, gic_dist_base + GIC_DIST_ACK); }
mask
方法:用于屏蔽中断。在某些情况下,需要暂时禁止某个中断的触发,此时可以调用mask
方法。例如,在进行一些关键操作时,为了避免中断干扰,可以屏蔽网卡中断。static void gic_mask(struct irq_data *d) {// 设置GIC的中断屏蔽寄存器writel_relaxed(1 << (d->irq & 31), gic_dist_base + GIC_DIST_ENABLE_CLEAR); }
四、执行中断处理函数
1.触发
handle_irq
入口函数内核在初始化中断控制器(如 GIC)时,会根据硬件中断号范围(<32 或≥32)设置 irq_desc 的 handle_irq 函数指针。
irq_desc[linux_irq].handle_irq
根据硬件中断号是否小于 32,被设置为不同的函数:
- 硬件中断号 < 32:
handle_percpu_devid_irq
(用于早期的单 CPU 中断)。- 硬件中断号 ≥ 32:
handle_fasteoi_irq
(用于现代多核中断)。比如在网卡接收数据的这个案例中处理逻辑如下:
void handle_fasteoi_irq(struct irq_desc *desc) {// 快速EOI中断处理流程desc->irq_data.chip->ack(&desc->irq_data);generic_handle_irq_desc(desc); // 遍历irqaction链表desc->irq_data.chip->eoi(&desc->irq_data); }
2.遍历
irqaction
链表
irq_desc[128]
维护irqaction
链表。调用generic_handle_irq
,最终触发generic_handle_irq_desc
,遍历链表:
- 若链表中存在网卡驱动注册的
irqaction
,则调用其.handler
函数(如nic_irq_handler
)。3.网卡驱动的
irqaction
工作网卡驱动注册的
irqaction
包含:
.handler
:中断处理函数(如nic_irq_handler
)。.dev_id
:网卡设备标识,用于区分共享中断的不同设备。
nic_irq_handler
执行:
- 读取数据:通过寄存器操作,读取网卡接收缓冲区数据。
- 数据封装与提交:将数据封装成网络协议帧,提交给上层网络协议栈(如 Linux 网络子系统)。
- 清除中断状态:清除网卡中断状态寄存器,准备下次中断接收。
中断下半部(软中断)处理
- 触发软中断:在中断处理函数中,若有耗时操作(如协议处理)不适合在中断处理函数中完成,因此引入了中断下半部分(软中断)的机制,软中断可以在中断处理函数返回后,在合适的时机(如系统空闲时)执行,从而避免影响系统的实时性。通过
netif_rx
等函数触发软中断(如NET_RX_SOFTIRQ
)。
- 网卡驱动的中断处理函数(
nic_irq_handler
)作为中断上半部分,主要完成一些紧急的操作,如读取网卡接收缓冲区的数据,并将数据传递给软中断处理函数。static irqreturn_t nic_irq_handler(int irq, void *dev_id) {// 读取网卡接收缓冲区的数据struct sk_buff *skb = nic_read_rx_buffer();if (skb) {// 将数据传递给软中断处理函数netif_rx(skb);}return IRQ_HANDLED; }
- 软中断处理函数:系统在合适时机(如
do_softirq
执行时),调用软中断处理函数(如net_rx_action
):
- 从接收队列取出数据,进行详细解析、协议处理(如 IP 层、TCP 层处理),最终将数据传递给应用层。
static void net_rx_action(struct softirq_action *h) {struct sk_buff *skb;while ((skb = skb_dequeue(&rx_queue))) {// 数据解析、协议处理等操作netif_receive_skb(skb);} }open_softirq(NET_RX_SOFTIRQ, net_rx_action);
五、中断处理完成
处理函数(含软中断)执行完毕后,通过
irq_chip
的.eoi
方法(如gic_eoi
):
- 告知 GIC 中断处理完成。GIC 更新寄存器,允许后续同类型中断再次触发,完成一次完整的中断处理闭环。
关键结构体协作总结
结构体 作用 irq_chip
提供 GIC 操作方法( ack
、eoi
、mask
等),直接控制中断控制器硬件行为。irq_domain
管理硬件中断号到 Linux 中断号的映射,确保内核正确识别中断来源。 irq_desc
存储中断描述符,维护 irqaction
链表,是中断处理的核心管理结构。irqaction
包含网卡驱动的中断处理函数,执行数据读取、协议处理等业务逻辑。
https://github.com/0voice