中断下半部分(读书笔记)

January 15th, 2014 by JasonLe's Tech Leave a reply »

中断处理分为两个部分:上半部和下半部。中断处理程序属于上半部.
下半部的任务就是执行与中断处理程序密切相关但中断处理程序本身不执行,推后执行的工作。
对于一个工作是放在上半部还是放在下半部去执行,可以参考下面四条:
1)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
2)如果一个任务和硬件相关,将其放在中断处理程序中执行。
3)如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
4)其他所有任务,考虑放在下半部去执行。

上半部和下半部在对中断的影响上的区别:
上半部:屏蔽在所有处理器上的当前对应的中断线。如果指定了SA_INTERRUPT,则禁止所有本地中断。
下半部:允许响应所有中断。
下半部执行的时间:注意,下半部执行的时间强调只要不是‘马上’就可以,并不需要指明一个确切时间,只要把这些任务推迟一点,让它们在系统不太繁忙并且中断恢复后执行就可以了。下半部执行的关键在于当他们运行的时候,可以响应所有中断。通常下半部在中断处理程序一返回就会马上运行。
上半部和下半部的实现机制:
上半部:只能用中断处理程序执行。
下半部:(2.6)软中断,tasklet和工作队列。
另外一个可以用于将工作推后执行的机制是内核定时器。也就是说,如果想把任务推后执行,有两种方法可供选择:
1)放到下半部:软中断,tasklet和工作队列。这种方法对时间的要求不确定,只要不是现在就行。
2)内核定时器:推后到确定的时间后执行。

软中断用的比较少,而tasklet是下半部更常用的一种形式,但tasklet是通过软中断来实现的,
软中断是在编译期间静态分配的,而tasklet能被动态的注册或者去除。软中断由softirq_action结构表示,定义在<linux/interrupt.h>中。

struct softirq_action{
        void (*action)(struct softirq_action*);/*待执行的函数*/
};

不管是用什么办法唤起,软中断都要在do_softirq()中执行。如果有待处理的软中断,则该函数会遍历每一个softirq_action,然后调用相关的处理函数。
函数的核心部分:kernel/softirq.c

u 32 pending= softirq_pending(cpu);/*返回待处理的软中断的32位位图*/

if(pending){
    struct softirq_action *h = softirq_vec;

    softirq_pending(cpu)= 0; /*因为所有被标记的软中断将被处理,所以将位图清0*/

    do{
            if(pending& 1)
                h->action(h);

            h++;
            pending >>= 1;/*位掩码右移一位*/
        }while(pending);
}

目前内核中只有两个子系统直接用到了软中断:网络和SCSI。对于时间要求严格并能自己高效完成加锁工作的应用,软中断会是正确的选择。
tasklet是通过软中断实现的,所以本质上来说也是软中断。tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SFOTIRQ,唯一区别是前者的优先级高些,先执行。
1,内核中tasklet的表示。
tasklet由tasklet_struct结构体表示,在<linux/interrupt.h>中定义。

struct tasklet_struct{
    struct tasklet_struct *next;
    unsigned long state;/*tasklet的状态*/
    atomic_t count;/*引用计数器*/
    void(*func)(unsignedlong);/*tasklet处理函数*/
    unsigned long data;/*给tasklet处理函数传递的参数*/
};

其中参数的意义如下:
state:只能是0,TASKLET_STATE_SCHEDHE和TASKLET_STATE_RUN。
count为引用计数,只有为0的时候tasklet才可被激活,并且在被设置了挂起状态以后,该tasklet才能够执行。(激活—->挂起—->被调用)
func为tasklet的处理函数。
2,内核中tasklet的调度
已调度的tasklet(等同于被触发的软中断)存放在两个数据结构(每个处理器一个)中:tasklet_vec(普通tasklet)和tasklet_hi_vec(高优先级的tasklet)中。然后tasklet由tasklet_schedule()和tasklet_hi_schedule()进行调度。
由于tasklet本质上也是软中断,所以当执行do_softirq()的时候,因为TASKLET_SOFTIRQ和HI_SOFTIRQ已经被设置了,所以do_softirq()会执行相应的软中断处理程序:tasklet_action()和tasklet_hi_action()。这两个函数会检索tasklet_vec和tasklet_hi_vec,然后处理每一个待处理的tasklet。即调用其相关的tasklet处理函数。
注意:同一时刻,相同类型的tasklet只有一个被执行,而不同类型的tasklet可同时执行。
3,使用tasklet
下面来看看如何使用自己定义的tasklet。
1)声明自己的tasklet。
tasklet即可以静态创建也可以动态创建,选择哪种方式取决于你想要的是一个tasklet的直接引用还是间接引用。
静态声明可以用如下两个宏:
DECLARE_TASKET(name, func, data)或DECLARE_TASKLET_DISABLED(name, func, data)
两个宏的区别:前面的宏创建的tasklet引用计数器设置为0,tasklet处于激活状态。而后面的创建的tasklet的引用计数器设置为1,所以该tasklet处于禁止状态。
动态声明:
tasklet_init(my_tasklet, tasklet_handler, dev);
2)编写自己tasklet的处理函数
tasklet处理函数必须符合以下格式:
void tasklet_handler(unsigned long data)
注意:因为是靠软中断实现,即还处在中断上下文,所以tasklet不能睡眠。这意味着你不能在tasklet中使用信号量或者其他什么阻塞式的函数。但是tasklet运行时允许相应中断。
3)调度自己的tasklet
通过调用tasklet_schedule()函数并传递给它相应的tasklet_struct指针,该tasklet就会被调度以便执行。
tasklet_schedule(&my_tasklet); /*把my_tasklet标记为挂起*/
因为在一开始声明初始化的时候就已经处于激活状态了,但是必须得标记为挂起状态后才能被执行。经过这步后,tasklet被挂起,之后有机会得时候,它就会被执行了。
同样,也可以用函数tasklet_disable()来禁止某个指定的tasklet。tasklet_enable()函数可以激活一个tasklet。也可以通过tasklet_kill()函数从挂起的队列中去掉一个tasklet。