linux中断:顶半部与底半部

张开发
2026/4/16 1:18:21 15 分钟阅读

分享文章

linux中断:顶半部与底半部
中断可以分成两个部分顶半部和底半部一、中断为何分为两部分进入中断处理函数时CPU 通常处于关闭中断或屏蔽同级中断的状态。这意味着其他设备的中断进不来系统调度被暂停整个系统的响应速度会急剧下降如果你的中断处理函数里循环处理大量数据延时等待打印一堆 log做复杂计算那系统就会变卡、丢数据甚至看起来像 “死机”。所以内核设计者提出一个原则中断处理必须快进快出不能耗时。但现实是很多设备确实需要快速响应硬件又需要做一些耗时的后续处理。矛盾怎么解决于是就有了上半部 下半部 的分离设计。二、顶半部顶半部 硬件中断处理函数本身。顶半部只负责 “紧急且快速” 的事其余一律甩给下半部它的特点执行时机最紧急中断上下文不能睡眠、不能阻塞必须极快完成它只适合干三件事1、读取硬件状态 / 清除中断标志告诉硬件 “中断我收到了你别再重复触发”。2、做必须立即完成的极简操作比如简单寄存器读写、标记事件发生。3、启动底半部把真正耗时的工作丢出去。三、底半部底半部 被顶半部调度出来的、延后执行的处理流程。下半部处理不紧急、但耗时间的逻辑。它的特点不那么紧急执行时中断已经打开可以被新中断打断部分机制允许睡眠比如 workqueue适合干这些事数据拷贝、数据解析网络包 / 串口数据处理大量打印日志控制外设的慢操作上报数据到应用层补充同步调用事情要一件一件做完不做完就不往下走异步调用不用等你先去干别的等好了通知你因此底半部是异步调用的四、顶半部vs底半部对比项上半部 top half下半部 bottom half执行时机中断触发立即执行延后执行中断状态关中断 / 屏蔽中断开中断可被新中断打断上下文中断上下文软中断 / 进程上下文能否睡眠绝对不能workqueue 可以睡tasklet 不行执行速度必须极快可以适当耗时主要任务应答硬件、清中断、调度下半部真正的数据处理、业务逻辑五、Linux 中有哪些下半部实现1. tasklet小任务做不耗时任务是一种软中断1、特点也要较短小基于软中断不能睡眠执行快、轻量适合简单、快速的延后处理同一个 tasklet 不会在多个 CPU 上同时跑2、适用场景快速、简单、非阻塞的延后工作不需要睡眠中断后马上要处理的轻量任务3、使用步骤定义 tasklet 结构体写 tasklet 回调函数初始化 tasklet在中断上半部调度 tasklet2. workqueue工作队列做耗时任务就是一个普通的内核线程1、特点运行在内核线程上下文可以睡眠、可以阻塞写驱动最安全、最常用适合稍微复杂的处理逻辑2、适用场景需要延时需要睡眠需要复杂处理需要操作 I2C、SPI、GPIO 等慢设备3、使用步骤定义 work_struct写工作函数初始化 work在上半部调度3、二者对比机制适用场景核心特性你的选择建议Tasklet轻量级延时任务基于软中断不能睡眠执行效率极高处理速度极快、短小的任务Workqueue重量级延后处理运行在内核线程上下文可以睡眠最常用适合读写文件、复杂逻辑、延时操作对比项taskletworkqueue运行环境软中断上下文进程上下文能否睡眠不能可以能否阻塞不能可以执行速度很快稍慢安全性一般非常安全抢占不可抢占可被抢占驱动推荐轻量任务绝大多数驱动场景最典型用途快速清标志、简单处理数据处理、延时、设备操作六、完整驱动代码上半部 tasklet workqueue#include linux/init.h #include linux/module.h #include linux/interrupt.h #include linux/workqueue.h #include linux/gpio.h #include linux/of_gpio.h #include linux/platform_device.h /* 本驱动演示 * 中断上半部 → 快速处理、清中断 * tasklet → 轻量级下半部不能睡眠 * workqueue → 通用下半部可以睡眠 */ // // 1. 定义 tasklet软中断下半部 // struct tasklet_struct my_tasklet; void tasklet_func(unsigned long data) { printk( 下半部tasklet 执行不能睡眠\n); } // // 2. 定义 workqueue可睡眠下半部 // struct work_struct my_work; void work_func(struct work_struct *work) { printk( 下半部workqueue 执行可以睡眠\n); // workqueue 可以睡眠tasklet 绝对不能这么写 msleep(50); } // // 3. 中断上半部中断处理函数 // irqreturn_t my_irq_handler(int irq, void *dev_id) { printk(\n★ 进入中断上半部快速处理\n); // 1. 清中断硬件操作必须快 // 这里省略硬件寄存器操作... // 2. 调度 tasklet 下半部 tasklet_schedule(my_tasklet); // 3. 调度 workqueue 下半部 schedule_work(my_work); printk(★ 退出中断上半部\n); return IRQ_HANDLED; } // // 4. 驱动入口初始化 // static int __init my_drv_init(void) { int irq; // 初始化 tasklet tasklet_init(my_tasklet, tasklet_func, 0); // 初始化 workqueue INIT_WORK(my_work, work_func); // 模拟获取一个中断号真实驱动从设备树获取 // 这里假设你用的是 GPIO 中断 irq gpio_to_irq(101); // 101 是示例 GPIO // 注册中断 request_irq(irq, my_irq_handler, IRQF_TRIGGER_RISING, my_irq, NULL); printk( 中断驱动初始化完成 \n); return 0; } // // 驱动出口 // static void __exit my_drv_exit(void) { int irq gpio_to_irq(101); free_irq(irq, NULL); tasklet_kill(my_tasklet); printk( 驱动卸载 \n); } module_init(my_drv_init); module_exit(my_drv_exit); MODULE_LICENSE(GPL);七、补充1、中断上下文不可被打断不可打断不做休眠不做耗时任务不被操作系统调度这里的不可被打断是同级之间的同级之间不可打断高优先级可以打断中断上下文是 CPU 响应硬件中断后进入的执行环境它并非属于任何一个用户进程或内核进程没有对应的进程控制块task_struct也没有独立的进程 ID。当硬件触发中断时CPU 会立即暂停当前正在执行的任务跳转到中断处理函数即中断上半部此时就进入了中断上下文此外基于软中断实现的 tasklet其执行环境也属于中断上下文的范畴。中断上下文的核心要求是 “快进快出”因为在这种上下文下CPU 会屏蔽同级或更低优先级的中断若执行时间过长会导致系统调度暂停、其他设备中断无法响应甚至引发系统卡顿或崩溃。同时中断上下文有严格的限制绝对不能睡眠、不能进行进程调度也不能调用任何可能导致阻塞的函数如 msleep、mutex_lock、copy_from_user 等而且其栈空间非常有限不能进行递归调用或定义过大的数组只能执行最紧急、最简短的操作比如清除中断标志、读取硬件寄存器状态以及调度下半部任务完成后立即退出恢复系统正常运行。包含中断服务程序/软中断/tasklet2、进程上下文可以被打断可被打断、可休眠、可阻塞、可做耗时任务、可被操作系统调度workqueue属于进程上下文进程上下文是 CPU 执行用户进程、内核线程或驱动初始化 / 卸载函数时所处的执行环境它明确属于某个具体的进程或内核线程拥有完整的进程控制块有独立的进程 ID。无论是用户程序调用系统调用如 open、read、write进入内核还是内核线程比如 workqueue 对应的内核线程执行任务或是驱动的 init、exit 函数运行时都处于进程上下文。与中断上下文不同进程上下文没有 “必须快速执行” 的强制要求支持进程调度和睡眠 —— 内核可以根据调度策略将当前进程切换出去执行其他优先级更高的任务待条件满足后再切换回来继续执行。因此在进程上下文下我们可以调用绝大多数内核 API包括需要延时的 msleep、用于同步的互斥锁、用于数据拷贝的 copy_from_user/copy_to_user以及等待队列等执行时间也可以相对较长能够处理复杂的数据解析、外设慢操作等耗时任务。对于驱动开发而言workqueue 之所以能支持睡眠和阻塞操作核心就是因为它运行在进程上下文内核线程中这也是它成为驱动开发中最常用下半部机制的关键原因。包含open/read/write等等系统调用3、硬中断硬中断是可以打断tasklet的在 Linux 内核架构中硬中断对应的就是中断上半部运行在严格的中断上下文环境中。为了不阻塞整个系统硬中断处理有非常严格的限制执行时间必须极短不能睡眠、不能阻塞、不能进行耗时操作核心任务只包括快速应答硬件、清除中断标志避免硬件持续触发中断然后将复杂、耗时的处理逻辑交给软中断、tasklet 或 workqueue 等下半部机制去异步执行。

更多文章