第8章 下半部和推后执行的工作

8.5 下半部机制的选择

在各种不同的下半部实现机制之间做出选择很重要。在2.6版本的内核中,有三种可能的选择:软中断、tasklet和工作队列。tasklet基于软中断实现,两者很相近。工作队列靠内核线程实现。

从设计的角度考虑,软中断提供的执行序列化的保障最少。这就要求软中断处理函数必须格外小心地采取一些步骤确保共享数据的安全,两个甚至更多相同类别的软中断有可能在不同的处理器上同时执行。如果被考察的代码本身多线索化的工作就做得非常好,比如网络子系统,它完全使用单处理器变量,那么软中断是非常好的选择。对于时间要求严格和执行频率很高的应用来说,它执行得也最快。

如果代码多线索化考虑得不充分,那么选择tasklet意义更大。它的接口非常简单,而且,由于两个同种类型的tasklet不能同时执行,所以实现起来会简单一些。tasklet是有效的软中断,但不能并发运行。驱动程序应当尽可能选择tasklet而不是软中断,当然,如果准备利用每一处理器上的变量或者类似的情形,以确保软中断能安全地在多个处理器上并发地运行,那么还是选择软中断。

如果需要把任务推后到进程上下文中完成,那么只能选择工作队列。如果并不需要睡眠,那么软中断和tasklet可能更合适。工作队列造成的开销最大,因为它要牵扯到内核线程甚至是上下文切换。这并不是说工作队列的效率低,如果每秒钟有几千次中断,那么采用其他的机制可能更合适一些。尽管如此,针对大部分情况,工作队列都能提供足够的支持。

如果讲到易于使用,工作队列当仁不让。使用缺省的events队列简直不费吹灰之力。接下来是tasklet,它的接口也很简单。最后才是软中断,必须静态创建,并且需要慎重考虑其实现。

一般的驱动程序的编写者需要做两个选择。首先,是不是需要一个可调度的实体来执行需要推后完成的工作——从根本上说,有休眠的需要吗?要是有,工作队列是唯一的选择。否则最好使用tasklet。要是必须专注于性能的提高,那么就考虑软中断。

8.6 在下半部之间加锁

在使用下半部机制时,即使是在一个单处理器的系统上,避免共享数据被同时访问是至关重要的。一个下半部实际上可能在任何时候执行。

使用tasklet(基于软中断实现)的一个好处在于,它自己负责执行的序列化保障:两个相同类型的tasklet不允许同时执行,即使在不同的处理器上也不行。tasklet之间的同步(当两个不同类型的tasklet共享同一个数据时)要正确使用锁机制。

如果进程上下文和一个下半部共享数据,在访问数据之前,需要禁止中断并得到锁的使用权。是为了本地和SMP的保护并且防止死锁的出现。

任何在工作队列中被共享的数据也需要使用锁机制。其中有关锁的要点和在一般内核代码中没有区别,因为工作队列本就是在进程上下文中执行。

8.7 禁止下半部

一般单纯禁止下半部的处理是不够的。为了保证共享数据的安全,常见的做法是,先获得锁然后再禁止下半部的处理。驱动中通常采用这种方法。如果编写的是内核的核心代码,可能仅需要禁止下半部就可以了。

如果需要禁止所有的下半部处理(所有的软中断和所有的tasklet),可以调用local_bh_disable()。允许下半部进行处理,可以调用local_bh_enable()。

这些函数有可能被嵌套使用——最后被调用的local_bh_enable()最终激活下半部。比如,第一次调用local_bh_disable(),则本地软中断处理被禁止;如果local_bh_disable()被调用三次,则本地处理器仍然被禁止;只有当第四次调用local_bh_enable()时,软中断处理才被重新激活。

函数通过preempt_count为每个进程维护一个计数器。当计数器变为0时,下半部才能够被处理。因为下半部的处理已经被禁止,所以local_bh_enable()还需要检查所有现存的待处理的下半部并执行它们。

这些函数与硬件体系结构相关,位于<asm/softirq.h>中,通常由一些复杂的宏实现。

这些函数并不能禁止工作队列的执行。因为工作队列是在进程上下文中运行的,不会涉及异步执行的问题,所以没有必要禁止它们执行。由于软中断和tasklet是异步发生的,所以,内核代码必须禁止它们。另一方面,对于工作队列来说,它保护共享数据所做的工作和其他任何进程上下文中所做的都差不多。

猜你喜欢

转载自blog.csdn.net/xiezhi123456/article/details/82777644