软中断主要由do_softirq来执行,看看它的源代码如下:
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
// this is the reason why softirq won't preempt another softirq
// in_interrupt() contains context of an interrupt (both top and bottom halves)
// by Tom Xue
if (in_interrupt())
return;
local_irq_save(flags);
printk("tomxue: flags = %x\n", flags);
pending = local_softirq_pending();
printk("tomxue: pending = %x\n", pending);
if (pending)
__do_softirq();
local_irq_restore(flags);
}
如果进入__do_softirq()函数内部,看看是否有更多的发现?
asmlinkage void __do_softirq(void)
{
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
pending = local_softirq_pending();
account_system_vtime(current);
// 我猜下面两行是禁止了本地的软中断,但在其他处理器上仍可以执行(同一或者其他)软中断
__local_bh_disable((unsigned long)__builtin_return_address(0),
SOFTIRQ_OFFSET);
lockdep_softirq_enter();
cpu = smp_processor_id();
restart:
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
// 各个软中断执行时,允许响应本地中断
local_irq_enable();
h = softirq_vec;
do {
if (pending & 1) {
unsigned int vec_nr = h - softirq_vec;
int prev_count = preempt_count();
kstat_incr_softirqs_this_cpu(vec_nr);
trace_softirq_entry(vec_nr);
h->action(h);
trace_softirq_exit(vec_nr);
if (unlikely(prev_count != preempt_count())) {
printk(KERN_ERR "huh, entered softirq %u %s %p"
"with preempt_count %08x,"
" exited with %08x?\n", vec_nr,
softirq_to_name[vec_nr], h->action,
prev_count, preempt_count());
preempt_count() = prev_count;
}
rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();
pending = local_softirq_pending();
if (pending && --max_restart)
goto restart;
if (pending)
wakeup_softirqd();
lockdep_softirq_exit();
account_system_vtime(current);
__local_bh_enable(SOFTIRQ_OFFSET);
}
可见,如果软中断在一开始时,检测到此时正处于中断上下文中( in_interrupt() ),即有别的中断处理程序(top half)或者下半部在运行的话,那么就会立刻返回。
软中断执行过程中,允许响应中断,且当前处理器上的软中断被禁止,但是其他的处理器上仍可以运行别的甚至相同的软中断。
这些别扭的、拗口的规则其实通过查看源代码,很容易看到它们具体是怎么做的。
我的理解是:
- 中断一般是耽误不得的,只要发生,就立刻去处理,即使是软中断也要被打断,反正中断处理会压栈出栈它能返回嘛。
- 中断处理时(top half),屏蔽本地中断即本处理器上的中断,是为了防止中断嵌套吧,这样会增加问题的复杂性,同时压栈会消耗内存。反正中断线上的状态会维持住,这是硬件保证的,直到你处理了它,再清除这个状态。这样,top half是串行执行的,不用嵌套,很清爽的设计。且top half执行得快,所以没啥问题。
- 软中断在本地处理器上可重入?不,这主要是为了降低操作的复杂性。事实上,既然软中断处理过程中开了中断,就会有新的软中断等候执行。但是在当前软中断还在执行过程中,那些新增的软中断应该会被调度到其他处理器上执行吧(我猜的,具体怎么做的,咱再研究
)。直到当前软中断执行完了,会再次查看本地软中断pending状态,有pending的软中断,咱就继续执行之。
pending = local_softirq_pending(); if (pending && --max_restart) goto restart;
好吧,我想说的是,kernel太复杂了,不能事无巨细,只能连蒙带猜去理解,用杨振宁的话说,这叫渗透性学习:确定无疑的,那就是捡到了;模糊不确定的,也已经慢慢渗透进去了,渐渐会越来越清晰的。
再把目光放在pending这个变量身上,它是一个32位数(__u32,这应该是出于可移植性考虑而定义的数据类型),然后我们看到里面的do...while()循环用到了它,在循环里不断地做右移操作。这样,我就明白了为什么Robert Love在他的LKD3中说有32个软中断,我想他是说这个可以有。但是Linux中应该目前只用到了10个,那就是说,这个目前只有...如下:
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
而从__do_softirq()中,我们看到了
pending = local_softirq_pending();
追踪下去,
/* arch independent irq_stat fields */
#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id(), __softirq_pending)
继续追踪,
#ifndef __ARCH_IRQ_STAT
extern irq_cpustat_t irq_stat[]; /* defined in asm/hardirq.h */
#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#endif
继续追踪,
#ifndef __ARCH_IRQ_STAT
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
EXPORT_SYMBOL(irq_stat);
#endif
由此,得出pending应该是这样的:
pending = irq_stat[smp_processor_id()].__softirq_pending
而根据下面的定义,可知irq_stat是个数组,数组的大小即CPU的个数,数组里的每个元素是一个结构体(这个叫per CPU变量吧),而结构体内是一个无符号的整数...
typedef struct {
unsigned int __softirq_pending;
} ____cacheline_aligned irq_cpustat_t;
不过这个__softirq_pending我暂时没找到它是在哪里赋值的,做个记号吧
如上分析,估计有很多问题,还请各位多多指教!