1.Bh的处理
当需要执行一个特定的bh函数(例如bh_base[TIMER_BH]())时,首先要提出请求,这是由mark_bh()函数完成的(在Interrupt.h中):
static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}
从上面的介绍我们已经知道,bh_task_vec[]每个元素为tasklet_struct结构,函数的指针func指向bh_action()。
接下来,我们来看tasklet_hi_schedule()完成什么功能:
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);
}
其中smp_processor_id()返回当前进程所在的CPU号,然后以此为下标从tasklet_hi_vec[]中找到该CPU的队列头,把参数t所指向的tasklet_struct结构链入这个队列。由此可见,当某个bh函数被请求执行时,当前进程在哪个CPU上,就把这个bh函数“调度”到哪个CPU上执行。另一方面,tasklet_struct代表着将要对bh函数的一次执行,在同一时间内,只能把它链入一个队列中,而不可能同时出现在多个队列中。对同一个tasklet_struct结构,如果已经对其调用了tasklet_hi_schedule()函数,而尚未得到执行,就不允许再将其链入该队列,所以标志位TASKLET_STATE_SCHED就是保证这一点的。最后,通过cpu_raise_softirq()发出软中断请求,其中的参数HI_SOFTIRQ表示bh与HI_SOFTIRQ软中断对应。
软中断HI_SOFTIRQ的服务例程为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) {
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t)) {
if (!atomic_read(&t->count)) {
if (!test_and_clear_bit(TASKLET_STATE_SCHED,
&t->state))
BUG();
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();
}
}
这个函数除了加锁机制以外,读起来比较容易。其中要说明的是t->func(t->data)语句,这条语句实际上就是调用bh_action()函数:
/* BHs are serialized by
spinlock global_bh_lock.
It is still possible to make
synchronize_bh() as
spin_unlock_wait(&global_bh_lock). This operation is not used
by kernel
now, so that this lock is not made private only
due
to wait_on_irq().
It can be removed only after
auditing all the BHs.
*/
spinlock_t global_bh_lock =
SPIN_LOCK_UNLOCKED;
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);
}
这里对bh函数的执行又设置了两道锁。一是hardirq_trylock(),这是防止从一个硬中断内部调用bh_action()。另一道锁是spin_trylock()。这把锁就是全局量global_bh_lock,只要有一个CPU在这个锁所锁住的临界区运行,别的CPU就不能进入这个区间,所以在任何时候最多只有一个CPU在执行bh函数。至于根据bh函数的编号执行相应的函数,那就比较容易理解了。
2.软中断的执行
内核每当在do_IRQ()中执行完一个中断请求队列中的中断服务例程以后,都要检查是否有软中断请求在等待执行。下面是do_IRQ()中的一条语句:
if
(softirq_pending(cpu))
do_softirq();
在检测到软中断请求以后,就要通过do_softirq()执行软中断服务例程,其代码在/kernel/softirq.c中:
asmlinkage void
do_softirq()
{
int cpu = smp_processor_id();
__u32 pending;
long flags;
__u32 mask;
if (in_interrupt())
return;
local_irq_save(flags);/*把eflags寄存器的内容保存在flags变量中*/
pending = softirq_pending(cpu);
if (pending) {
struct softirq_action *h;
mask = ~pending;
local_bh_disable();
restart:
/* Reset the pending bitmask before enabling irqs */
softirq_pending(cpu) = 0;
local_irq_enable(); /*开中断*/
h = softirq_vec;
do {
if (pending & 1)
h->action(h);
h++;
pending >>= 1;
} while (pending);
local_irq_disable(); / *关中断*/
pending = softirq_pending(cpu);
if (pending & mask) {
mask &= ~pending;
goto restart;
}
__local_bh_enable();
if (pending)
wakeup_softirqd(cpu);
}
local_irq_restore(flags); /*恢复eflags寄存器的内容*/
}
从do_softirq()的代码可以看出,使CPU不能执行软中断服务例程的“关卡”只有一个,那就是in_interrupt(),这个宏限制了软中断服务例程既不能在一个硬中断服务例程内部执行,也不能在一个软中断服务例程内部执行(即嵌套)。但这个函数并没有对中断服务例程的执行进行“串行化”限制。这也就是说,不同的CPU可以同时进入对软中断服务例程的执行,每个CPU分别执行各自所请求的软中断服务。从这个意义上说,软中断服务例程的执行是“并发的”、多序的。但是,这些软中断服务例程的设计和实现必须十分小心,不能让它们相互干扰(例如通过共享的全局变量)。
从前面对软中断数据结构的介绍可以知道,尽管内核最多可以处理32个软中断,但目前只定义了四个软中断。在对软中断进行初始化时,soft_Init()函数只初始化了两个软中断TASKLET_SOFTIRQ和HI_SOFTIRQ,这两个软中断对应的服务例程为tasklet_action()和tasklet_hi_action()。因此,do_softirq()中的do_while循环实际上是调用这两个函数。前面已经给出了tasklet_hi_action()的源代码,而tasklet_action()的代码与其基本一样,在此不再给出。