引言

软中断、tasklet和工作队列并不是Linux内核中一直存在的机制,而是由更早版本的内核中的“下半部”(bottom half)演变而来。

上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。举个例子:在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。
两者的主要区别在于:中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面4条:

  1. 如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
  2. 如果一个任务和硬件相关,将其放在中断处理程序中执行。
  3. 如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
  4. 其他所有任务,考虑放在下半部去执行。

有写内核任务需要延后执行,因此才有的下半部,进而实现了三种实现下半部的方法。这就是本文要讨论的软中断Tasklet工作队列

1

软中断

软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:

  • 产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(即单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)。
  • 可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(一个函数在被调用执行期间(尚未调用结束),由于某种时序又被重复调用,称之为重入; 允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结构。

tasklet

由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:

  • 一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
  • 多个不同类型的tasklet可以并行在多个CPU上。
  • 软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行。

工作队列

从上面的介绍看以看出,软中断运行在中断上下文中,因此不能阻塞和睡眠,而tasklet使用软中断实现,当然也不能阻塞和睡眠。但如果某延迟处理函数需要睡眠或者阻塞呢?便使用工作队列来实现。

内核定义了一组队列,其中每个队列都包含一个由等待调用的函数组成的链表。根据其所处队列的位置,这些函数会在某个时刻执行。驱动程序可以把它们的下半部注册到合适的队列上去。缺点是,对于一些性能要求较高的子系统(例如定时器、网络部分),它则不能胜任。

把推后执行的任务叫做工作(work),描述它的数据结构为work_struct ,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct ,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events。

工作队列(work queue)是另外一种将工作推后执行的形式。工作队列可以把工作推后,交由一个内核线程去执行—这个下半部分总是会在进程上下文执行,但由于是内核线程,其不能访问用户空间。最重要特点的就是工作队列允许重新调度甚至是睡眠。

软中断、tasklet与工作队列的区别与联系

软中断

  1. 软中断是在编译期间静态分配的。

  2. 最多可以有32个软中断。

  3. 软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。

  4. 可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

  5. 目前只有两个子系直接使用软中断:网络和SCSI。

  6. 执行时间有:从硬件中断代码返回时、在ksoftirqd内核线程中和某些显示检查并执行软中断的代码中。

tasklet

  1. tasklet是使用两类软中断实现的:HI_SOFTIRQ和TASKLET_SOFTIRQ。
  2. 可以动态增加减少,没有数量限制。
  3. 同一类tasklet不能并发执行。
  4. 不同类型可以并发执行。
  5. 大部分情况使用tasklet。

工作队列

  1. 由内核线程去执行,换句话说总在进程上下文执行。
  2. 可以睡眠,阻塞。

2

选择方法

  • 如果推后执行的任务需要睡眠,那么只能选择工作队列
  • 如果推后执行的任务需要延时指定的时间再触发,那么使用工作队列,因为其可以利用timer延时(内核定时器实现)。
  • 如果推后执行的任务需要在一个tick之内处理,则使用软中断或tasklet,因为其可以抢占普通进程和内核线程,同时不可睡眠。
  • 如果推后执行的任务对延迟的时间没有任何要求,则使用工作队列,此时通常为无关紧要的任务。

参考:

https://blog.csdn.net/godleading/article/details/52971179

https://blog.csdn.net/Ivan804638781/article/details/115312421