2010年(49)
分类: 嵌入式
2010-09-07 15:59:34
中断处理程序分为上半部和下半部。上半部的接口是唯一的,但是下半部实现的方法有很多。下半部(bottom half)实现的方法称为下半部机制。下述表格摘自《linux内核设计与实现》,可以清楚的说明下半部机制的状态。
下半部机制 |
状态 |
BH |
在2.5中去除 |
任务队列(task queue) |
在2.5中去除 |
软中断(softriq) |
从2.3中引入 |
Tasklet |
从2.3中引入 |
工作队列(work queue) |
从2.5中引入 |
对我们来说,最重要的是软中断和tasklet。
这里的软中断和系统调用里面提到的“软件中断”是截然不同的。“软件中断”实际是仍然是CPU的硬件中断,只是该中断由软件来“触发”而已;但是软中断仅仅是软件上的一种机制。
软中断是在编译期间静态分配的。在kernel/softirq.c中定义了如下软中断:
static struct softirq_action softirq_vec[NR_SOFTIRQS]
软中断最多允许32个,但是在
类比于硬件中断,每个软中断都有自己的处理函数。softirq_action结构(
struct softirq_action
{
void (*action)(struct softirq_action *);
};
其中softirq_action函数指针就指向该软中断的“中断处理函数”。
当上半部执行结束,应该使能对应的软中断;这样当软中断被调度时(do_softirq()),会对所有的软中断遍历,并执行使能的软件断处理函数。
按照上半部和下半部理论,每个硬件中断都应该对应一个软中断,这样可以保证每一个上半部(硬件中断处理函数)都能对应一个下半部(软件中断处理函数)。但是问题随之而来:CPU的硬件中断数量有可能超过32。于是,软中断的是使用分成了两个类别。
看一下
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
这些软中断从0开始索引来表示一种相对优先级。索引号小的优先级最高。
第一类软中断如NET_TX_SOFTIRQ和NET_RX_SOFTIRQ,对应于网络系统,其处理速度要求很高,那么这两个软中断对应的中断处理函数就唯一用于处理网络packet的收或发处理;
第二类软中断如HI_SOFTIRQ 和TASKLET_SOFTIRQ软中断,由未在软中断列出的其他硬件中断共享,其软中断处理函数是tasklet_hi_action()和tasklet_action()函数。两者的唯一区别是其软中断优先级不同而已。这两个软中断处理函数是tasklet机制的核心,它会分别遍历tasklet_hi_vec和tasklet_vec链表,执行其中的所有允许执行的tasklet。
由动态链表tasklet_hi_vec和tasklet_vec构成,其数据结构定义在linux/interrupt.h:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
State有两种(
enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};
Count是tasklet的计数器。如果它不为0,则tasklet被禁止,不允许执行;只有它为0时,tasklet才被激活,并且在state为TASKLET_STATE_SCHED时,该tasklet才能被执行。
Func就是tasklet对应得执行函数。
共分为三个步骤,分别为声明、激活、调度。
(1)声明tasklet
声明一个tasklet,其实就是定义一个tasklet_struct结构。可以选择静态或动态的创建一个tasklet。
静态创建可以使用linux/interrupt.h中定义的两个宏中的一个:
#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 }
这两个宏都会静态的创建一个tasklet_struct结构。区别在于引用计数器的初始值设置不同。前面一个设置计数器为0,该tasklet处于激活状态。另一个则把引用计数器设为1,tasklet处于禁止状态。
动态创建时,可以使用kernel/softirq中的tasklet_init函数:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}
(2)激活或禁止tasklet
针对于引用计数器,定义在linux/interrupt.h中:
static inline void tasklet_enable(struct tasklet_struct *t)
static inline void tasklet_disable(struct tasklet_struct *t)
如果声明tasklet时就已经处于激活状态,那么就不需要重新激活了。
(3)调度tasklet
由在软中断中的分析可知,tasklet的执行依靠对tasklet_hi_vec和tasklet_vec链表的遍历。如何将我们自己声明的tasklet加入这两个链表呢?需要用户调度自己的tasklet。已经调度的tasklet就存放在tasklet_hi_vec或tasklet_vec链表中。
tasklet_hi_schedule()和tasklet_schedule函数接受一个tasklet_struct指针作为参数,对该tasklet进行调度,如果调度成功,则该tasklet就会存到tasklet_hi_vec或tasklet_vec链表。
4 总结
软中断是一种下半部执行的机制,而tasklet是基于软中断实现的。增加自己的软中断是比较繁琐的,需要自己修改linux源码,比较繁琐。而Tasklet已经能够满足基本的下半部需求,所以基本上都使用tasklet机制。