Linux驱动——中断子系统框架
一、中断
轮询是 CPU 主动问:
1 | |
中断是设备主动通知 CPU:
1 | |
中断的核心价值在于不需要 CPU 一直轮询,并且在此期间 CPU 可以做别的事情。中断适合按键、网卡、摄像头、DMA、VPU、NPU 这类异步事件。
但中断上下文有严格限制:
1 | |
因为中断上下文没有对应的进程,调度器无法让它睡眠等待。
中断不是“用户态调用驱动”,而是硬件主动打断 CPU:
1 | |
这些事件都可能通过中断通知 CPU。Linux 不能把每个中断都交给不同驱动随意处理,而是有一套统一框架:
1 | |
二、中断子系统框架
从硬件到驱动,一次中断大致经过:
1 | |
驱动一般不直接操作 GIC 或 irq_desc,而是用标准 API:
1 | |
三、上半部与下半部
一个真实中断往往包含两类事情:
1 | |
所以内核把中断处理拆成上半部和下半部:
| 部分 | 上下文 | 做什么 |
|---|---|---|
| 上半部 hardirq | 中断上下文 | 最快速度确认事件、记数、清中断、调度下半部 |
| 下半部 | softirq / 进程上下文 | 耗时处理、拷贝数据、投递队列、唤醒用户态 |
下半部常见机制:
| 机制 | 上下文 | 能否睡眠 | 适合 |
|---|---|---|---|
| softirq | 中断相关上下文 | 不能 | 网络等高吞吐场景 |
| tasklet | softirq | 不能 | 旧的轻量延后处理 |
| workqueue | 进程上下文 | 能 | 需要睡眠、加锁、耗时的处理 |
| threaded IRQ | 内核线程 | 能 | 处理逻辑天然属于该中断 |
可以按下面的判断表选机制:
| 需求 | 推荐机制 |
|---|---|
| 只是为了不丢事件,做最少处理 | hardirq 上半部 |
| 极短、不能睡眠、对延迟敏感 | tasklet |
| 需要睡眠、拿 mutex、做耗时事情 | workqueue |
| 处理逻辑天然属于这个中断,希望内核帮你管理线程 | threaded IRQ |
| 只是想等一个事件发生 | waitqueue / completion |
| 用户态同时等多个设备 | poll / epoll + waitqueue |
四、从设备树到中断号
设备树里常见的中断描述有两类。
3.1 直接写 interrupts
1 | |
驱动里可以:
1 | |
适合 SoC 内部外设,比如 DMA、VPU、I2C、SPI、UART。
3.2 用 GPIO 作为中断源
1 | |
驱动里可以:
1 | |
适合按键、红外、传感器、编码器、外部触发信号。
五、中断处理函数
中断处理函数的原型如下:
1 | |
参数:
- 第一个参数是要中断处理函数要相应的中断号
- 第二个参数是一个通用指针,需要与
request_irq函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构
返回值:
-
中断处理函数的返回值为
irqreturn_t类型,定义如下:1
2
3
4
5
6enum irqreturn {
IRQ_NONE = (0 << 0), // 该中断对应的设备并不是在注册处理函数期间指定的产生源时
IRQ_HANDLED = (1 << 0), // 中断处理程序被正确调用且确实是它对应的设备产生了中断
IRQ_WAKE_THREAD = (1 << 1), // 需要唤醒 threaded handler
};
typedef enum irqreturn irqreturn_t;一般中断服务函数返回值使用这种形式:
return IRQ_RETVAL(IRQ_HANDLED)
用户需要自己来实现中断处理函数,例如:
1 | |
注意,中断处理函数要保证快进快出,可以做一些比如:记录计数、唤醒等待者、调度 workqueue 等工作;不要做 sleep、打印日志等耗时任务
六、注册中断
典型写法:
1 | |
参数:
| 参数 | 含义 |
|---|---|
dev |
设备指针,用于 devm 自动释放 |
irq |
中断号 |
handler |
中断处理函数 |
flags |
触发方式、共享标志等 |
name |
/proc/interrupts 中显示的名字 |
priv |
传给 handler 的 data |
常见触发方式:
| flags | 含义 |
|---|---|
IRQF_TRIGGER_RISING |
上升沿触发 |
IRQF_TRIGGER_FALLING |
下降沿触发 |
IRQF_TRIGGER_HIGH |
高电平触发 |
IRQF_TRIGGER_LOW |
低电平触发 |
IRQF_TRIGGER_RISING + IRQF_TRIGGER_FALLING |
双边沿触发 |
IRQF_SHARED |
共享中断 |
七、实例
irq_lab.c:
1 | |
irq_lab_testdev.c:
1 | |
Makefile:
1 | |
运行查看:
1 | |
说明:
1 | |
再看:
1 | |
| 字段 | 含义 |
|---|---|
144 |
Linux IRQ number |
多个 0 |
每个 CPU 上此中断计数 |
rockchip_gpio_irq |
GPIO 中断控制器 |
1 |
GPIO line offset |
Edge |
边沿触发 |
irq_lab |
request_irq 注册的名字 |
Linux驱动——中断子系统框架
http://example.com/2026/08/06/Linux驱动——中断子系统框架/