通常下半部分在中断处理程序一返回就会马上运行,下半部分执行的关键是,它们运行时允许中断。
如果我们需要在一段时间后允许,我们可以使用内核定时器实现下半部分的任务。
现在能用的有软中断,tasklet,工作队列。
软中断使用较少(相对于tasklet),但是tasklet是基于软中断实现的。
软中断是在编译期间静态分配的,不像tasklet那样能动态地注册或者注销。
内核可以注册32个软中断,而当前内核版本只注册了9个。
软中断结构体:
struct softirq_action{
void (*action)(struct softirq_action*);
};
内核中的全局变量:
static struct softirq_action softirq_vec[NR_SOFTIRQS];//32个软中断
软中断处理函数action函数原型:
void softirq_handler(struct softirq_action *);
当只有一个处理器上,一个软中断不会抢占另一个软中断,唯一可以抢占软中断的是中断处理程序。不过,软中断可以在不同处理器上执行。
一个注册的软中断只有在标记后才会执行,中断处理程序会在返回前标记它的软中断,使其在稍后执行。
在下列地方,会遍历寻找待处理的软中断并且执行:
具体代码
u32 pending;
pending=local_softirq_pending();
if(pending){
struct softirq_action *h;
set_softirq_pending(0);
h=softirq_vec;
do{
if(pending & 1)
h->action(h);
h++;
pending>>=1;
}while(pending);
}
软中断保留给系统中对时间最敏感最严格的下半部分使用。目前,只有两个子系统(网络和SCSI)直接使用软中断。此外,内核定时器和tasklet都是建立在软中断上的。相比之下,tasklet可以动态生成,使用更方便。
软中断有32位,因为do_softirq()是从下到大的遍历位图,所以小的索引会在大的索引之前执行,索引具有优先级。
tasklet | 优先级 | 软中断描述 |
---|---|---|
HI_SOFTIRQ | 0 | 优先级高的tasklet |
TIMER_SOFTIRQ | 1 | 定时器的下半部 |
NET_TX_SOFTIRQ | 2 | 发送网络数据包 |
NET_RX_SOFTIRQ | 3 | 接收网络数据包 |
BLOCK_SOFTIRQ | 4 | BLOCK装置 |
TASKLET_SOFTIRQ | 5 | 正常优先级的tasklet |
SCHED_SOFTIRQ | 6 | 调度程序 |
HRTIMER_SOFTIRQ | 7 | 高分辨率定时器 |
RCU_SOFTIRQ | 8 | RCU锁定 |
open_softirq(NET_TX_SOFTIRQ,net_t_action);//网络子系统注册自己的软中断处理函数。
有两个参数,第一个是索引号,第二个是处理函数。
raise_softirq(NET_TX_SOFTIRQ);//将中断号为2的网络发送标志设置为1
在do_softirq()中会触发其函数net_t_action();
只在高频率使用软中断,大多数情况使用tasklet。
tasklet本质上也是软中断,只不过同一个处理程序的多个实例不能在多处理器上同时运行。所以软中断需要考虑多个处理器同时运行软中断执行函数。而tasklet不需要考虑并发问题。
struct tasklet_struct {
struct tasklet_struct *next; //链表下一个
unsigned long state; //状态
atomic_t count; //引用计数
void (*func)(unsigned long); //核心处理函数
unsigned long data; //给func的参数
}
state状态有:0,TASKLET_STATE_SCHED(已被调度),TASKLET_STATE_RUN(正在运行,只在多处理器上使用)
每个tasklet都存放在两个链表中的其一: tasklet_vec(普通tasklet),tasklet_hi_vec(高优先级tasklet).
静态创建:
DECLARE_TASKLET(tasklet_name,tasklet_func,tasklet_data);
tasklet不能睡眠,所以不能使用阻塞,信号量等。
两个相同的tasklet绝不会在多个处理器上同时执行,这点和软中断不同
tasklet_schedule()执行步骤:
do_softirq()检测到位图的TASKLET_SOFTIRQ为1,则执行tasklet_action().
tasklet_action():
当需要睡眠时,那么就使用工作队列。
将工作推后,交由一个内核进程去执行(在进程上下文中执行)。