软中断 、 tasklet 和 工作队列 是内核实现中断下半部的三种方式。
软中断
软中断保留给系统中对时间要求最严格以及最重要的下半部使用。目前,只有两个子系统(网络和 SCSI)直接使用软中断。此外,内核定时器和tasklet 都是建立在软中断上的。如果你想加入一个新的软中断,首先应该问问自己为什么用tasklet实现不了。tasklet 可以动态生成,由于它们对加锁的要求不高,所以使用起来也很方便,而且它们的性能也非常不错。当然,对于时间要求严格并能自己高效地完成加锁工作的应用,软中断会是正确的选择。
软中断处理程序执行的时候,允许响应中断,但它自己不能休眠。 在一个处理程序运行的时候,当前处理器上的软中断被禁止。但其他的处理器仍可以执行别的软中断。实际上,如果同一个软中断在它被执行的同时再次被触发了,那么另外一个处理器可以同时运行其处理程序。这意味着任何共享数据(甚至是仅在软中断处理程序内部使用的全局变量)都需要严格的锁保护 。这点很重要,它也是tasklet更受青睐的原因。单纯地禁止你的软中断处理程序同时执行不是很理想。如果仅仅通过互斥的加锁方式来防止它自身的并发执行,那么使用软中断就没有任何意义了。因此,大部分软中断处理程序,都通过采取单处理器数据(仅属于某一个处理器的数据,因此根本不需要加锁)或其他一些技巧来避免显式地加锁,从而提供更出色的性能。 引入软中断的主要原因是其可扩展性 。如果不需要扩展到多个处理器,那么,就使用tasklet吧。tasklet本质上也是软中断,只不过同一个处理程序的多个实例不能在多个处理器上同时运行。
tasklet
tasklet是利用软中断实现的一种下半部机制。我们之前提到过,它和进程没有任何关系。tasklet和软中断在本质上很相似,行为表现也相近,但是,它的接口更简单,锁保护也要求较低。 选择到底是用软中断还是tasklet 其实很简单:通常你应该用tasklet。就像我们在前面看到的,软中断的使用者屈指可数。它只在那些执行频率很高和连续性要求很高的情况下才需要使用。而tasklet却有更广泛的用途。大多数情况下用tasklet效果都不错,而且它们还非常容易 使用。
因为是靠软中断实现,所以tasklet不能睡眠 。这意味着你不能在tasklet中使用信号量或者其他什么阻塞式的函数。由于tasklet运行时允许响应中断,所以你必须做好预防工作(如屏蔽中断然后获取一个锁),如果你的tasklet和中断处理程序之间共享了某些数据的话。两个相同的tasklet决不会同时执行 ,这点和软中断不同---尽管两个不同的tasklet 可以在两个处理器上同时执行。如果你的tasklet和其他的tasklet 或者是软中断共享了数据,你必须进行适当地锁保护。
工作队列
工作队列(work queue)是另外一种将工作推后执行的形式,它和我们前面讨论的所有其他形式都不相同。工作队列可以把工作推后,交由一个内核线程去执行--这个下半部分总是会在进程上下文中执行 。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许重新调度其至是睡眠 。 通常,在工作队列和软中断/tasklet中做出选择非常容易。如果推后执行的任务需要睡眠那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择软中断或tasklet。实际上工作队列通常可以用内核线程替换。但是由于内核开发者们非常反对创建新的内核线程(在有些场合,使用这种冒失的方法可能会吃到苦头),所以我们也推荐使用工作队列。当然,这种接口也的确很容易使用。 如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列。它是唯一能在进程上下文中运行的下半部实现机制,也只有它才可以睡眠。这意味着在你需要获得大量的内存时,在你需要获取信号量时,在你需要执行阻塞式的IO操作时,它都会非常有用。如果你不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet吧。
参考自《linux内核设计与实现》