一、Linux中断系统演进二、Linux对中断的扩展:硬件中断、软件中断2.1 概念2.2 硬件与软件中断区别2.3 硬件和软件中断关系三、硬件中断处理流程3.1 中断处理原则1:不能嵌套3.2 中断处理原则2:越快越好四、上半部和下半部机制4.1 源码分析4.2 流程图说明情景 a. 硬件中断 A 处理过程中,没有其他中断发生情景 b. 硬件中断 A 处理过程中,又再次发生了中断 A情景 c. 硬件中断 A 处理过程中,又再次发生了中断 B五、软件中断详解5.1 主要数据结构5.2 软件中断执行流程5.3 多种下半部机制的并存六、Tasklet机制6.1 简介Tasklet 概念应用领域6.2 实现细节Tasklet 数据结构Tasklet 与软中断的关系Tasklet 执行流程Tasklet 调度函数Tasklet 处理函数Per-CPU 变量Tasklet 初始化与控制函数Tasklet 设计优势Tasklet vs. 其他延迟执行机制6.3 源码示例与应用基本 Tasklet 使用方法高优先级 Tasklet 示例Tasklet 控制示例网络驱动中的 Tasklet 应用七、工作队列(Workqueue7.1 简介7.2 实现细节7.2.1 核心数据结构7.2.1.1 工作项 (work_struct)7.2.1.2 工作队列 (workqueue_struct)7.2.1.3 工作者线程池 (worker_pool)7.2.2 函数调用栈7.2.2.1 初始化工作项7.2.2.2 提交工作项7.2.2.3 工作线程执行循环7.2.2.4 执行工作项7.2.3 工作队列的状态管理 7.3代码示例:使用工作队列7.3.1 基本用法7.3.2 延迟执行工作7.3.3 创建自定义工作队列八、线程化中断(Threaded IRQ)
参考资料
- 异常和中断,以及线程和进程处理机制可参考原理篇03-实现上下文处理关键-异常处理 | Felix’s Micro Space
一、Linux中断系统演进
- 早期Linux内核(2.4之前):使用较为简单的中断处理机制,所有中断处理程序都在中断上下文中完成,容易导致中断响应延迟。
- Linux 2.6:引入了"上半部"(top half)和"下半部"(bottom half)的概念,分离紧急和非紧急处理,提升中断响应能力。
- Linux 4.x:进一步优化了中断处理框架,引入通用中断控制器(GIC)支持,增强了多核SMP系统中的中断亲和性(Interrupt Affinity)支持。
- Linux 4.9.88:完善了中断线程化处理机制,改进了实时性能和中断控制器抽象层。
二、Linux对中断的扩展:硬件中断、软件中断
2.1 概念
Linux系统中的中断机制可以分为两大类:硬件中断和软件中断。这两类中断共同构成了Linux完整的中断处理框架,使操作系统能够高效地响应外部事件并平衡实时性(快速响应中断)与系统吞吐量(高效处理大量工作)。
- 硬件中断(Hardware Interrupt)是由外部设备(如键盘、网卡、硬盘控制器等)或CPU内部事件(如时钟、异常等)触发的中断信号。它们通过物理中断线连接到中断控制器,然后传递给CPU,强制CPU暂停当前执行的程序,转而执行中断处理程序。硬件中断可用于设备驱动、时钟中断、外部事件响应等。
- 软件中断(Software Interrupt)是由操作系统内部代码触发的中断,本质上是一种延迟执行机制,用于处理不需要立即完成且可能耗时较长的任务。Linux中,软件中断是实现"下半部"(Bottom Half)中断处理的主要机制之一。软中断可用于网络协议栈处理、块设备I/O完成、调度、定时器等
2.2 硬件与软件中断区别
2.3 硬件和软件中断关系
三、硬件中断处理流程
对硬件中断的处理有2个原则:不能嵌套,越快越好。
3.1 中断处理原则1:不能嵌套
中断嵌套会导致栈被消耗完,为了简单化中断的处理,在Linux系统上中断无法嵌套:即当前中断A没处理完之前,不会响应另一个中断B(即使它的优先级更高)。
3.2 中断处理原则2:越快越好
在中断的处理过程中,该CPU是不能进行进程调度的,所以中断的处理要越快越好,尽早让其他中断能被处理──进程调度靠定时器中断来实现。
在Linux系统中使用中断是挺简单的,为某个中断irq注册中断处理函数handler,可以使用
request_irq
函数:在handler函数中,代码尽可能高效。
假如某个中断工作量很大,没办法加快。比如对于按键中断,需要等待几十毫秒消除机械抖动。当一个中断要耗费很多时间来处理时,它的坏处是:在这段时间内,其他中断无法被处理。因为这段时间是关闭中断的。
要如何避免中断服务程序阻塞太长时间?
四、上半部和下半部机制
如果某个中断就是要做那么多事,我们能不能把它拆分成两部分:紧急的、不紧急的?在handler函数里只做紧急的事,然后就重新开中断,让系统得以正常运行;那些不紧急的事,以后再处理,处理时是开中断的。
- 上半部(Top Half):
- 硬件中断处理
- 快速响应,最小化中断屏蔽时间
- 只处理关键和紧急的任务
- 下半部(Bottom Half):
- 软件中断处理
- 延迟执行,可被抢占
- 处理非紧急但耗时的任务
4.1 源码分析

硬件中断处理流程:
- 调用
irq_enter()
,该函数会增加preempt_count
的值(通过preempt_count_add(HARDIRQ_OFFSET)
)。preempt_count
用于表示当前 CPU 进入了硬件中断上下文。
generic_handle_irq(irq)
: 调用与该中断号关联的中断处理函数,通常是通过request_irq
注册的函数。
- 调用
irq_exit
函数,从preempt_count
中减去HARDIRQ_OFFSET
,表示硬件中断处理完成。
软中断的处理是通过
do_softirq
函数完成的。流程如下:local_bh_disable_ip(RET_IP, SOFTIRQ_OFFSET)
:- 通过
local_bh_disable
禁用软中断。 preempt_count++
表明进入软中断上下文。
local_irq_enable()
- 执行软中断时会短暂打开硬件中断(即重新允许处理中断),以防止阻塞其他高优先级中断。
invoke_softirq()
:- 调用所有挂起的软中断处理函数。
- 针对系统中的每种类型的软中断,都会分别执行其对应的处理函数(比如网络子系统处理
NET_RX_SOFTIRQ
、块设备 I/O 处理BLOCK_SOFTIRQ
等)。
- 软中断完成后,调用
local_bh_enable
重新关闭软中断,并减少preempt_count
。
preempt_count
和中断嵌套管理preempt_count
是 Linux 内核中用于管理中断上下文、抢占以及嵌套处理的计数器。
4.2 流程图说明
情景 a. 硬件中断 A 处理过程中,没有其他中断发生
- 纯粹的中断 A 响应即按流程完成处理。
- 中断进入后,通过
irq_enter()
将preempt_count++
,执行硬件中断的上半部处理逻辑。
- 上半部处理完成后,调用
irq_exit()
,通过preempt_count--
退出硬件中断。
- 若有挂起的软中断,执行软中断处理,上半部、下半部的代码各执行一次。
情景 b. 硬件中断 A 处理过程中,又再次发生了中断 A
出现中断嵌套的情况,此时需要处理两次硬件中断 A,可能导致
preempt_count
值的连续增加与减少。- 第一个硬件中断 A 进入时,
preempt_count++
进入硬件中断上下文并开始处理上半部。
- 在上半部处理期间,再次发生硬件中断 A,嵌套触发,再次调用
irq_enter()
,preempt_count++
值累加(变为 2),继续处理新中断。
- 第二个硬件中断完成后退出时,
preempt_count--
降到 1,回到外层中断的上下文。
- 第2次中断发生后,打断了第一次中断的第⑦步处理。当第2次中断处理完毕,CPU会继续去执行第⑦步。可以看到,发生2次硬件中断A时,它的上半部代码执行了2次,但是下半部代码只执行了一次。所以,同一个中断的上半部、下半部,在执行时是多对一的关系
情景 c. 硬件中断 A 处理过程中,又再次发生了中断 B
此情景与情景 b 类似,但处理的嵌套中断类型不同,这里分析两个不同中断类型的执行顺序。
- 第一个硬件中断 A 进入处理,
preempt_count++
表明进入中断上下文。
- 在上半部处理中,另一硬件中断 B 发生,再次触发进入硬件中断上下文,
preempt_count++
累加。
- 内核优先完成硬件中断的处理:B 的上半部完成后退出上下文,同时
preempt_count--
更新,④步发现preempt_count
等于1,所以直接结束当前第2次中断的处理;。
- 第2次中断发生后,打断了第一次中断A的第⑦步处理。当第2次中断B处理完毕,CPU会继续去执行第⑦步。在第⑦步里,它会去执行中断A的下半部,也会去执行中断B的下半部。所以,多个中断的下半部,是汇集在一起处理的。
五、软件中断详解
5.1 主要数据结构
Linux软件中断系统的主要数据结构:
这里
NR_SOFTIRQS
在Linux 4.9.88中定义了10种不同类型的软中断:核心数据结构分析:
5.2 软件中断执行流程
在Linux 4.9.88中,软中断的核心代码位于
kernel/softirq.c
。软中断执行的主要入口是__do_softirq()
函数:5.3 多种下半部机制的并存
Linux提供多种下半部机制,每种适用于不同场景:
- 软中断(Softirq):
- 最底层的机制
- 静态分配、高性能
- 适合对延迟要求严格的场景
- Tasklet:
- 基于软中断
- 动态创建,易于使用
- 同一类型Tasklet串行执行
- 工作队列(Workqueue):
- 在进程上下文中执行
- 可以睡眠/阻塞
- 适合需要阻塞操作的场景
- 线程化IRQ:
- 将中断处理放入内核线程
- 简化驱动开发
- 适合复杂处理且不需要极低延迟的场景
六、Tasklet机制
6.1 简介
Tasklet 是 Linux 内核中一种基于软中断的延迟执行机制,是中断"下半部"(Bottom Half)处理的重要实现方式之一。它提供了一种在中断上下文之外执行非紧急任务的轻量级方法,平衡了系统的实时响应能力和处理效率。
Tasklet 概念
Tasklet 是一种特殊的软中断处理机制,具有以下特点:
- 轻量级:比工作队列(workqueue)更轻量,但功能不如工作队列灵活
- 动态创建:可以在运行时动态创建和调度
- 执行上下文:在软中断上下文中执行,不可睡眠
- 串行执行:同一类型的 tasklet 保证串行执行,不会并行
应用领域
Tasklet 在 Linux 内核中广泛应用于:
- 网络驱动的数据包处理
- 存储设备的 I/O 完成处理
- 输入设备的事件处理
- 定时器过期处理
- 设备驱动中需要延迟执行但不需要睡眠的任务
6.2 实现细节
Tasklet 数据结构
在 Linux 4.9.88 中,tasklet 的核心数据结构定义在
include/linux/interrupt.h
中:Tasklet 与软中断的关系
Tasklet 基于软中断(softirq)实现,使用两种软中断:
HI_SOFTIRQ
和 TASKLET_SOFTIRQ
,分别用于高优先级和普通优先级的 tasklet。Tasklet 执行流程
Tasklet 调度函数
以下是 Tasklet 主要调度函数的源码分析:
高优先级 tasklet 的调度函数:
Tasklet 处理函数
对应的 tasklet 处理函数在软中断处理中被调用:
Per-CPU 变量
Tasklet 系统使用 Per-CPU 变量来存储 tasklet 链表,避免多核系统上的锁竞争:
Tasklet 初始化与控制函数
Tasklet 设计优势
- 动态创建:与软中断不同,tasklet 可以动态创建和销毁
- 串行执行:同一 tasklet 不会在多个 CPU 上并行执行,简化同步
- 性能优化:使用 Per-CPU 变量避免了多 CPU 系统上的缓存抖动
- 优先级区分:提供高优先级和普通优先级两种 tasklet
Tasklet vs. 其他延迟执行机制
比较:
- Tasklet vs. 软中断:tasklet 更易于使用,软中断性能更高但数量固定
- Tasklet vs. 工作队列:tasklet 不能睡眠,工作队列可以但开销更大
- Tasklet vs. 线程化IRQ:tasklet 适用于通用延迟处理,线程化IRQ 专门用于中断处理
6.3 源码示例与应用
基本 Tasklet 使用方法
高优先级 Tasklet 示例
Tasklet 控制示例
网络驱动中的 Tasklet 应用
Linux 网络子系统广泛使用 tasklet 处理网络数据包:
七、工作队列(Workqueue
7.1 简介
工作队列(Workqueue)是Linux内核中一种重要的延迟执行机制,它允许内核将耗时操作推迟到进程上下文中执行。工作队列通过创建内核线程(称为worker线程)来执行被延迟的工作,保证这些操作在可安全睡眠的上下文中进行。
工作队列在Linux内核中有广泛应用,特别适合:
- 需要在中断处理程序之外执行的耗时操作
- 可能需要睡眠的操作
- IO密集型任务
- 需要访问用户空间的操作
Linux内核从2.6版本开始引入工作队列,在Linux 4.9.88中,工作队列系统已经演化为一个复杂而高效的子系统。
7.2 实现细节
7.2.1 核心数据结构
7.2.1.1 工作项 (work_struct)
这是工作队列系统的基本单位,定义在
include/linux/workqueue.h
中:data
:包含工作项的标志位和指针信息
entry
:用于在队列中链接工作项
func
:工作项的回调函数,当工作项被执行时会调用
7.2.1.2 工作队列 (workqueue_struct)
工作队列代表一组工作线程,定义在
kernel/workqueue.c
中:pwqs
:此工作队列的所有pool workqueue列表
name
:工作队列的名称
flags
:控制工作队列行为的标志位
cpu_pwqs
:每CPU的pool workqueue数组
7.2.1.3 工作者线程池 (worker_pool)
管理工作者线程的结构,定义在
kernel/workqueue_internal.h
中:worklist
:等待执行的工作项列表
workers
:此线程池中所有工作线程的列表
idle_list
:空闲工作线程列表
nr_workers
:此池中的工作线程总数
nr_idle
:当前空闲的工作线程数量
7.2.2 函数调用栈
让我们详细分析工作队列调用栈的关键函数:
7.2.2.1 初始化工作项
在使用工作队列前,需要初始化工作项:
此宏初始化工作项的列表入口,设置回调函数,并标记工作项为等待状态。
7.2.2.2 提交工作项
有多种方式可以提交工作项到工作队列:
queue_work()
将工作项提交到默认CPU,而queue_work_on()
允许指定CPU。核心的
__queue_work()
函数负责将工作项插入到适当的工作队列:7.2.2.3 工作线程执行循环
工作线程的主循环由
worker_thread()
函数实现:7.2.2.4 执行工作项
process_one_work()
是实际执行工作项的函数:7.2.3 工作队列的状态管理
工作项可能处于以下几种状态:
- 初始化:已创建但尚未提交到队列
- 排队:已提交到工作队列,等待处理
- 运行中:正在被工作线程执行
- 完成:工作项已执行完成
- 取消:工作项被取消,不会执行
Linux内核提供了丰富的API来管理工作项的状态:
cancel_work()
:取消尚未执行的工作项
cancel_work_sync()
:取消工作项并等待正在执行的完成
flush_work()
:等待特定工作项完成
flush_workqueue()
:等待工作队列中所有工作项完成
7.3代码示例:使用工作队列
7.3.1 基本用法
7.3.2 延迟执行工作
7.3.3 创建自定义工作队列
八、线程化中断(Threaded IRQ)
线程化中断是Linux内核的一个强大功能,允许将中断处理程序放在内核线程中执行: