Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思,这里是指一小段可执行的代码,且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。
从某种程度上讲,tasklet机制是Linux内核对BH机制的一种扩展。在2.4内核引入了softirq机制后,原有的BH机制正是通过tasklet机制这个桥梁来纳入softirq机制的整体框架中的。正是由于这种历史的延伸关系,使得tasklet机制与一般意义上的软中断有所不同,而呈现出以下两个显著的特点:
1. 与一般的软中断不同,某一段tasklet代码在某个时刻只能在一个CPU上运行,而不像一般的软中断服务函数(即softirq_action结构中的action函数指针)那样——在同一时刻可以被多个CPU并发地执行。
2. 与BH机制不同,不同的tasklet代码在同一时刻可以在多个CPU上并发地执行,而不像BH机制那样必须严格地串行化执行(也即在同一时刻系统中只能有一个CPU执行BH函数)。
软中断是中断下半部总体的说法,Tasklet机制是软中断的特殊例子,软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。 最后BH是通过Tasklet机制融入软中断框架的。
一、软中断的初始化
1、初始化Tasklet机制
void __init softirq_init()
{
int i;
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
struct tasklet_struct bh_task_vec[32];
enum
{
HI_SOFTIRQ=0,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
TASKLET_SOFTIRQ
};
tasklet_init函数如下:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->func = func;
t->data = data;//0~32
t->state = 0;
atomic_set(&t->count, 0);
}
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
unsigned long flags;
int i;
spin_lock_irqsave(&softirq_mask_lock, flags);
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
for (i=0; i<NR_CPUS; i++)//NR_CPUS为1
softirq_mask(i) |= (1<<nr);//irq_stat[0].__softirq_mask第1位和第4位都置1
spin_unlock_irqrestore(&softirq_mask_lock, flags);
}
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
};
static struct softirq_action softirq_vec[32] __cacheline_aligned;
#define softirq_mask(cpu) __IRQ_STAT((cpu), __softirq_mask)
#define __IRQ_STAT(cpu, member) ((void)(cpu), irq_stat[0].member)
typedef struct {
unsigned int __softirq_active;
unsigned int __softirq_mask;
unsigned int __local_irq_count;
unsigned int __local_bh_count;
unsigned int __syscall_count;
unsigned int __nmi_count; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
extern irq_cpustat_t irq_stat[];
void __init sched_init(void)
{
/*
* We have to do a little magic to get the first
* process right in SMP mode.
*/
......
init_bh(TIMER_BH, timer_bh);
init_bh(TQUEUE_BH, tqueue_bh);
init_bh(IMMEDIATE_BH, immediate_bh);
/*
* The boot idle thread does lazy MMU switching as well:
*/
atomic_inc(&init_mm.mm_count);
enter_lazy_tlb(&init_mm, current, cpu);
}
enum {
TIMER_BH = 0,
TQUEUE_BH,
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
SCSI_BH,
IMMEDIATE_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};
void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}
二、将tasklet_struct结构的(bh_task_vec+0)链入tasklet_hi_vec[0]
在时钟处理函数中,do_timer中执行了mark_bh(TIMER_BH),代码如下:
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
t->next = tasklet_hi_vec[cpu].list;//将tasklet_struct结构的(bh_task_vec+0)链入tasklet_hi_vec[0]
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_restore(flags);
}
}
struct tasklet_head
{
struct tasklet_struct *list;
} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));
extern struct tasklet_head tasklet_hi_vec[NR_CPUS];//NR_CPUS为0
static inline void __cpu_raise_softirq(int cpu, int nr)
{
softirq_active(cpu) |= (1<<nr);//irq_stat[0].__softirq_active第1位置1
}
三、软中断响应
内核每当在do_IRQ()中执行完一个通道中的中断服务程序以后,以及每当从系统调用返回时,都要检查是否有软中断请求在等待执行。
if (softirq_active(cpu) & softirq_mask(cpu))//irq_stat[0].__softirq_active第1位置1 & irq_stat[0].__softirq_mask第1位和第4位都置1 结果不为0
do_softirq();
asmlinkage void do_softirq()
{
int cpu = smp_processor_id();
__u32 active, mask;
if (in_interrupt())//如果已经增加了irq_stat[0].__local_bh_count计数,还没有减去,那么直接返回,也就是说
return;
local_bh_disable();//增加irq_stat[0].__local_bh_count计数
local_irq_disable();//cli关中断
mask = softirq_mask(cpu);
active = softirq_active(cpu) & mask;
if (active) {
struct softirq_action *h;
restart:
/* Reset active bitmask before enabling irqs */
softirq_active(cpu) &= ~active;
local_irq_enable();//sti开中断
h = softirq_vec;
mask &= ~active;
do {
if (active & 1)
h->action(h);//执行tasklet_hi_action
h++;
active >>= 1;
} while (active);
local_irq_disable();
active = softirq_active(cpu);
if ((active &= mask) != 0)
goto retry;
}
local_bh_enable();////减少irq_stat[0].__local_bh_count计数
/* Leave with locally disabled hard irqs. It is critical to close
* window for infinite recursion, while we help local bh count,
* it protected us. Now we are defenceless.
*/
return;
retry:
goto restart;
}
#define local_bh_disable() cpu_bh_disable(smp_processor_id())
#define cpu_bh_disable(cpu) do { local_bh_count(cpu)++; barrier(); } while (0)
#define local_bh_count(cpu) __IRQ_STAT((cpu), __local_bh_count)
#define __IRQ_STAT(cpu, member) ((void)(cpu), irq_stat[0].member)
#define in_interrupt() ({ int __cpu = smp_processor_id(); \
(local_irq_count(__cpu) + local_bh_count(__cpu) != 0); })
如果do_softirq执行的过程中,发生了中断,在同一个cpu上,又执行了do_softirq,此时在in_interrupt时,会立即返回。所以防止了tasklet机制执行程序的嵌套。 tasklet_hi_action,函数如下:
static void tasklet_hi_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = NULL;
local_irq_enable();
while (list != NULL) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (atomic_read(&t->count) == 0) {
clear_bit(TASKLET_STATE_SCHED, &t->state);
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_enable();
}
}
刚刚将tasklet_struct结构的(bh_task_vec+0)链入tasklet_hi_vec[0],这里取出来,执行t->func(t->data),也就是bh_action(0),代码如下:
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();
if (!spin_trylock(&global_bh_lock))//全局锁,同一时刻系统中只能有一个CPU执行BH函数
goto resched;
if (!hardirq_trylock(cpu))
goto resched_unlock;
if (bh_base[nr])
bh_base[nr]();//最后执行的是bh_base[0],也就是timer_bh
hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;
resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}
1、某一段tasklet代码在某个时刻只能在一个CPU上运行。
2、不同的tasklet代码在同一时刻可以在多个CPU上并发地执行。
如果t->func(t->data) 是bh_action(0),也就是bh函数,那么必须像BH机制那样,严格地串行化执行(也即在同一时刻系统中只能有一个CPU执行BH函数)。
所以BH是通过Tasklet机制融入软中断框架的。