Linux驱动中断申请全解析:从request_irq到线程化中断

📅 发布时间:2026/10/5 0:18:22
Linux驱动中断申请全解析:从request_irq到线程化中断
实际动手写驱动的时候中断这部分永远是最容易出“玄学问题”的地方。明明代码看着没问题一加载模块系统就重启或者中断触发一次之后再也不进了又或者莫名其妙丢中断。这些问题十有八九都出在申请中断这个环节上。这篇就结合我自己的实操经验把 Linux 中断子系统里驱动申请中断这条线完整捋一遍从最常用的 API 到底层机制再到那些文档里不会明说的注意事项一次讲透。1. 整体设计与思路拆解为什么申请中断不是“调一个函数”那么简单1.1 中断申请在内核里的真实位置先明确一个概念驱动里调的request_irq()并不是直接跟硬件打交道而是通过内核的中断子系统Interrupt Subsystem注册一个回调。整个链路大致是这样的设备产生中断信号 - GIC通用中断控制器- CPU 异常入口 - 内核 generic_handle_irq() - irq_desc 里注册的 action 链表 - 回调到你驱动的 handler我用一句大白话总结就是申请中断的本质是把你驱动的处理函数挂到内核中断描述符irq_desc的 action 链表上。这个链表是支持多个 handler 共存的前提是大家都用IRQF_SHARED标志。后续谁触发了、该调用谁由内核在中断上下文里统一调度。很多初学者以为request_irq只是“把中断打开”这是最大的误解。真正打开中断使能的是enable_irq()和硬件寄存器操作request_irq只是注册回调并做必要的初始化。这个认知如果不纠正后面排查问题会绕很多弯路。1.2 设计意图为什么需要一套如此复杂的机制中断子系统设计得这么复杂核心原因有三个第一是共享中断。硬件上多个设备可能共用同一条 IRQ 线尤其是在 PCI/ISA 设备上非常常见内核必须支持多个驱动同时注册同一个中断号触发时依次调用所有 handler由各 handler 自己判断“是不是我的设备产生的中断”。没有共享机制这类硬件根本无法工作。第二是中断上下文的限制。中断 handler 运行在特殊上下文atomic context不能睡眠、不能调用可能睡眠的函数如kmalloc(GFP_KERNEL)、mutex_lock、copy_to_user。可很多驱动确实需要在中断到来时做大量耗时工作比如处理网络数据包、读取传感器数据。内核为此提供了「中断下半部」机制包括 softirq、tasklet、workqueue以及后来引入的线程化中断threaded IRQ。这一整套设计都是为了在“响应快”和“能干活”之间取得平衡。第三是通用性。Linux 要跑在 X86、ARM、RISC-V、MIPS 等各种架构上这些架构的中断控制器千差万别。中断子系统通过 irq_chip、irq_domain 等抽象层把硬件差异隔离掉驱动程序只需要面向 Linux 的通用中断 API 编程不需要关心具体芯片寄存器。1.3 驱动程序申请中断的三大要素实际编码时驱动工程师必须要想清楚三件事缺一不可中断号从哪来设备树里配置的interrupts属性经过解析得到的 IRQ number或者 PCI 设备用pci_alloc_irq_vectors()动态分配。注册哪个处理函数是只注册一个快速 handler还是用线程化中断配合request_threaded_irq()。用什么标志是否共享、触发方式边沿/电平、是否禁用自动使能等这些直接决定中断行为。这三要素就是整个申请中断流程的核心。很多 bug 都是因为只关注了“函数名”没关注“标志位”而产生的后面我会逐一解析。2. 核心机制与 API 细节request_irq 家族全解析2.1 最常用的请求函数清单内核提供的申请中断 API 有好几个很多新人容易搞混我先把它们列出来API参数特点典型应用场景request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)只注册硬中断 handler简单设备处理函数很快完成request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev)注册硬中断 handler 和线程化 handler处理耗时较长的场景request_any_context_irq()自动检测上下文某些可能运行在 thread 或 hardirq 环境的驱动devm_request_irq()/devm_request_threaded_irq()设备资源管理的版本推荐所有平台驱动使用自动释放irq_of_parse_and_map()/platform_get_irq()获取中断号设备树 / 平台驱动标配这里面最核心的是前两个其他都是变体或者包装。比如devm_request_irq()内部最终调用的是request_threaded_irq()只是在资源释放路径上加了自动管理。2.2 request_irq 和 request_threaded_irq 的关系我直接给结论request_irq(irq, handler, flags, name, dev)等价于request_threaded_irq(irq, handler, NULL, flags, name, dev)。当thread_fn为 NULL 时内核会使用默认的irq_default_primary_handler()作为硬中断 handler。这个默认 handler 的作用很简单如果中断没有被IRQF_ONESHOT标记就直接返回IRQ_WAKE_THREAD唤醒内核为这个中断创建的内核线程去执行你传入的thread_fn。这里有一个很多资料没讲透的点如果你希望只用 hardirq handler 而不创建内核线程那么必须用request_irq()来注册。如果你硬是调request_threaded_irq()并把thread_fn也传了内核一定会创建线程即使你的handler返回IRQ_HANDLED中断线程也不会被唤醒。还有一个常见问题是handler参数是否可以传 NULL答案是如果thread_fn不为 NULL那么handler可以为 NULL。此时内核会注册irq_default_primary_handler()中断发生直接唤醒线程。如果handler和thread_fn都为 NULL那这个申请就是错的内核会返回-EINVAL。2.3 flags 标志位的真实含义flags是申请中断最重要的参数它控制着中断控制器硬件层面的行为。我按实际开发中的使用频率排个序逐一说明IRQF_TRIGGER_RISING / IRQF_TRIGGER_FALLING / IRQF_TRIGGER_HIGH / IRQF_TRIGGER_LOW设置中断触发方式。边沿触发和电平触发在硬件行为上有本质区别电平触发只要电平保持有效就会持续上报边沿触发只在信号跳变沿上报一次。很多人申请完中断后发现“进了多次中断”或者“漏中断”基本都是触发方式和硬件实际行为没对上。IRQF_SHARED声明该中断可共享。需要注意同一根中断线上的所有 handler 都必须声明IRQF_SHARED只要有一个不声明共享注册就会失败。另外共享中断的 trigger 类型必须一致比如都必须是高电平触发这是硬件层面的硬性约束。IRQF_ONESHOT这个标志很关键。它表示在thread_fn执行完成之前中断线一直被屏蔽硬件层面 disabled不允许嵌套触发。这个标志主要用于防止线程化中断处理期间重复触发导致数据错乱特别适合那些需要“一次性完整处理”的边缘触发设备。但副作用也很明显如果thread_fn里耗时太长会严重影响中断响应实时性。IRQF_NO_THREAD强制不使用线程化即使系统开启了threadirqs启动参数。这个标志一般给真正不能线程化的场景使用比如某些时钟事件中断。IRQF_NOBALANCING标记中断不要参与 CPU 间的均衡迁移在 NUMA 系统里某些设备中断绑定固定 CPU 可能更合适。IRQF_NO_SUSPEND系统 suspend 时内核默认会禁用中断但某些设备要求 suspend 期间仍然能唤醒系统这时候需要这个标志。IRQF_FORCE_RESUME强制在 resume 时重新使能中断即使原本没有该中断的 suspend 处理逻辑。这些标志组合起来就让中断行为差异巨大。比如一个典型的多功能按键驱动如果按键抖动比较严重通常会选择IRQF_TRIGGER_FALLING | IRQF_ONESHOT把去抖逻辑放在线程化 handler 里期间自动屏蔽中断。2.4 设备树中断号的获取方式前面说过申请中断要有一个合法的 irq number。现在主流平台都采用设备树描述硬件中断号的获取有标准套路。设备树里典型的中断属性长这样gpio1 { key_int: key-int { compatible my-company,key-int; interrupt-parent gpio1; interrupts 5 IRQ_TYPE_EDGE_FALLING; }; };这里的5 IRQ_TYPE_EDGE_FALLING表示使用 GPIO1 组的第 5 号线下降沿触发。内核里获取中断号有几种方式/* 方式一platform 驱动推荐 */ int irq platform_get_irq(pdev, 0); if (irq 0) return irq; /* 方式二直接解析 device_node */ int irq irq_of_parse_and_map(dev-of_node, 0); /* 方式三通用的 fwnode 接口 */ int irq fwnode_irq_get(dev_fwnode(dev), 0);需要特别注意的是老的接口platform_get_resource(pdev, IORESOURCE_IRQ, 0)获取到的并不是最终的 Linux irq number在设备树环境下这个值可能是硬件中断号不能直接传给request_irq()。必须经过 irq domain 的映射后才能得到 Linux 中断号。现在内核已经废弃了这一用法新驱动都在用platform_get_irq()或irq_of_parse_and_map()不再需要手动映射。2.5 devm_ 版本的资源管理优势从我个人的实践来看新写的平台驱动强烈建议用devm_request_threaded_irq()理由非常简单不需要在驱动的 remove 接口里手动调用free_irq()。一旦设备驱动被卸载或者 probe 失败内核的资源管理框架会自动调用释放函数避免遗漏导致的中断残留。但这里有一个隐藏陷阱很多人都踩过devm_ 版本的释放顺序是在驱动 remove 回调之后执行的。如果你的驱动有一个运行中的线程或者 tasklet 还会访问硬件而且在 remove 时没有先把它停下来那可能在 remove 之后、devm 释放中断之前线程仍在跑突然触发中断结果是 use-after-free 或者访问已注销的硬件。所以即使是 devm 版本也建议在 remove 回调里显式地先停掉业务线程再让 devm 框架帮你释放中断。还有一个更细节的点devm_request_irq()的参数里那个void *dev到底怎么传很多驱动传 NULL但这有一个致命问题——如果用free_irq(irq, NULL)释放时内核会通过 dev_id 找到对应的 handler。当多个驱动共享同一个 IRQ 时内核靠 dev_id 来区分哪个 handler 属于谁。如果都用 NULL释放时会直接 release 整条链路上的所有 handler导致其他驱动的中断失效。所以 dev_id 绝对不是随便传的通常传struct device *或struct my_private_data *。3. 实操过程与核心实现从头写一个申请中断的串口按键驱动3.1 场景定义与前置条件接下来用一个具体的例子把整个流程走一遍。假设现在有一块开发板上面有一个 GPIO 按键通过下降沿触发中断。我们写一个非常小的字符设备驱动功能是每次按键按下时在中断上下文里记录一次按键事件并把次数累积到原子变量中用户态程序通过读节点获取按键总次数。先看一下硬件和内核环境的基本条件SoC某 ARM 平台GPIO 中断由 gpio-keys 之外的普通 GPIO 驱动管理内核版本5.10 LTS设备树已配置好 interrupt-parent 和 interrupts设备树部分之前已经给出这里从驱动代码开始。3.2 模块初始化和中断号解析#include linux/module.h #include linux/platform_device.h #include linux/interrupt.h #include linux/of.h #include linux/atomic.h #include linux/fs.h #include linux/miscdevice.h #include linux/uaccess.h #define DRIVER_NAME demo_key_irq struct key_irq_data { int irq; atomic_t press_count; struct miscdevice misc; }; static struct key_irq_data *g_key_data;需要关注的是这个私有数据结构体后续request_threaded_irq的 dev_id 就会传它。把设备相关的所有上下文收集到一个结构体里是内核驱动的通行做法。3.3 probe 中申请中断的完整流程这一步是整个文章的核心我把每一步的思考都写在注释里。static irqreturn_t key_hardirq_handler(int irq, void *dev_id) { /* 硬中断上下文只做最快速的处理 */ struct key_irq_data *data dev_id; /* 记录一次硬中断产生事件实际业务不在这里处理 */ atomic_inc(data-press_count); return IRQ_WAKE_THREAD; } static irqreturn_t key_thread_fn(int irq, void *dev_id) { /* 线程上下文可以做耗时操作比如上报事件、睡眠式读取等 */ struct key_irq_data *data dev_id; /* 这里可以调用 msleep()、mutex_lock()、甚至 schedule_work() */ dev_info(data-misc.this_device-dev, key pressed, total%d\n, atomic_read(data-press_count)); return IRQ_HANDLED; } static int key_irq_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct key_irq_data *data; int ret; data devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; >static void key_irq_remove(struct platform_device *pdev) { struct key_irq_data *data g_key_data; /* 先注销 misc 设备确保不再有用户态访问 */ misc_deregister(data-misc); /* 不需要手动 free_irqdevm 会处理但如果业务线程还在跑 必须先停止业务线程避免 remove 后仍访问寄存器 */ }有人可能会问既然 devm 会释放中断那 remove 里到底要不要显式调用disable_irq()我的经验是如果你有并发线程可能访问硬件必须在 remove 回调里先停止那些线程然后手动调用disable_irq()或者确保没有新的中断在途再退出 remove。因为 devm 释放中断的实际时机是在 remove 回调完全返回之后这个窗口期内如果还有代码路径触碰硬件就会出问题。3.6 中断上下文的编码限制清单写硬中断 handler 时我建议把下面这张“禁止清单”贴在旁边方便随时对照禁止操作原因替代方案kmalloc(size, GFP_KERNEL)可能睡眠用 GFP_ATOMIC 或预分配mutex_lock()睡眠锁spinlock 或 atomic 操作copy_to_user()/copy_from_user()需要访问用户态页表workqueue 或线程化中断msleep()/udelay()长延时死锁/阻塞尽量减少必要时用 ndelay 短延时直接调用printk()太频繁严重拖慢系统trace_printk 或者只在错误时打印访问用户空间指针无意义/危险永不访问硬中断 handler 需要保证的第一原则就是要么快速返回IRQ_HANDLED要么快速返回IRQ_WAKE_THREAD。超过几十微秒的处理时间就是不合格的驱动。3.7 编译验证与中断触发测试代码写完先编译加载# 编译 make -C /lib/modules/$(uname -r)/build M$(pwd) modules # 安装 sudo insmod key_irq.ko # 查看中断是否注册成功 cat /proc/interrupts | grep demo_key_irq如果一切正常按下按键后/proc/interrupts对应行的计数会递增。同时可以用dmesg查看线程化 handler 的打印信息。一个非常有用的调试工具是trace_irq_handler_entry和trace_irq_handler_exit可以用 tracefs 直接跟踪每次中断进入和退出的耗时cd /sys/kernel/tracing echo 1 events/irq/irq_handler_entry/enable echo 1 events/irq/irq_handler_exit/enable cat trace通过这个跟踪点可以直接看到 handler 的入口和出口时间戳轻松定位那个 handler 执行超时。4. 常见问题与排查技巧实录4.1 问题速查表我先给出一张高频问题表下面是每个问题的详细分析和处理思路。现象可能原因排查方向insmod 报IRQ handler type mismatch中断共享标志或触发方式冲突检查硬件 IRQ 是否被其他设备使用对比 flags中断只触发一次之后失效IRQF_ONESHOT线程化处理中异常查看 thread_fn 是否返回 IRQ_HANDLED或线程崩溃按键按下乱触发多次触发方式不对或没有消抖更改为电平触发或在硬件上做 RC 滤波request 返回-EBUSY中断号已被申请cat /proc/interrupts查看占用者或换 dev_id卸载模块后系统死机释放中断顺序不对remove 里先停止业务再注销设备中断响应延迟超大中断被其他长处理阻塞使用threadirqs启动参数或改用线程化中断/proc/interrupts无计数gpio 子系统未配置为中断模式检查gpio_request和gpio_to_irq确认引脚功能4.2 “IRQ handler type mismatch”深度解析这个错误在实际项目里出现频率极高主要原因通常只有一个多个设备共享同一个物理 IRQ但申请时 trigger 类型不一致。举个例子某款开发板把 WIFI 模块和蓝牙模块的 interrupt 引脚连到了同一个 GPIO 中断控制器上WIFI 驱动申请的是高电平触发蓝牙驱动申请的是下降沿触发。第二个request_irq()就会报这个错因为内核检测到同一根中断线上 trigger 配置冲突。解决方式有几种修改设备树让两者使用一致的 trigger 类型并都加上IRQF_SHARED。如果硬件允许把其中一个设备改接到其他中断引脚彻底避免共享。使用 irq domain 的硬件配置强制在中断控制器层面统一触发方式。排查这种问题第一件事是去/proc/interrupts看这一条 IRQ 线是谁在占用第二件事是看cat /sys/kernel/irq/irq_number/trigger里记录的类型。4.3 共享中断的 dev_id 正确用法共享中断场景下request_irq()的最后一个参数必须传一个能唯一标识本驱动的值通常用struct device *或者驱动的私有数据结构指针。原因前面说了free_irq()要精准删除自己的 handler靠的就是这个 dev_id。我在实际项目里见过一个反面教材某同事为了图省事所有中断都传 NULL。平时单设备没事。后来产品加了一个共享中断的功能直接free_irq(irq, NULL)把同一个 IRQ 上另一个驱动的中断一起干掉了导致另一个子系统完全瘫痪排查了两天。这种事说多了都是泪但确实非常典型。还有一个相关的坑共享中断的 handler 返回IRQ_NONE的语义。因为共享中断触发时所有 handler 都会被调用但只有真正产生中断的设备对应的 handler 才应该返回IRQ_HANDLED其他 handler 必须诚实地返回IRQ_NONE。如果某个共享中断的所有 handler 都返回IRQ_NONE内核会认为发生了“spurious interrupt”如果这种情况频繁出现内核可能会直接禁用该中断线导致设备从此收不到中断。所以共享中断 handler 的第一件事就是读取硬件状态寄存器确认到底是不是自己的设备产生了中断不是就直接返回IRQ_NONE谁也不要冤枉谁。4.4 中断线程化后 IRQF_ONESHOT 的副作用IRQF_ONESHOT好用但副作用也很明显线程化 handler 执行期间这个中断线在硬件层面是被屏蔽的。如果线程里处理时间很长期间设备再次触发中断可能被硬件无谓地丢弃。这个问题在“边沿触发 高速事件”场景下尤其致命。比如计数器类设备多次触发在同类事件中非常常见但由于IRQF_ONESHOT把中断线关了中间的事件直接丢失。解决策略很简单只有防重入必要性极高的场景才用IRQF_ONESHOT其他场景可以让 thread_fn 自己通过原子标志位防重入。比如用test_and_set_bit()判断当前是否正在处理同一事件是则直接返回处理完成后再清除标志位。这个方法既能保证不重入又不会完全关闭中断线。4.5 中断 handler 执行耗时过长怎么办如果硬中断 handler 执行时间超过 100 微秒这个驱动基本就不合格了。排查耗时有一个非常实用的工具内核的irqsofftracer 和preemptirqsofftracer。# 打开延迟跟踪 echo 0 /sys/kernel/tracing/tracing_on echo irqsoff /sys/kernel/tracing/current_tracer echo 1 /sys/kernel/tracing/tracing_on # 触发几次中断操作 # 然后查看记录 cat /sys/kernel/tracing/trace这个 trace 会告诉你中断关闭irqsoff的最长延迟发生在哪个函数、哪一行代码。定位到具体函数后把耗时操作挪到线程化 handler 或 workqueue 中即可。如果是硬件本身的原因导致中断处理必须慢比如需要 SPI 读寄存器那最快的方案仍然是线程化中断。线程化中断虽然响应会有几百微秒的额外延迟但在非实时场景下完全可接受。4.6 free_irq 时机不当导致 oops模块卸载时最常见的崩溃场景是free_irq()执行时中断线程还在跑。内核在free_irq时虽然会同步等待中断线程退出synchronize_irq()但如果你的驱动里还有其他工作队列或内核线程在访问同一块寄存器或私有数据那就和中断本身无关了崩溃只是时间问题。我建议模块卸载时按这个严格顺序操作对外停止业务注销 misc 设备、misc 设备节点不再可访问。停止辅助内核线程 / 取消 workqueue。调用free_irq()或等待 devm 自动释放。释放 DMA buffer / 寄存器映射等资源。第 2 步和第 3 步绝对不能调换否则会有竞态窗口。4.7 线程化中断 if 与 tasklet 的选择很多新人会问既然线程化中断这么方便为什么还需要 tasklet我的理解是这两者根本不是替代关系而是面向不同实时性需求的工具。tasklet运行在 softirq 上下文中本质还是原子上下文不能睡眠但它的优先级高于普通进程调度延迟很小。适合计算密集但不需要睡眠的中断后续处理。线程化中断运行在普通内核线程上下文可以睡眠调用mutex_lock()、copy_to_user()都没问题。但它本质是一个普通线程调度优先级取决于你设置的 RT 优先级。我通常的判断标准是如果中断后续处理需要访问用户空间、获取互斥锁、或者嵌入到一个大的业务逻辑流中选线程化中断。如果只是纯数据处理比如从 FIFO 里搬数据到内存选 tasklet 或者硬中断直接处理。完全没有必要为了“看起来优雅”而强制使用线程化中断过度设计同样是隐性负债。4.8 调试小工具/proc/interrupts 的正确解读/proc/interrupts是排查中断问题最直接的信息来源但很多新手通常不知道怎么看。第一列是 IRQ number后面跟的是各个 CPU 上该中断触发的次数然后是中断控制器类型和驱动名称。几个关键判断技巧如果某个中断在多个 CPU 上的计数明显不均衡说明没有打开 irqbalance 或者中断亲和性配置不对。如果触发次数远大于设备实际事件数多半是触发方式配置错误导致重复上报。如果有none驱动占用中断号说明某些中断控制器内部中断还没有驱动注册。另外cat /proc/interrupts时留意中断名后面的(shared)标记这说明这一行有多个驱动共享。4.9 设备树中断配置常见的几个坑设备树里interrupts属性的数字怎么填不同平台差异很大。第一个数字是 controller 内部的 hwirq 号第二个是触发类型。这里最常犯的错误是直接把 GPIO 编号当 hwirq 用但平台上 GPIO 中断控制器有自己的中断域hwirq 编号可能和 GPIO 编号有偏移或映射关系。比如某 SoC 的 GPIO 控制器的中断域起始偏移是 32那么设备树里用第 5 号 GPIO 做中断interrupts 5 ...中的 5 会被 GPIOLIB irqdomain 解释为从 32 开始的偏移最终映射到 Linux irq number 37。如果你在驱动里想当然地打印 irq number 并对硬件手册找编号就会对不上。排查这类问题有一个必杀技调试时打印platform_get_irq()的返回值然后对比cat /proc/interrupts里该中断在哪个控制器下、编号对不对。切记设备树里的数字只是 hwirq不是 Linux 最终 irq number。5. 真实案例分析一次按键中断导致系统卡死的排查实录最后分享一个我处理过的真实案例用来把上面的知识点串起来。当时有一块带 LCD 屏和按键的开发板按键驱动使用的是线程化中断 IRQF_ONESHOT刚加载时一切正常但按键连续快速按了几十次之后整个系统开始变慢最后完全卡死。从dmesg里只能看到大量的BUG: soft lockup - CPU#0 stuck for 22s!报错。因为没法直接看现场只能通过代码审查和 trace 逐步缩小范围。最终定位到thread_fn里使用了一个全局互斥锁来保护一份共享的按键上报缓冲区但这个锁同时也被另一个高优先级实时线程持有。按键中断线程陷入阻塞由于IRQF_ONESHOT又在等这个线程执行完才打开中断线导致后续中断一直处于 pending 状态系统整体崩溃。这个案例的核心教训有三个线程化中断的 thread_fn 里不能拿普通互斥锁去等一个不确定时长的锁特别是在实时线程存在的情况下。正确做法是使用无锁队列如kfifo、spsc_queue或者try_lock后立即返回。IRQF_ONESHOT的“保护”本质是“把中断线关掉等线程”如果线程内部有不确定性这个保护就会变成灾难。中断处理路径上绝不能调用任何可能导致长时间睡眠的函数即使函数本身是“可以睡眠”的也要仔细评估锁的依赖关系。最终修复方案是把互斥锁替换成无锁环形缓冲区线程之间通过原子读写索引完成数据传递。改动不大但系统立刻恢复正常连续按键几万次也没有再出问题。6. 写在最后几个延伸的小建议顺着中断这个话题我再说几个延伸的小技巧对排查问题会有帮助。一个是系统启动参数threadirqs。加上它之后内核会把几乎所有非关键中断自动线程化。这是排查“中断处理耗时过长导致系统卡顿”类问题的利器。如果加了threadirqs之后系统明显改善说明有驱动的硬中断 handler 超时了。再一个是中断亲和性。通过/proc/irq/irq_number/smp_affinity可以手动绑定中断到特定 CPU在多核系统上有时候能显著降低中断对某个核心的冲击。不过一般场景下交给 irqbalance 自动处理就够了。最后想说中断子系统是 Linux 内核里非常精巧的一套设计但越是精巧的机制越要求使用者在充分理解的基础上才能用好。申请中断看起来只是几个 API 的调用真正决定质量的是你对中断上下文、标志位语义、资源生命周期这些底层细节的把握。希望这篇文章能帮你在写驱动的时候少踩几个坑尤其是那些文档里写不明白的坑。以后有机会再单独写一篇关于中断下半部的文章。这四篇系列做到了驱动申请中断这一步基本把中断子系统的主链路走完了。实操中有问题欢迎留言交流毕竟这类问题在书本上往往讲不到这么细。