一、中断
1. 概念
中断:CPU 运行程序时,突发事件到来,CPU 暂停当前程序,跳转处理中断事件,处理完毕后返回原程序继续执行。
- 分类:内部中断(CPU 内部产生)、外部中断(来自 CPU 外部外设)
- 作用:处理异步事件、随机事件、非顺序逻辑场景
- 查看系统中断信息命令:
cat /proc/interrupts
2. 中断相关函数接口
| 函数名 | 功能 |
|---|---|
| request_irq | 注册中断处理程序,绑定中断号与中断服务函数 |
| free_irq | 释放已经注册的中断资源 |
| disable_irq | 禁止指定中断 |
| disable_irq_nosync | 禁止中断,立即返回,不等待当前中断处理结束 |
| enable_irq | 使能指定中断 |
| local_irq_save | 禁用当前 CPU 所有中断,保存中断状态,适用于 SMP 多核 CPU |
| local_irq_disable | 禁止当前 CPU 全部中断,适用于多核 |
| local_irq_restore | 恢复之前保存的中断状态,适用于多核 |
| devm_request_irq | 资源自动管理的中断注册函数,设备卸载自动释放中断 |
3. 开发操作步骤
① 设备树 DTS 适配
puutkey {
#address-cells = <1>;
#size-cells = <1>;
compatible = "puut-key";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_RISING>;
};
interrupt-parent:指定中断父控制器。GPIO 引脚产生外部中断,先提交给 GPIO 控制器,GPIO 控制器再连接 GIC 通用中断控制器。interrupts:描述引脚编号、触发方式IRQ_TYPE_EDGE_RISING:上升沿触发IRQ_TYPE_EDGE_FALLING:下降沿触发IRQ_TYPE_EDGE_BOTH:双边沿触发
gpio 控制器节点示例:
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller; /* GPIO可以作为其他外设的中断父控制器 */
#interrupt-cells = <2>;
};
② 驱动开发步骤
-
通过
of_irq_get、gpio_to_irq从设备树解析获取中断号,GPIO 切换至中断输入模式 -
调用
request_irq申请中断,绑定中断处理函数 -
编写中断处理函数,注意返回值:
enum irqreturn {
IRQ_NONE, /* 不是本驱动处理的中断,转交其他驱动 /
IRQ_HANDLED, / 中断正常处理完成,内核不再传递 /
IRQ_WAKE_THREAD / 唤醒threaded irq线程处理 */
}; -
编写上层应用程序测试
中断驱动完整回顾步骤
- 设备树节点添加
interrupt-parent、interrupts属性 - 添加 pinctrl 引脚配置
- 设备节点引用 pinctrl、gpio、中断配置,status 设为 okay
misc_register注册杂项设备- of 函数解析获取设备树节点
- of 接口获取 gpio 编号
- 向内核申请 GPIO 资源
- of_irq_get /gpio_to_irq 获取中断号
request_irq申请注册中断
二、Linux 中断子系统
中断处理讲究快进快出,内核将中断拆分为顶半部(Top Half) 、底半部(Bottom Half),平衡实时性与工作量。
- 顶半部(上半部):硬件中断上下文,紧急硬件操作;不可休眠、不可被新中断打断,执行时间极短。仅做寄存器读写、唤醒底半部。
- 底半部(下半部):处理耗时、非紧急任务,可以被新中断打断。分为软中断、tasklet、workqueue 工作队列三类。

| 机制 | 运行环境 | 能否休眠 | 调度时机 | 并行性 | 适用场景 |
|---|---|---|---|---|---|
| 顶半部 | 中断上下文 | ❌ 不能 | 立即执行 | 独占 CPU | 紧急硬件操作 |
| 软中断 softirq | 中断上下文 | ❌ 不能 | 顶半部返回前 | 多核可并行 | 网络、块设备、定时器,内核底层使用 |
| tasklet | 中断上下文 | ❌ 不能 | 顶半部返回前 | 同类型 tasklet 不能并行 | 驱动最常用,轻量底半部,按键、GPIO 中断 |
| workqueue 工作队列 | 进程上下文 | ✅ 允许 | 内核线程调度 | 完全并行 | 耗时操作、需要休眠的任务 |
1. 软中断 softirq
内核底层机制,属于底半部,运行在中断上下文;资源有限,驱动开发几乎不用自定义软中断。
2.tasklet(驱动最常用,软中断封装)
- 特点:简单安全,不能休眠;同一个 tasklet 不会多核并行执行。
- 使用:
DECLARE_TASKLET定义 tasklet;顶半部调用tasklet_schedule触发调度。 - 适用:按键处理、GPIO 中断、快速数据读取,不需要休眠的场景。
3.workqueue 工作队列
唯一允许休眠的底半部,运行在内核线程上下文,可以调用 msleep、mutex、kmalloc、copy_to_user。
- 使用:
INIT_WORK初始化工作;schedule_work触发调度。 - 适用:延时、内存分配、I2C/SPI 读写等需要休眠的耗时操作。
上下文区分
- 硬件中断上下文(顶半部):收到硬件中断立即执行,不可休眠
- 软中断 /tasklet 上下文:属于中断上下文,不可休眠
- 进程上下文(workqueue):内核线程,可以休眠
三、内核同步互斥机制(并发与竞态)
并发访问共享资源会产生竞态,需要互斥保护,常用 5 种:原子操作、自旋锁、信号量、互斥体 (mutex)、完成量 completion
1. 原子操作
保证对整型变量修改排他执行,无锁,适用于简单变量计数。
| 函数 | 功能 |
|---|---|
| atomic_set | 设置原子变量值 |
| atomic_read | 读取原子变量 |
| atomic_add/atomic_sub | 原子变量增减 |
| atomic_inc/atomic_dec | 自增、自减 |
| 位操作 set_bit /clear_bit/test_and_set_bit | 位原子操作 |
2. 自旋锁 spinlock
临界区持有锁期间 CPU 循环忙等,不进入休眠。中断上下文推荐 ,禁止在持有自旋锁时休眠。 核心 API:spin_lock_init、spin_lock、spin_unlock、spin_lock_irqsave(关中断 + 获取锁)
3. 信号量 semaphore
可计数信号量,获取不到锁时进程休眠,适合资源数量大于 1 的场景。API:sema_init、down、up。
4. 互斥体 mutex
信号量特例,计数固定为 1,同一时间只能一个进程持有;只能进程上下文使用,不可用于中断上下文。API:mutex_init、mutex_lock、mutex_unlock。
5. 完成量 completion
专门用于一个线程等待另外一个线程完成事件 ,常用于中断通知任务。API:init_completion、wait_for_completion、complete。
各类锁对比简表
| 类型 | 等待方式 | 能否休眠 | 适用上下文 |
|---|---|---|---|
| 自旋锁 spinlock | 忙等待 | ❌ 不可休眠 | 中断上下文、进程上下文 |
| 信号量 semaphore | 睡眠等待 | ✅ 可休眠 | 进程上下文 |
| 互斥体 mutex | 睡眠等待 | ✅ 可休眠 | 进程上下文,独占访问 |
| 完成量 completion | 事件等待 | ✅ 可休眠 | 进程上下文,事件通知场景 |
总结
- 原子操作:简单变量保护,无锁开销。
- 自旋锁:短临界区,中断上下文使用,不可休眠。
- 信号量:资源计数,进程上下文,可休眠。
- mutex 互斥体:独占资源,进程上下文,不可在中断使用。
- 完成量:专门用于任务等待事件完成,中断通知线程场景。