驱动开发硬核特训 · Day 17:深入掌握中断机制与驱动开发中的应用实战
🎥 视频教程请关注 B 站:“嵌入式 Jerry”
一、前言
在嵌入式驱动开发中,“中断”几乎无处不在。无论是 GPIO 按键、串口通信、网络设备,还是 SoC 上的各种控制器,中断都扮演着核心触发机制的角色。对中断机制掌握程度的高低,直接影响到你驱动开发水平的深度。
本篇将从以下结构展开:
- 中断的基本概念与分类
- Linux 内核中的中断框架
- request_irq() 的实战用法与原理解析
- 中断处理函数与上下半部机制
- 中断在 DTS 中的描述与匹配方式
- 实战:为一个 GPIO 按键注册中断
- 调试技巧与常见误区
二、中断基础概念
2.1 什么是中断?
中断(Interrupt)是指处理器在运行过程中,外部设备或内部条件发生特定事件时,打断当前正在执行的任务,转而执行一个中断服务程序(ISR)的机制。
2.2 中断的分类
类型 | 描述 |
---|---|
外部中断 | 外设发出的信号,如 GPIO、UART、定时器等 |
内部中断 | 处理器内部事件触发,如异常、软中断等 |
边沿触发 | 在电平变化的瞬间触发(如上升沿、下降沿) |
电平触发 | 在保持某一电平时持续触发(如高电平、低电平) |
三、Linux 内核中的中断机制
Linux 内核为中断提供了统一的框架,通过 IRQ(Interrupt Request)编号管理设备之间的中断。
3.1 中断号(IRQ number)
每个硬件中断都被映射为一个唯一的 IRQ 号,Linux 内核使用这个编号来注册/处理中断。
可通过如下命令查看系统中断分配:
cat /proc/interrupts
3.2 request_irq() 的框架
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev);
参数 | 含义 |
---|---|
irq | 要申请的中断号 |
handler | 中断服务函数 |
flags | 触发方式,如 IRQF_TRIGGER_FALLING 等 |
name | 中断名称,用于 /proc/interrupts 识别 |
dev | 通常为 pdev 或私有结构体指针,用于共享中断区分 |
四、中断处理函数与上下半部机制
中断处理时间越短越好,因此 Linux 提供 中断上下半部机制:
部分 | 描述 |
---|---|
上半部(top half) | 中断刚发生时的 ISR(中断服务函数),需快速返回 |
下半部(bottom half) | 通过 tasklet、workqueue 等机制延迟处理 |
示例:使用 workqueue 实现下半部
static void my_work_handler(struct work_struct *work)
{pr_info("Bottom half executed.\n");
}static DECLARE_WORK(my_work, my_work_handler);static irqreturn_t my_irq_handler(int irq, void *dev)
{schedule_work(&my_work);return IRQ_HANDLED;
}
五、设备树中断描述与解析
5.1 DTS 中的中断定义
button@0 {compatible = "gpio-keys";gpios = <&gpio3 19 GPIO_ACTIVE_LOW>;linux,code = <KEY_ENTER>;label = "Enter Button";interrupt-parent = <&gpio3>;interrupts = <19 IRQ_TYPE_EDGE_FALLING>;
};
5.2 驱动中解析中断号
irq = of_irq_get(np, 0);
ret = request_irq(irq, handler, IRQF_TRIGGER_FALLING, "button", dev);
六、实战讲解:为 GPIO 按键注册中断
以下是一个完整的按键中断驱动代码片段:
static irqreturn_t button_isr(int irq, void *dev_id)
{pr_info("Button interrupt triggered!\n");return IRQ_HANDLED;
}static int btn_probe(struct platform_device *pdev)
{int irq;struct device_node *np = pdev->dev.of_node;irq = of_irq_get(np, 0);if (irq < 0)return irq;return devm_request_irq(&pdev->dev, irq, button_isr,IRQF_TRIGGER_FALLING, "button", NULL);
}
驱动匹配表
static const struct of_device_id btn_of_match[] = {{ .compatible = "myvendor,button" },{ }
};
MODULE_DEVICE_TABLE(of, btn_of_match);
七、调试技巧与排错建议
7.1 无法响应中断?
- 检查
of_irq_get()
返回值 - 确保设备树中
interrupt-parent
正确 - 查看内核是否启用了相应的 GPIO 中断支持
- 使用
cat /proc/interrupts
观察中断是否触发计数增加
7.2 中断多次触发?
- 检查
IRQF_TRIGGER_*
设置是否与实际硬件匹配 - 排查上拉/下拉电阻或信号抖动问题
八、问答环节总结
Q1:一个驱动中能注册多个中断吗?
可以。使用 request_irq()
多次注册不同 irq。
Q2:可以使用中断做耗时操作吗?
不建议。应放入下半部如 workqueue
中。
Q3:中断共享怎么办?
设置 IRQF_SHARED
,并正确传入 dev_id
区分中断源。
Q4:中断服务函数可以被抢占吗?
在默认情况下,中断服务函数运行在中断上下文,不能被抢占,但可以被更高优先级中断打断。
九、总结提炼
关键点 | 说明 |
---|---|
中断是驱动中的基础机制 | 几乎所有外设都依赖中断响应事件 |
request_irq() 是核心接口 | 注册中断的入口函数 |
使用设备树描述中断资源 | 通过 interrupts + interrupt-parent 精确匹配 |
上下半部机制非常重要 | 把耗时操作放到 workqueue/tasklet |
实战中注意调试 | 利用 /proc/interrupts 和日志追踪触发行为 |
✅ 今日作业建议
- 在你的开发板上找一个 GPIO 按键,编写设备树 + 驱动注册中断。
- 使用 printk 或 dev_info() 输出中断触发信息,验证中断触发次数。
- 尝试通过
workqueue
延迟执行中断下半部逻辑,如点亮 LED。
🎥 视频教程请关注 B 站:“嵌入式 Jerry”