学 Linux 驱动时,中断机制是一个绕不开的坎。很多初学者会卡在几个问题上:外设为什么能“打断”CPU?中断号从哪里来?中断处理函数什么时候执行?为什么中断里不能随便睡眠?上半部、下半部、线程化中断又是什么?
这篇文章不从复杂源码开始,而是先建立一条清晰主线:**外设发出中断信号,中断控制器汇总并送给 CPU,CPU 进入内核中断入口,内核找到对应驱动的处理函数,驱动快速处理关键现场,再把耗时工作延后。**
理解这条链路,后面再看 request_irq()、/proc/interrupts、设备树里的 interrupts 属性,思路就会顺很多。

先从一个最朴素的问题开始:假设按键被按下,CPU 怎么知道?
最简单的方法是轮询。CPU 每隔一段时间去读一次 GPIO 状态,看看按键有没有变化。这个方法容易理解,但有明显缺点:
中断的思路相反:CPU 平时正常执行任务,外设有事件时主动发信号通知 CPU。CPU 暂停当前执行流,跳到内核中断处理路径,处理完再回到原来的位置继续运行。
这就像你不用一直盯着门口看快递到了没有,而是让门铃响了再去开门。
在 Linux 系统里,一次典型中断路径可以拆成几步:
这里有一个关键点:**中断不是某个驱动自己凭空触发的,而是硬件事件、控制器、CPU 和内核共同完成的一套机制。**

在嵌入式 SoC 里,外设通常不会直接把中断送到 Linux 驱动。它们会先连接到中断控制器,例如 ARM 平台常见的 GIC。GPIO 控制器、I2C 控制器、SPI 控制器、网卡、USB 控制器、定时器等外设,都可能有自己的中断输出。
中断控制器负责几件事:
驱动里看到的 IRQ number,并不一定等于硬件手册里的原始中断线编号。Linux 会把硬件中断号映射成内核使用的虚拟 IRQ 编号,驱动通常只需要使用内核分配好的 IRQ。
在设备树平台里,中断信息常常长这样:
●●●// 示例:设备通过设备树描述自己的中断资源
example_device: sensor@40 {
compatible = "demo,irq-sensor";
reg = <0x40>;
interrupt-parent = <&gpio1>;
interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
};这段描述表达的是:设备的中断接在 gpio1 控制器的第 12 路,触发方式是下降沿。驱动并不直接硬编码这个数字,而是从设备树或平台资源中获取 IRQ。
触发方式也很重要:
如果触发方式配置错了,可能出现两类问题:
这也是调试中断问题时必须检查硬件原理图、设备树和芯片手册的原因。

中断来了以后,CPU 会进入内核中断上下文。这个上下文很特殊:它不是普通进程上下文,不能像普通线程那样随便睡眠、等待锁、做长时间阻塞操作。
因此 Linux 驱动设计里有一个基本原则:**中断处理函数要尽量短。**
传统说法里,中断处理分为上半部和下半部:
上半部通常做这些事:
它不适合做这些事:
一个简化的中断处理函数大概是这样:
●●●// 示例:表达中断上半部的典型处理逻辑
static irqreturn_t demo_irq_handler(int irq, void *dev_id)
{
struct demo_dev *ddev = dev_id;
u32 status;
// 读取状态寄存器,确认是否是本设备产生的中断
status = readl(ddev->base + DEMO_INT_STATUS);
if (!status)
return IRQ_NONE;
// 清除中断,避免同一个中断反复进入
writel(status, ddev->base + DEMO_INT_CLEAR);
// 保存轻量状态,唤醒后续处理
ddev->last_status = status;
wake_up_interruptible(&ddev->waitq);
return IRQ_HANDLED;
}这段代码的重点不是寄存器名字,而是处理策略:先确认来源,再清中断,再保存必要状态,最后唤醒后续逻辑。复杂工作不要堆在这里。
Linux 里常见的下半部机制包括:
对于初学驱动的人,重点先记住一句话:**硬中断里只做必须马上做的事,能晚点做的事放到下半部。**

驱动想处理某个中断,需要先向内核注册处理函数。常见接口是 request_irq() 或 devm_request_irq()。
devm_request_irq() 更适合现代驱动,因为它和设备生命周期绑定,设备卸载时资源会自动释放,减少忘记 free_irq() 的风险。
简化示例如下:
●●●// 示例:驱动 probe 阶段申请中断
staticint demo_probe(struct platform_device *pdev)
{
struct demo_dev *ddev;
int irq;
int ret;
// 从设备树或平台资源中获取 IRQ 编号
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ddev = devm_kzalloc(&pdev->dev, sizeof(*ddev), GFP_KERNEL);
if (!ddev)
return -ENOMEM;
init_waitqueue_head(&ddev->waitq);
// 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq, demo_irq_handler,
IRQF_TRIGGER_FALLING,
"demo-irq-device", ddev);
if (ret)
return ret;
platform_set_drvdata(pdev, ddev);
return0;
}这里有几个参数值得理解:
irqdemo_irq_handlerIRQF_TRIGGER_FALLING"demo-irq-device"/proc/interrupts 里的名字。ddev如果多个设备共享一根中断线,还可能使用 IRQF_SHARED。共享中断时,中断处理函数必须检查硬件状态,确认中断是否真的来自本设备。如果不是,就返回 IRQ_NONE。
中断问题不要一上来就钻源码。更高效的顺序是先确认硬件信号、内核计数和驱动日志。
先看 /proc/interrupts:
●●●# 查看系统中断计数和中断名称
cat /proc/interrupts重点看几件事:
再看内核日志:
●●●# 观察驱动 probe、中断注册、异常日志
dmesg -w如果驱动注册失败,常见原因包括:
interrupt-parent再结合硬件确认:
中断风暴是初学者很容易遇到的问题。它通常表现为 CPU 占用升高,/proc/interrupts 计数疯狂增长,系统响应变慢。常见原因是中断状态没有清除、电平触发信号一直有效,或者触发方式配置错。
可以临时用下面的方式观察中断计数变化:
●●●# 每秒刷新一次中断计数
watch -n 1 cat /proc/interrupts如果某一行计数在没有真实事件时也持续快速增长,就要优先检查清中断逻辑和触发方式。
学习 Linux 中断机制,不建议一开始就陷入架构相关汇编入口。更有效的方法是先建立四层模型:
以后遇到中断不触发,可以按这个顺序排查:
1. 先确认硬件信号有没有变化。 2. 再确认设备树或平台资源是否描述正确。 3. 再看 /proc/interrupts 计数是否增长。 4. 再看中断处理函数是否进入。 5. 最后检查是否清中断、是否唤醒等待队列、是否调度下半部。
这样排查比盲目加日志更稳定。
Linux 中断机制看起来复杂,本质上是在解决一个问题:外设发生事件时,如何让 CPU 高效、及时、可控地响应。
它的主线并不难:
对驱动开发来说,真正重要的不是背概念,而是形成工程判断:中断处理要短,触发方式要对,状态要清干净,调试时要看 /proc/interrupts、dmesg 和硬件信号。
掌握这条链路之后,再去读具体平台的 GIC、GPIO 中断、request_irq()、线程化中断源码,理解难度会明显下降。
【往期推荐】