昨天调一块新板子按键中断死活不触发。用万用表量了信号明明有下降沿可内核就是没反应。查了半天发现是中断申请时没指定正确的触发方式。这种问题在驱动开发里太常见了今天咱们就聊聊中断线申请和释放的那些门道。从实际案例说起先看一段有问题的代码这是我早期写驱动时犯的错staticirqreturn_tmy_interrupt_handler(intirq,void*dev_id){// 这里直接操作硬件寄存器regreadl(base_addrREG_OFFSET);writel(reg|0x1,base_addrREG_OFFSET);// 忘了判断中断是否真的是我们的returnIRQ_HANDLED;}staticintprobe(structplatform_device*pdev){// 这样申请中断很危险retrequest_irq(irq_num,my_interrupt_handler,0,my_dev,NULL);}这段代码至少有三个问题中断处理函数里没做任何共享判断、flags参数传0不合适、dev_id传NULL在共享中断下会出问题。当年调试时系统时不时就卡死最后发现是中断风暴导致的。正确的中断申请姿势现在看看应该怎么写。中断申请的核心是request_irq()或者它的新版本request_threaded_irq()。我更喜欢用后者特别是需要长时间处理的中断。staticirqreturn_tmy_handler(intirq,void*dev_id){structmy_device*devdev_id;u32 status;// 先读中断状态寄存器statusreadl(dev-baseINT_STATUS_REG);// 一定要检查是不是我们的中断if(!(statusDEV_INT_MASK)){returnIRQ_NONE;// 不是我们的中断赶紧返回}// 清除中断标志注意顺序先处理再清除有时候更安全writel(status,dev-baseINT_STATUS_REG);// 如果只是简单处理可以直接在这里完成// 如果需要大量计算或可能休眠应该用线程化中断的底半部returnIRQ_HANDLED;}staticirqreturn_tmy_thread_fn(intirq,void*dev_id){// 这里可以放心调用可能休眠的函数// 比如等待队列、mutex、msleep等process_data(dev_id);returnIRQ_HANDLED;}staticintprobe(structplatform_device*pdev){structmy_device*dev;intirq_num;// 获取中断号现在常用平台设备的方式irq_numplatform_get_irq(pdev,0);if(irq_num0){returnirq_num;// 这里直接返回错误码就行}// 推荐用线程化中断特别是处理复杂的任务retrequest_threaded_irq(irq_num,my_handler,// 顶半部快速处理my_thread_fn,// 底半部线程中运行IRQF_TRIGGER_FALLING|IRQF_ONESHOT,my_device,dev);if(ret){dev_err(pdev-dev,申请中断失败: %d\n,ret);returnret;}// 保存中断号释放时要用dev-irqirq_num;}这里有几个关键点IRQF_ONESHOT用于线程化中断确保中断线在底半部完成前不会重新触发。IRQF_TRIGGER_FALLING指定下降沿触发这个一定要和硬件实际信号匹配。中断标志位的选择关于flags参数我总结了个表格放在脑子里边沿触发用IRQF_TRIGGER_RISING或IRQF_TRIGGER_FALLING电平触发用IRQF_TRIGGER_HIGH或IRQF_TRIGGER_LOW共享中断必须加IRQF_SHARED而且dev_id必须唯一线程化中断通常配合IRQF_ONESHOT使用特别注意电平触发中断在handler里必须清除中断源否则会一直触发。有一次我调试一个高电平触发的中断忘了在handler里拉低电平结果系统直接卡死在中断里。释放中断的注意事项释放中断看起来简单但细节不对也会出问题staticintremove(structplatform_device*pdev){structmy_device*devplatform_get_drvdata(pdev);// 释放中断前先禁用中断控制器上的该中断线disable_irq(dev-irq);// 等待可能正在执行的中断处理完成free_irq(dev-irq,dev);// 注意free_irq之后不要再操作dev了// 因为中断可能还在执行dev可能已经被释放}有个坑我踩过在模块卸载时直接调用free_irq()没先调disable_irq()。结果中断刚好在释放过程中触发访问了已经释放的设备结构体直接oops。中断上下文那些限制在中断处理函数顶半部里你不能做这些事调用可能休眠的函数比如mutex_lock、kmalloc带GFP_KERNEL标志访问用户空间内存copy_from_user之类执行耗时太长的操作我见过有人在中断handler里调用printk打印大量调试信息结果系统响应变慢中断丢失。正确的做法是用dev_dbg()配合动态调试或者用per_cpu变量记录信息在proc文件中查看。调试中断问题的方法当中断不触发时我的排查顺序先查/proc/interrupts看中断计数有没有增加检查硬件信号用示波器量实际波形确认中断触发方式设置对了没检查中断控制器配置有些SoC需要额外使能中断路由中断触发太频繁时检查是不是没正确清除中断标志电平触发中断是不是电平一直保持共享中断是不是被别的设备误触发个人经验建议中断处理要快进快出这是铁律。但“快”是相对的现在CPU这么快在handler里做几百条指令的简单处理完全没问题。关键是不能阻塞。线程化中断是个好东西特别是对于USB、SD卡这类复杂外设。但简单的中断比如GPIO按键直接用传统的request_irq就行没必要过度设计。共享中断现在用得少了但写驱动时最好还是考虑一下。检查中断状态寄存器、返回IRQ_NONE这两个习惯能避免很多奇怪的问题。最后记住申请中断时用的dev_id一定要在驱动整个生命周期都有效。我习惯直接用设备结构体指针这样在handler里能直接拿到所有设备上下文。调试中断问题最痛苦的是现象不稳定时好时坏。这种多半是竞争条件或者中断使能/禁用的顺序问题。加个disable_irq()和enable_irq()的调试打印经常能发现意外禁用中断的地方。中断驱动就像外科手术精准是关键。多一毫秒少一毫秒效果天差地别。