工作延迟是Linux内核的核心机制,核心价值是将任务推迟执行,适配系统负载或特定时间窗口,通过区分执行上下文,弥补中断处理的局限性,实现中断上半部与延迟下半部的高效协同,为驱动开发提供关键的异步任务处理方案。
中断处理程序要求极简执行、快速退出,仅能处理关键操作,剩余耗时任务需转移至负载较低时执行,且中断上下文禁止阻塞调用,休眠操作需在进程上下文开展。延迟机制正好契合这一需求,让中断上半部快速收尾,将后续操作交由下半部异步处理。当下半部多在进程上下文运行,便于调度可休眠任务,这种“上半部快处理、下半部慢执行”的模式,成为内核中断处理的核心思路。
Linux内核提供三种延迟机制:softIRQ、tasklet和workqueue。softIRQ与tasklet在原子上下文运行,不可休眠,保障响应速度;workqueue在进程上下文运行,允许休眠,适配耗时任务,三者覆盖不同场景需求。
.1 softIRQ:内核核心高频延迟机制
softIRQ即软件中断,执行时可抢占除硬件中断外的其他任务,运行期间保持中断开启,专为高频线程任务设计,网络与块设备是内核仅有的两个直接使用softIRQ的子系统。该机制虽在中断开启状态下运行,但仍不可休眠,共享数据需用自旋锁防护,其相关定义在kernel/softirq.c,使用时需包含<linux/interrupt.h>。
softIRQ采用静态注册模式,无法动态创建和销毁,且仅支持静态编译的内核代码,可动态加载的模块无法使用。其核心结构为struct softirq_action,仅包含函数指针成员,指向软中断触发时执行的处理函数,函数原型固定为接收指向自身的指针。
运行时,softIRQ需通过open_softirq注册,参数为索引和处理函数指针,内核预定义了10种索引,索引值越小优先级越高,依次为HI、TIMER、NET_TX、NET_RX、BLOCK、BLOCK_IOPOLL、TASKLET、SCHED、HRTIMER、RCU,可通过/proc/softirqs查看运行统计。
调度softIRQ需调用raise_softirq或raise_softirq_irqoff,前者先关闭本地中断,完成调度后再恢复,后者要求中断已关闭时调用,二者最终均通过设置CPU的软中断位图标记待执行任务。调度后,do_softirq负责实际执行,它会保存软中断位图,禁用软中断后逐一执行挂起任务,且执行受两个条件限制:循环次数不超过10次,单次执行总时长不超过2毫秒,超出后唤醒ksoftirqd内核线程,在进程上下文处理剩余任务。
softIRQ的关键特性决定了其适用场景,同一softIRQ可跨CPU并行执行,但自身无法被抢占,需借助自旋锁保护共享数据,这种特性让它成为高负载、低延迟需求任务的首选,但严格的使用限制也让驱动模块无法直接采用。
.2 ksoftirqd:软中断的进程上下文补充
ksoftirqd是内核为每个CPU创建的专用线程,核心作用是兜底处理未执行的软中断。它随内核引导启动,优先级较低,与普通进程遵循相同的CPU调度规则。
当softIRQ在硬中断退出后的处理超时或超次数时,do_softirq会主动唤醒ksoftirqd,该线程在进程上下文运行,可规避原子上下文的限制,更适合处理海量积压的软中断任务。不过,若ksoftirqd长期占用CPU,说明系统软中断负载过高,会影响整体性能。
.3 tasklet:驱动开发的易用延迟方案
tasklet构建在softIRQ之上,基于HI_SOFTIRQ和TASKLET_SOFTIRQ实现,高优先级tasklet优先于普通tasklet执行,同时继承了softIRQ的原子上下文特性,不可休眠,但对驱动开发者更为友好。
其核心结构为struct tasklet_struct,包含链表指针、状态标识、原子计数和处理函数指针,数据成员可传递上下文参数。初始化支持静态和动态两种方式,静态用DECLARE_TASKLET或DECLARE_TASKLET_DISABLED,后者初始化为禁用状态,需通过tasklet_enable启用;动态则用tasklet_init,可灵活嵌入自定义数据结构。
调度tasklet需调用tasklet_schedule或tasklet_hi_schedule,分别对应普通优先级和高优先级,调度后会标记TASKLET_STATE_SCHED状态,将任务加入CPU对应的链表。执行时,同一tasklet仅在一个CPU运行,自动串行化,执行期间可被自身或中断处理程序重新调度,不会出现并发冲突,不同tasklet之间仍需自旋锁保护共享数据。
tasklet还提供完善的控制接口,tasklet_disable会阻塞等待当前tasklet执行完毕再返回,tasklet_disable_nosync则无需等待立即生效,tasklet_enable用于重新启用被禁用的tasklet,tasklet_kill用于销毁tasklet,它会等待任务执行完成并阻止后续调度,是模块卸载时的关键操作,但该函数只能在进程上下文调用,否则会引发死锁。
与softIRQ相比,tasklet的核心优势是易用性和串行化特性,同一tasklet不会并行执行,无需额外处理自身并发,仅需关注tasklet间的数据同步,同时它支持动态创建和销毁,适配驱动模块开发需求,因此成为驱动开发中原子上下文延迟任务的首选。