分类: LINUX
2008-11-18 21:09:34
2、下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。
3、并不存在严格明确的规定来说明到底什么任务应该在哪个部分中完成,如何做决定完全取决于驱动程序开发者自己的判断。对于在上半部和下半部之间划分工作,尽管不存在某种严格的规则,但还是有一些提示可供借鉴:
4、和上半部分只能通过中断处理程序实现不同,下半部可以通过多种机制实现。最早的Linux只提供"bottom half"这种机制用于实现下半部,这种机制也被称为"BH"。不久,内核开发者们就引入了任务队列机制来实现工作的推后执行,并用来代替BH机制。目前 这两种机制已经在2.5之后的版本中被去除。在2.6版本中,内核提供了三种不同形式的下半部实现机制:软中断、tasklet和工作队列。其实还有另外 一个可以用于将工作推后执行的机制是内核定时器。它可以把操作推迟到某个确定的时间段之后执行。也就是说,当你必须保证在一个确定的时间段过去后再运行 时,你应该使用内核定时器。
5、软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行-即使两个类型相同也可以。其适合于像网络这样对性能要求非常高的情况。此外,软中断必须在编译期间就进行静态注册。
6、软中断的实现:软中断的代码位于kernel/softirq.c文件中。其由softirq_action结构表示,定义
在
不管用什么办法唤起,软中断都要在do_softirq()中执行。该函数很简单,如果有待处理的软中断,do_softirq()就会循环遍历每一个,调用它们的处理程序。
7、使用软中断:软中断保留给系统中对时间要求最严格以及最重要的下半部使用。目前只有两个子系统-网络和SCSI直接使用软中断。此外,内核定时器和tasklets都是建立在软中断上的。对于时间要求严格并能自己高效地完成加锁工作的应用,软中断会是正确的选择。
1)分配索引
在编译期间,通过
2)注册你的处理程序
接着,在运行时通过调用open_softirq()注册软中断处理程序。软中断处理程序执行的时候,允许响应中断,但它自己不能休眠。在一个处理程序运 行的时候,当前处理器上的软中断被禁止。但其他处理器仍可以执行别的软中断。实际上,如果一个软中断在它被执行的同时再次被触发了,那么另外一个处理器可 以同时运行其处理程序。这意味着任何共享数据-甚至是仅在软中断处理程序内部使用的全局变量都需要严格的锁保护。如果仅仅通过互斥的加锁方式来防止它自身 的并发执行,那么使用软中断就没有任何意义。因此大部分软中断处理程序都通过采取单处理器数据或其他一些技巧来避免显式地加锁,从而提供更出色的性能。
3)触发你的软中断
raise_softirq()函数可以将一个软中断设置为挂起状态,让它在下次调用do_softirq()函数时投入运行。该函数在触发一个软中断之 前先要禁止中断,触发后再恢复回原来的状态。如果中断已经被禁止了,那可以调用另一函数raise_softirq_irqoff(),这会带来一些优化 效果。在中断处理程序中触发软中断是最常见的形式。在这种情况下,中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出。内核在执行完中 断处理程序以后,马上就会调用do_softirq()函数。于是软中断开始执行中断处理程序留给它去完成的剩余任务。
8、Tasklets是利用软中断实现的一种下半部机制。它和进程没有任何关系。Tasklets和软中断在本质上很相似,行为表现也相近,但是它 的接口更简单,锁保护也要求较低。选择到底是用软中断还是tasklets其实很简单:通常你应该用tasklets,软中断的使用者屈指可数。它只在那 些执行频率很高和连续性要求很高的情况下才需要。
9、tasklet是通过软中断实现的,所以它们本身也是软中断。Tasklets由tasklet_struct结构表示。每个结构体单独代表一
个tasklet,其定义在
10、使用Tasklets
1)声明自己的Tasklet
既可以使用
2)编写自己的tasklet处理程序
tasklet处理程序必须符合规定的函数类型:void tasklet_handler(unsigned long data)。因为是靠软中断实现,所以tasklet不能睡眠。这意味着你不能在tasklet中使用信号量或其他什么阻塞式函数。如果你的 tasklet和其他的tasklet或软中断共享了数据,你必须进行适当的锁保护。
3)调度自己的tasklet
通过调用tasklet_schedule()函数来调度。在tasklet被调度以后在其还没有得到运行机会之前,如果一个相同的tasklet又被调 度了,那么它仍只会运行一次。而如果这时它已经开始运行了,那么这个新的tasklet会被重新调度并再次运行。作为一种优化措施,一个tasklet总 在调度它的处理器上执行-这是希望更好地利用处理器的高速缓存。可以调用tasklet_disable()函数来禁止某个指定的tasklet,也可以 调用tasklet_enable()函数激活一个tasklet。还可以调用tasklet_kill()函数从挂起的队列中去掉一个tasklet。
11、每个处理器都有一组辅助处理软中断的内核线程。当内核中出现大量软中断的时候,这些内核进程就会辅助处理它们。这些内核线程在最低的优先级上运行(nice值是19),这能避免它们跟其他重要的任务抢夺资源,但它们最终肯定会被执行。
12、工作队列是另外一种将工作推后执行的形式,它和我们之前讨论过的所有其他形式都不相同。工作队列可以把工作推后,交由一个内核线程去执行-该 工作总是会在进程上下文执行。如果你需要用一个可以重新调度的实体来执行你的下半部处理,你应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的 机制,也只有它才可以睡眠。这意味着在你需要获得大量的内存时、在你需要获取信号量时,在你需要执行阻塞式的I/O操作时,它都会非常有用。
13、工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务。它创建的这些内核线程被称作工作者 线程。工作队列子系统提供了一个缺省的工作者线程来处理需要推后的工作。不过如果需要在工作者线程中执行大量的处理操作,也可以创建属于自己的工作者线 程。这么做有助于减轻缺省线程的负担,避免工作队列中其他需要完成的工作处于饥饿状态。
14、下半部机制的选择:简单地说,一般的驱动程序编写者需要做两个选择。首先,你是不是需要一个可调度的实体来执行需要推后完成的工作-你有休眠的需要吗?要是有,工作队列就是你的唯一选择。否则最好用tasklet。要是必须专注于性能的提高,那么就考虑软中断吧。