在Linux内核中申请中断是设备驱动程序开发的核心任务之一,它允许硬件设备在需要处理时主动通知CPU,以下是详细的技术流程和注意事项:
中断申请的核心函数
Linux内核通过 request_irq()
或 request_threaded_irq()
函数申请中断:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
- 参数解析:
irq
:中断号(如硬件IRQ编号),可通过platform_get_irq()
或pci_irq_vector()
获取。handler
:中断处理函数指针,类型为irqreturn_t (*handler)(int, void*)
。flags
:控制中断行为的标志位,常用值:IRQF_SHARED
:允许多个设备共享同一中断线。IRQF_TRIGGER_RISING
:上升沿触发。IRQF_ONESHOT
:线程化中断中确保中断线保持禁用直到处理完成。
name
:在/proc/interrupts
中显示的设备标识。dev
:传递给处理函数的私有数据指针(必须唯一,用于共享中断)。
中断处理函数编写规范
处理函数需遵循固定原型:
irqreturn_t my_handler(int irq, void *dev_id) { // 1. 快速处理:读取状态寄存器、清除中断标志 // 2. 若需耗时操作,触发下半部(如tasklet/workqueue) return IRQ_HANDLED; // 或 IRQ_NONE(非本设备中断) }
关键要求:
- 非阻塞:禁止使用睡眠函数(如
mutex_lock()
、kmalloc(GFP_KERNEL)
)。 - 短时执行:理想执行时间 < 100微秒,避免影响系统响应。
线程化中断(推荐方式)
通过 request_threaded_irq()
将中断分为顶半部(快速响应)和底半部(线程中执行):
int request_threaded_irq(unsigned int irq, irq_handler_t handler, // 顶半部 irq_handler_t thread_fn, // 底半部(线程内运行) unsigned long flags, const char *name, void *dev);
- 优势:
- 底半部可睡眠、执行复杂逻辑。
- 避免中断屏蔽时间过长。
- 示例:
request_threaded_irq(irq, NULL, my_thread_fn, IRQF_ONESHOT, "my_device", dev);
完整代码示例
以PCI设备驱动为例:
static irqreturn_t my_interrupt(int irq, void *dev_id) { struct my_device *dev = dev_id; if (!check_hw_irq(dev)) return IRQ_NONE; // 非本设备中断 return IRQ_WAKE_THREAD; // 唤醒底半部线程 } static irqreturn_t my_thread_fn(int irq, void *dev_id) { struct my_device *dev = dev_id; process_data(dev); // 执行耗时操作 return IRQ_HANDLED; } static int probe(struct pci_dev *pdev, const struct pci_device_id *id) { int irq = pci_irq_vector(pdev, 0); ret = request_threaded_irq(irq, my_interrupt, my_thread_fn, IRQF_SHARED | IRQF_ONESHOT, "my_pci_card", pdev); if (ret) dev_err(&pdev->dev, "Failed to request IRQ %d\n", irq); return ret; } static void remove(struct pci_dev *pdev) { free_irq(pci_irq_vector(pdev, 0), pdev); }
关键注意事项
- 中断号获取:
- 旧方法:直接使用硬件IRQ(如
5
),已废弃。 - 现代方法:通过设备树(DT)或ACPI动态获取(如
platform_get_irq()
)。
- 旧方法:直接使用硬件IRQ(如
- 共享中断:
- 必须设置
IRQF_SHARED
。 dev_id
必须是设备唯一标识(通常用设备结构体指针)。
- 必须设置
- 资源释放:
- 在驱动卸载或错误路径调用
free_irq(irq, dev_id)
。 - 否则导致内核崩溃或中断泄漏。
- 在驱动卸载或错误路径调用
- 中断统计:
- 查看
/proc/interrupts
确认中断是否成功注册及触发次数。
- 查看
最佳实践
- 优先选择线程化中断:避免实时性任务被长时间中断阻塞。
- 避免共享中断:除非硬件设计强制要求(如PCI设备)。
- 中断亲和性:通过
irq_set_affinity()
绑定中断到特定CPU核心,优化多核性能。 - 锁的使用:在中断上下文中,优先使用自旋锁(
spin_lock_irqsave()
)。
常见错误
- 未返回
IRQ_WAKE_THREAD
:线程化中断中顶半部必须返回此值以唤醒底半部。 - 遗漏
free_irq
:引发 “Trying to free already-free IRQ” 内核错误。 - 阻塞操作:在非线程化处理函数中调用可能睡眠的API。
正确申请中断是驱动稳定性的基石,开发者需深入理解硬件特性(如触发方式)和内核约束(如执行上下文),对于新开发,线程化中断是首选方案,它平衡了实时性与安全性,实际开发中务必参考最新内核文档(如 Linux Kernel Interrupt Handling),并利用 ftrace
或 perf
工具监控中断延迟。
引用说明: 基于 Linux 内核 6.x 版本文档、《Linux Device Drivers, 3rd Edition》(O’Reilly)及内核源码(
include/linux/interrupt.h
),实践代码遵循 GPL-2.0 许可,技术细节以 kernel.org 官方文档为准。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/6584.html