Linux并不是一次性把中断所要求的事情全部做完,而是分两部分来做,下面具体描述内核如何处理中断的下半部。
一、为什么把中断分为两部分来处理
一般都是在中断请求关闭的条件下执行中断服务程序,以避免嵌套使中断控制复杂化。但是,中断是一个随机事件,随时会到来,如果关中断的时间太长,CPU不能响应其它的中断请求,会造成中断丢失。内核的目标是尽可能快地处理完中断请求,把更多的处理向后推迟。例如,假设一个数据块已到达网线,当中断控制器接收到这个中断请求信号时,Linux内核只是简单地标志数据到来,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行。因此,内核把中断处理分两部分:上半部和下半部,上半部(中断服务程序)内核立即执行,下半部(就是一些内核函数)留着稍后处理,如图5.6所示。
首先,用一个快速的上半部来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分的工作很少。
下半部运行时允许中断请求的,而上半部运行时是关中断的,这是二者之间的区别。
但内核到底什么时候执行下半部,以什么方式组织下半部?这是要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做下半部,在2.4以后的版本中有新的发展和改进,改进的目标使下半部在多处理器上并发执行,有助于驱动程序的开发者进行驱动程序的开发,这种执行机制叫软中断机制。软中断中常用的小任务(Tasklet)机制及2.6版本内核中的工作队列机制。
二、小任务机制
小任务是指对要推迟执行的函数进行组织的一种机制。数据结构为tasklet_struct,每个结构代表一个独立的小任务,定义如下:
struct tasklet_struct
{
struct tasklet_struct *next; /* 指向链表中的下一个元素 */
unsigned long state; /* 小任务的状态 */
atomic_t count; /* 引用计数器 */
void (*func)(unsigned long); /* 要调用的函数 */
unsigned long data; /* 传递给函数的参数 */
};
func域就是下半部中要推迟执行的函数,data是它唯一的参数。
state域的取值为TASKLET_STATE_SCHED或TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示小任务已被调度,正准备投入运行,TASKLET_STATE_RUN表示小任务正在运行。TASKLET_STATE_RUN只有在多处理器系统上才使用,任何时候单处理器系统都清楚一个小任务是不是正在运行。
count域是小任务的引用计数器。如果它不为0,则小任务被禁止,不允许执行;只有当它为0,小任务才被激活,并且在被设置为挂起时,小任务才能够执行。
1、声明和使用小任务
大多数情况下,为了控制一个寻常的硬件设备,小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来比较快。
小任务可以静态创建,也可以动态创建。选择哪种方式取决于是到对小任务直接引用还是间接引用。如果静态创建一个小任务(也就是对它直接引用),可以使用如下两个宏中的任意一个:
#define DECLARE_TASKLET(name, func, data)
#define DECLARE_TASKLET_DISABLED(name, func, data)
这两个宏都能根据给定的名字静态创建一个tasklet_struct结构。当该小任务被调度后,给定的函数func被执行,其参数由data给出。这两个宏的区别在于引用计数器的初始值设置不同。DECLARE_TASKLET宏把创建的小任务的引用计数器设置为0,处于激活状态。
DECLARE_TASKLET_DISABLED宏把创建的小任务的引用计数器设置为1,处于禁止状态。例如:DECLARE_TASKLET(tasklet, taskl