浅谈linux2.4 内核中断下半部分(softirq机制)
linux2.4 内核中断下半部分(bottom half)理解(请结合linux2.4内核代码看):
首先先说一下为什么要采用中断下半部分:
中断服务函数大多需要在关中断的情况下去执行,但是有的中断服务函数执行需要较长的时间,如果系统长时间关中断就会让其他的中断得不到响应,所以把需要关中断执行的部分放到中断的上部分,而把对实时性要求不高,不需要关中断执行的操作放到中断下半部分中去。
但是中断下半部分也不能够嵌套执行,否则会复杂化代码实现,所以需要有个实现中断下半部分串行化的机制,即bh机制,bh机制的串行化有两方面:
一是bh函数不能嵌套,二是在多cpu系统中在同一时间只允许一个cpu执行bh函数
由于第二点使bh函数的执行完全串行化了,当有多个cpu的时候,只能一个cpu执行,降低的系统性能。但是如果放宽条件以前的bh函数就不能用了,所以比较好的办法是保留bh,另外在增设一种或几种机制,并把它们纳入统一的框架中,这就是本文要讲的软中断机制。
void __init softirq_init()
{
int i;
/*在这个循环中将bh_task_vec[32]中的.action指针全部初始化为bh_action函数,然后在init_bh()中将bh_action函数里要执行的bh_base[]函数指针数组初始化为具体的函 *数,由数组下标做索引。*/
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
/*在open_softirq函数中将全局数组变量softirq_vec[].action指针初始化为tasklet_action和tasklet_hi_action函数,数组下标为TASKLET_SOFTIRQ和HI_SOFTIRQ*/
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
那么我们注册的这些函数是在什么时候调用的呢,换句话说,中断下部分是在什么时候被执行的呢。
在linux2.4内核中在中断执行函数do_IRQ和系统调用返回前会判断系统当前有没有软中断在申请执行,下面是do_IRQ和arch/i386/kernel/entry.S的代码片段:
if (softirq_active(cpu) & softirq_mask(cpu))
do_softirq();
+++++++++++++++++++++++++++++++++++++++
ENTRY(ret_from_sys_call)
#ifdef CONFIG_SMP
movl processor(%ebx),%eax
shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
movl SYMBOL_NAME(irq_stat)(,%eax),%ecx# softirq_active
testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx# softirq_mask
#else
movl SYMBOL_NAME(irq_stat),%ecx# softirq_active
testl SYMBOL_NAME(irq_stat)+4,%ecx# softirq_mask
#endif
jne handle_softirq
handle_softirq:
call SYMBOL_NAME(do_softirq)
jmp ret_from_intr
看到红色标记的函数了吧,对了,就是执行了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:
/* Reset active bitmask before enabling irqs */
softirq_active(cpu) &= ~active;
local_irq_enable();
/*将数组softirq_vec的指针赋给h,然后执行action指针指向的函数,这个action指向的函数是我们在前面open_softirq函数中赋值的*/
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();
/* 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;
}
那么,既然do_IRQ里面执行的是softirq_vec数组里的函数,即要么是tasklet_action()要么是tasklet_hi_action(),但和我么提到的bh_task_vec数组函数指针又有什么关系呢要明白这个我们还得再介绍两个函数即:
static inline void tasklet_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_vec[cpu].list;
tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
}
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;//注意这两句赋值的意思是将 t 插入到原来的链表头前,同时把链表头指针指向 t ,使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_vec[NR_CPUS];
extern struct tasklet_head tasklet_hi_vec[NR_CPUS];
这两个函数的函数都是struct tasklet_struct *t。
tasklet_hi_schedule()中将参数t 链接到tasklet_hi_vec数组元素中的链表头中,这个函数是有bh_mark函数调用的,实现如下:
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
即把bh_task_vec数组里的第nr个函数指针挂载到tasklet_hi_vec链表头中。
而tasklet_schedule函数是在其它软中断的函数中被调用的,如sun_kbd_init--->tasklet_schedule(&keyboard_tasklet);即把要注册的tasklet_struct结构体挂载到tasklet_vec链表中。
注意:bh函数执行的上下文是完全串行化的,而其它软中断函数只是在非中断上下文中执行(即软中断函数和中断函数中)。
好了,基础知识已经讲完了,现在来讲在do_softirq函数中执行的tasklet_action或者tasklet_hi_action函数。这两个函数非常相近,除了我在下面标记的红色部分不一样以外:
static void tasklet_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_vec[cpu].list;
tasklet_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);
/*
* talklet_trylock() uses test_and_set_bit that imply
* an mb when it returns zero, thus we need the explicit
* mb only here: while closing the critical section.
*/
#ifdef CONFIG_SMP
smp_mb__before_clear_bit();
#endif
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_enable();
}
}
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_hi_vec和tasklet_vec,所以tasklet_action和tasklet_hi_action其实执行的就是tasklet_hi_vec和tasklet_vec链表里面的函数了,而这两个链表里面的函数就是我们最前面注册的中断下半部分的函数,即bh函数和其他软中断函数,这样通过softirq机制就把以前老机制的bh函数和新机制的软中断函数给统一到一个框架里面去了。