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); }
open_softirq函数如下:
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[];
2、初始化bh
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();
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); }
如果t->func(t->data) 不是bh_action(0),也就不是bh函数,那么tasklets机制满足:
1、某一段tasklet代码在某个时刻只能在一个CPU上运行。
2、不同的tasklet代码在同一时刻可以在多个CPU上并发地执行。
如果t->func(t->data) 是bh_action(0),也就是bh函数,那么必须像BH机制那样,严格地串行化执行(也即在同一时刻系统中只能有一个CPU执行BH函数)。
所以BH是通过Tasklet机制融入软中断框架的。