讲解一:
-
什么是延迟处理机制?
在Linux内核中,**延迟处理机制(Deferred Execution Mechanisms)**是指将某些无法立即执行或不宜立即执行的任务推迟到稍后时间点执行的机制。这些机制主要用于:
减少中断处理时间(避免长时间关中断)
提高系统响应能力(避免关键路径阻塞)
优化CPU利用率(合理调度非紧急任务) -
主要延迟处理机制
Linux内核提供了多种延迟处理方式,适用于不同场景:
(1) 软中断(SoftIRQ)
特点:
运行在中断上下文(不能睡眠)
可并行执行(多核CPU上)
低延迟,用于高频短时任务
典型应用:
网络数据包处理(NET_RX_SOFTIRQ)
块设备I/O调度(BLOCK_SOFTIRQ)
(2) 任务队列(Tasklet)
特点:
基于软中断实现
串行执行(同一tasklet不会并发)
适合需要顺序处理的中断下半部
典型应用:
USB设备事件处理
输入设备中断处理
(3) 工作队列(Workqueue)
特点:
运行在进程上下文(可以睡眠)
由内核线程(kworker)执行
适合长时间运行的任务
典型应用:
文件系统操作
设备驱动初始化
(4) 内核定时器(Timer)
特点:
在指定时间点触发
支持单次或周期性执行
典型应用:
超时处理
定期状态检查 -
机制对比
机制执行上下文可抢占性并行性适用场景软中断中断上下文不可抢占并行高频短时任务(网络包处理)Tasklet中断上下文不可抢占串行需要顺序处理的中断下半部Workqueue进程上下文可抢占并行长时间可睡眠任务定时器中断上下文不可抢占N/A定时/周期性任务 -
如何选择合适机制?
需要极低延迟 → 软中断
需要任务顺序执行 → tasklet
操作可能睡眠 → workqueue
定时触发需求 → 内核定时器 -
实际应用案例
网络子系统:使用软中断处理数据包接收
USB驱动:使用tasklet处理设备事件
文件系统:使用workqueue进行数据写入
看门狗:使用定时器进行超时检测
这些机制共同构成了Linux内核高效处理异步事件的基础架构,使得系统能够在保证实时性的同时维持高吞吐量。
讲解二:
软中断、tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来。下半部的机制实际上包括五种,但2.6版本的内核中,下半部和任务队列的函数都消失了,只剩下了前三者。本文重点在于介绍这三者之间的关系。
(1)上半部和下半部的区别
上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。举个例子:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。
两者的主要区别在于:中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面四条:
a)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
b)如果一个任务和硬件相关,将其放在中断处理程序中执行。
c)如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
d)其他所有任务,考虑放在下半部去执行。
(2)为什么要使用软中断?
软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。
(3)为什么要使用tasklet?(tasklet和软中断的区别)
由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:
a)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
b)多个不同类型的tasklet可以并行在多个CPU上。
c)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。
(4)为什么要使用工作队列work queue?(work queue和软中断的区别)
上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是do_IRQ退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。
因此在2.6版的内核中出现了在内核态运行的工作队列(替代了2.4内核中的任务队列)。它也具有一些可延迟函数的特点(需要被激活和延后执行),但是能够能够在不同的进程间切换,以完成不同的工作。