软中断和 Bottom Half
为什么需要 Bottom Half?
背景: 中断服务程序一般是在关闭中断的情况下执行的,以避免嵌套而使控制复杂化。但是,如果长时间不打开中断,又会丢失中断。为此,desc的status中的标记位 SA_INTERRUPT可以控制在执行终端服务子程序的时候是否需要关闭中断。实际的情况是,关闭中断会‘扩大打击面’,打开中断会使情况变复杂。
1) 一般来说,一次中断服务的过程常常可以分为两部分。开头的部分必须要在关闭中断的情况下‘原子地‘,’立即‘得到执行。
2) 后面的部分则没有严格的要求,而且可以合并处理,成为 Bottom Half.
什么是软中断?
为简化bh的设计,Bottom Half也像 do_IRQ 一样被严格的串形化了。 1) bh函数的执行不允许嵌套。,对同一CPU上的嵌套执行加了锁。 2) 在SMP中,在同一个时间内只允许一个CPU执行bh函数。 性能问题: do_IRQ的串形化是针对一个通道的,但是bh没有通道的概念,它的串行化是全局的。 而之前版本的内核的bh是串行化的,为了系统的解决这个问题,增加了一套“软中断softirq”的机制。 “信号”也被称为软中断,那么这几个概念的区别: “硬中断”:是由外设发起的对CPU的中断。 “softirq”:是“硬中断的服务程序”发起的对内核的中断。 “信号”:是由内核或其他进程发起的对其他进程的中断。
softirq机制的初始化
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); }
1) kernel中有4种类型的软中断,bh只是其中的一种。 enum { HI_SOFTIRQ=0, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, TASKLET_SOFTIRQ }; 2) bh_task_vec数组,从注释中也可以看到这是老kernel中的bh数组。而新的kernel在bh机制上层加了“软中断”的机制。 extern struct tasklet_struct bh_task_vec[]; struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }; softirq, tasklet, bh函数 之间的关系是: softirq -> tasklet -> bh函数。 tasklet和softirq的区别:tasklet只能在一个CPU上执行。 tasklet和bh的区别:不同的tasklet可以同时在不同的CPU上执行。 tasklet是在softirq的机制下限制了并发的一种特例。 3) open_softirq 软中断号TASKLET_SOFTIRQ 的服务子程序是 tasklet_action, 软中断号HI_SOFTIRQ 的服务子程序是 tasklet_hi_action.
tasklet_init(bh_task_vec+i, bh_action, i);
一个bh就是一个做为tasklet执行的。只不过抽象出了一个叫做tasklet的东东更加系统的解决了bh的问题。 有32个bh就有32个tasklet与之对应。 bh_action就是bh的函数
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) { t->func = func; t->data = data; t->state = 0; atomic_set(&t->count, 0); }
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL)
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++) softirq_mask(i) |= (1<<nr); spin_unlock_irqrestore(&softirq_mask_lock, flags); }
1) softirq_vec 一个软中断号大小的数组,和硬中断中的 irq_desc作用相同。 static struct softirq_action softirq_vec[32] __cacheline_aligned; struct softirq_action { void (*action)(struct softirq_action *); void *data; }; 2) softirq_mask(i) |= (1<<nr); extern irq_cpustat_t irq_stat[]; #define __IRQ_STAT(cpu, member) (irq_stat[cpu].member) #define softirq_mask(cpu) _IRQ_STAT((cpu), __softirq_mask) 可以看出,softirq_mask就是irq_statp[i].__softirq_mask 。 每个CPU都有自己的中断状态的数据结构 irq_cpustat_t: 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; } ____cacheline_aligned irq_cpustat_t; __softirq_active 相当于软中断请求寄存器。 __softirq_mask 相当于中断屏蔽寄存器。 打开软中断就是注册软中断的服务子程序action,然后在所有CPU的中断状态的 __softirq_mask上置位中断号nr的bit位。
bh服务程序的初始化
softirq_init 只是初始化了bh_task_vec和bh_action的关联, 相当于有了bh的执行机制, 同时也建立了软中断的机制, 相当于有了中断服务队列,中断服务程序还没有挂如队列. 具体的bh函数是通过init_bh来完成. sched_init的片段:
void __init sched_init(void) { int cpu = smp_processor_id(); int nr; init_task.processor = cpu; for(nr = 0; nr < PIDHASH_SZ; nr++) pidhash[nr] = NULL; init_timervecs(); init_bh(TIMER_BH, timer_bh); init_bh(TQUEUE_BH, tqueue_bh); init_bh(IMMEDIATE_BH, immediate_bh); atomic_inc(&init_mm.mm_count); enter_lazy_tlb(&init_mm, current, cpu); }
1) init_bh(TIMER_BH, timer_bh);
初始化了bh向量TIMER_BH的处理函数是 timer_bh.
2) 系统中预定义的bh向量
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
};
init_bh(TIMER_BH, timer_bh)
void init_bh(int nr, void (*routine)(void)) { bh_base[nr] = routine; mb(); }
1) bh_base[nr] = routine; bh的初始化就是设置了bh_base的数组. static void (*bh_base[32])(void);
bh和softirq服务程序的调用
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_hi_vec[cpu].list = t; __cpu_raise_softirq(cpu, HI_SOFTIRQ); local_irq_restore(flags); } } static inline void __cpu_raise_softirq(int cpu, int nr) { softirq_active(cpu) |= (1<<nr); }
1) mark_bh 调用一次bh. 2) test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) 检查是否有嵌套. 3) int cpu = smp_processor_id(); 当前CPU. 4) t->next = tasklet_hi_vec[cpu].list; 把这个要执行的bh所代表的tasklet_struct挂如当前CPU的tasklet_hi_vec[cpu]中,等待被执行. 5) __cpu_raise_softirq(cpu, HI_SOFTIRQ); 产生一次软中断,把Bottom Half对应的软中断号 HI_SOFTIRQ置位. 6) 内核有4个时机会检查是否有软中断到来,需要相应. 从系统调用中返回 ret_from_sys_call() 从异常中返回 ret_from_exception() 调度程序中 schedule() 处理完硬件中断之后 do_IRQ() if (softirq_active(cpu) & softirq_mask(cpu)) do_softirq();
do_softirq软中断的执行
asmlinkage void do_softirq() { int cpu = smp_processor_id(); __u32 active, mask; if (in_interrupt()) return; local_bh_disable(); local_irq_disable(); mask = softirq_mask(cpu); active = softirq_active(cpu) & mask; if (active) { struct softirq_action *h; restart: softirq_active(cpu) &= ~active; local_irq_enable(); h = softirq_vec; mask &= ~active; do { if (active & 1) h->action(h); h++; active >>= 1; } while (active); local_irq_disable(); active = softirq_active(cpu); if ((active &= mask) != 0) goto retry; } local_bh_enable(); return; retry: goto restart; }
1) while(active) 检查哪些软中断寄存器被置位了.调用相应的回调. 这个是HI_SOFTIRQ被置位了,对应的服务程序是tasklet_hi_action
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(); } }
1) int cpu = smp_processor_id(); 当前的CPU. 2) list = tasklet_hi_vec[cpu].list; 取出当前CPU对应的要执行的tasklet链表. 3) while (list != NULL) 遍历链表. 4) if (内核代码阅读(16) _trylock(t)) 注意: 因为tasklet_hi_action 是对软中断进行服务,要确保软中断不能嵌套. 上层的softirq_vec机制保证了同一个tasklet只能在一个CPU上得到执行,不同的tasklet可以在不同的CPU上执行. 这个需要进一步约束bh类型的tasklet的并发性. 5) t->func(t->data); 调用这个tasklet对应的函数指针.在softirq_init中被设置成了bh_action.
bh_action
static void bh_action(unsigned long nr) { int cpu = smp_processor_id(); if (!spin_trylock(&global_bh_lock)) goto resched; if (!hardirq_trylock(cpu)) goto resched_unlock; if (bh_base[nr]) bh_base[nr](); hardirq_endlock(cpu); spin_unlock(&global_bh_lock); return; resched_unlock: spin_unlock(&global_bh_lock); resched: mark_bh(nr); }
1) int cpu = smp_processor_id(); 2) bh_base[nr](); 可以看到bh_action最终执行到了bh_base数组,这个正是在init_bh中设置的 timer_bh.