内核将整个的中断处理流程分为了上半部和下半部。上半部就是之前所说的中断处理函数,它能最快的响应中断,并且做一些必须在中断响应后马上要做的事,而一些需要在处理函数后继续执行的操作,内核建议把它放在下半部执行。
三种方法可以实现下半部:软中断、tasklet和工作队列。
一、软中断
软中断即软件实现的异步中断,它的优先级比硬件中断低,但比普通进程优先级高,它和硬中断一样不能休眠。软中断很少用于实现下半部,但tasklet是通过软中断实现的。
软中断是在编译的时候静态分配的,要使用软中断必须修改内核代码。软中断实现下半部的方法很麻烦,而且很少使用,在此就不进行分析了。
二、tasklet
1、内核通过tasklet_struct来维护一个tasklet:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long); //tasklet处理函数
unsigned long data; //传给处理函数的参数
};
在初始化tasklet_struct之前,需要先定义好tasklet处理函数,如果需要传参,也需要指定参数,可以直接传数据,也可以传地址。如:
void heql_func(unsigned long data)
{
......
}
2、可以通过两种方式定义和初始化tasklet_struct
(1)静态初定义并初始化
#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }
这两个函数都是定义一个名字为name的tasklet_struct,并指定它的处理函数和传参分别为func和data。区别是:DECLARE_TASKLET_DISABLED初始化后的处于禁止状态,暂时不能被调用。
(2)动态初始化
先定义结构体,然后把结构体指针传给tasklet_init来初始化
/*kernel/softirq.c*/
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
如:
struct tasklet_struct heql_tasklet; //定义结构体
tasklet_init(&heql_tasklet, heql_func, (unsigned long)666);
效果相当于静态初始化:
DECLEAR_TASKLET(heql_tasklet, heql_func, (unsigned long)666);
3、在中断返回前调度tasklet
调度使用函数tasklet_schedule或者tasklet_hi_schedule,区别是:一个使用TASKLET_SOFTIRQ,另一个使用HI_SOFTIRQ。
static inline void tasklet_schedule(struct tasklet_struct *t)
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
如:
tasklet_schedule(&heql_tasklet);
4、模块卸载时,将tasklet_struct结构体移除
void tasklet_kill(struct tasklet_struct *t)
当一个设备正被关闭或者模块卸载时被调用,如果tasklet正在运行,程序会休眠,知道它执行完毕。
5、禁止与激活tasklet
被禁止的tasklet不能被调用,直到被激活。
static inline void tasklet_disable(struct tasklet_struct *t)
static inline void tasklet_enable(struct tasklet_struct *t)
以上介绍了tasklet机制来实现中断上下半部。
优点:运行在软中断上下文,优先级比普通进程高,调度速度快。
缺点:由于处于中断上下文,所以不能睡眠。