风雨兼程

ring my bells
posts - 49, comments - 14, trackbacks - 0, articles - 0

下半部和推后执行的工作

Posted on 2008-04-09 22:52 silentneil 阅读(157) 评论(0)  编辑 收藏 引用
1、中断处理程序的局限:

    * 中断处理程序以异步方式执行并且它有可能会打断其他重要代码的执行。因此,它们应该执行得越快越好。
    * 如果当前有一个中断处理程序正在执行,在最好的情况下,与该中断同级的其他中断会被屏蔽,在最坏的情况下,所有其他中断都会被屏蔽。因此,仍应该让它们执行得越快越好。
    * 由于中断处理程序往往需要对硬件进行操作,所以它们通常有很高的时限要求。
    * 中断处理程序不在进程上下文中运行,所以它们不能阻塞。

2、下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。

3、并不存在严格明确的规定来说明到底什么任务应该在哪个部分中完成,如何做决定完全取决于驱动程序开发者自己的判断。对于在上半部和下半部之间划分工作,尽管不存在某种严格的规则,但还是有一些提示可供借鉴:

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

4、和上半部分只能通过中断处理程序实现不同,下半部可以通过多种机制实现。最早的Linux只提供"bottom half"这种机制用于实现下半部,这种机制也被称为"BH"。不久,内核开发者们就引入了任务队列机制来实现工作的推后执行,并用来代替BH机制。目前这两种机制已经在2.5之后的版本中被去除。在2.6版本中,内核提供了三种不同形式的下半部实现机制:软中断、tasklet和工作队列。其实还有另外一个可以用于将工作推后执行的机制是内核定时器。它可以把操作推迟到某个确定的时间段之后执行。也就是说,当你必须保证在一个确定的时间段过去后再运行时,你应该使用内核定时器。

5、软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行-即使两个类型相同也可以。其适合于像网络这样对性能要求非常高的情况。此外,软中断必须在编译期间就进行静态注册。

6、软中断的实现:软中断的代码位于kernel/softirq.c文件中。其由softirq_action结构表示,定义在< linux/interrupt.h>中。在kernel/softirq.c中定义了一个包含有32个该结构体的数组。目前这32个项中只用到6 个。一个软中断不会抢占另外一个软中断。实际上,唯一可以抢占软中断的是中断处理程序。一个注册的软中断必须在被标记后才会执行。这被称作触发软中断。通常,中断处理程序会在返回前标记它的软中断,使其在稍后被执行。在下列地方,待处理的软中断会被检查和执行:

    * 在处理完一个硬件中断以后。
    * 在ksoftirqd内核线程中。
    * 在那些显式检查和执行待处理的软中断的代码中,如网络子系统中。

不管用什么办法唤起,软中断都要在do_softirq()中执行。该函数很简单,如果有待处理的软中断,do_softirq()就会循环遍历每一个,调用它们的处理程序。

7、使用软中断:软中断保留给系统中对时间要求最严格以及最重要的下半部使用。目前只有两个子系统-网络和SCSI直接使用软中断。此外,内核定时器和tasklets都是建立在软中断上的。对于时间要求严格并能自己高效地完成加锁工作的应用,软中断会是正确的选择。

   1)分配索引

   在编译期间,通过<linux/interrupt.h>中定义的一个枚举类型来静态地声明软中断。内核用这些从0开始的索引来表示一种相对优先级。索引号小的软中断在索引号大的软中断之前执行。建立一个新的软中断必须在此枚举类型中加入新的项。而加入时,不能像在其他地方一样,简单地把新项加到列表的末尾。相反,你必须根据你希望赋予它的优先级来决定加入的位置。

   2)注册你的处理程序

   接着,在运行时通过调用open_softirq()注册软中断处理程序。软中断处理程序执行的时候,允许响应中断,但它自己不能休眠。在一个处理程序运行的时候,当前处理器上的软中断被禁止。但其他处理器仍可以执行别的软中断。实际上,如果一个软中断在它被执行的同时再次被触发了,那么另外一个处理器可以同时运行其处理程序。这意味着任何共享数据-甚至是仅在软中断处理程序内部使用的全局变量都需要严格的锁保护。如果仅仅通过互斥的加锁方式来防止它自身的并发执行,那么使用软中断就没有任何意义。因此大部分软中断处理程序都通过采取单处理器数据或其他一些技巧来避免显式地加锁,从而提供更出色的性能。

   3)触发你的软中断

   raise_softirq()函数可以将一个软中断设置为挂起状态,让它在下次调用do_softirq()函数时投入运行。该函数在触发一个软中断之前先要禁止中断,触发后再恢复回原来的状态。如果中断已经被禁止了,那可以调用另一函数raise_softirq_irqoff(),这会带来一些优化效果。在中断处理程序中触发软中断是最常见的形式。在这种情况下,中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出。内核在执行完中断处理程序以后,马上就会调用do_softirq()函数。于是软中断开始执行中断处理程序留给它去完成的剩余任务。

8、Tasklets是利用软中断实现的一种下半部机制。它和进程没有任何关系。Tasklets和软中断在本质上很相似,行为表现也相近,但是它的接口更简单,锁保护也要求较低。选择到底是用软中断还是tasklets其实很简单:通常你应该用tasklets,软中断的使用者屈指可数。它只在那些执行频率很高和连续性要求很高的情况下才需要。

9、tasklet是通过软中断实现的,所以它们本身也是软中断。Tasklets由tasklet_struct结构表示。每个结构体单独代表一个tasklet,其定义在<linux/interrupt.h>中。已调度的tasklet(等同于被触发的软中断)存放在两个单处理器数据结构:tasklet_vec(普通tasklet)和tasklet_hi_vec(高优先级tasklet)中。这两个数据结构都是由 tasklet_struct结构体构成的链表。Tasklets由tasklet_schedule()和tasklet_hi_schedule() 函数进行调度。

10、使用Tasklets

   1)声明自己的Tasklet

   既可以使用<linux/interrupt.h>中定义的两个宏中的一个DECLARE_TASKLET或 DECLARE_TASKLET_DISABLED来静态创建tasklet,前者把创建的tasklet的引用计数器设置为0,该tasklet处于激活状态。另一个把引入计数器设为1,所以该tasklet处于禁止状态。还可以使用tasklet_init()动态创建一个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。要是必须专注于性能的提高,那么就考虑软中断吧。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理