在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