作为软中断,从内核同步的角度来说它有两个特点:一是软中断总是和cpu绑定在一起的,二是除了中断或是异常(一般内核太不会出现异常)没有什么东西能够抢占它。因为和cpu绑定,软中断喜欢使用cpu变量,这样就不用考虑SMP的竞争,因为不会被其他软中断或是内核抢占,使得不用在嵌套上太过于小心。一般而言,软中断是在中断的下半部分执行的,优先级大于进程,不过大量的软中断会阻塞进程的正常进行。因此内核有一个机制,软中断如果连续出现多次后就不再继续在中断下半部分执行软中断,而是将其放到ksoftirqd内核线程中继续执行。
-
asmlinkage void __do_softirq(void)
-
{
-
....
-
int max_restart = MAX_SOFTIRQ_RESTART;
-
....
-
pending = local_softirq_pending();
-
if (pending && --max_restart)
-
goto restart;
-
-
if (pending)
-
wakeup_softirqd();
-
....
-
}
《深入理解linux内核》提到,内核线程优先级较低,当系统负荷较低的时候,就会调度该线程继续完成软中断。每一个cpu对应一个ksoftirqd内核线程,但是作为内核线程,ksoftirqd与中断的下半部分的执行环境是非常不一样的,那它是如何能够完成软中断呢。
首先先说说绑定cpu的问题,由于启动软中断是通过设置的是cpu变量__softirq_pending来通知cpu的。一旦这个cpu中断了,在中断的下半部分里会检查此部分,若检查到有软中断请求就执行软中断。因此可以说向在哪个cpu上申请了软中断,就会在那个cpu上执行软中断。但是内核线程不能保证这点,就拿工作队列work thread而言,虽然说是每个cpu拥有一个work队列,但对于的执行内核线程worker_thread却不是cpu独有的。比如我向3号cpu上的队列里提交了一个work,有可能这个work是在1号cpu里运行完成的。为什么会这样呢,是因为内核线程是可以被抢占(开启内核抢占)或是主动休眠的。一旦内核线程不是正在当前cpu正在运行的进程,cpu在调度的时候就有可能将其放到别的cpu上面。内核之所以这样做是处于平衡各个cpu工作量的考虑,但这样就给我们ksoftirqd执行软中断带来了麻烦。因为ksoftirqd是不可能一直霸占着cpu不放的,事实上在代码中ksoftirqd会检查TIF_NEED_RESCHED位来主动放弃cpu。那内核是如何解决这个问题的呢,其实很简单,只要内核将ksoftirqd绑定在对应的cpu运行队列上就行了。
-
p = kthread_create(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu);
-
if (IS_ERR(p)) {
-
printk("ksoftirqd for %i failed\n", hotcpu);
-
return NOTIFY_BAD;
-
}
-
kthread_bind(p, hotcpu);
在创建ksoftirqd内核线程之后就会调 kthread_bind来绑定cpu。这样在内核调度的时候就会将ksoftirqd放在对应cpu的运行队列里,ksoftirqd必定运行在对应cpu上的效果。
-
void kthread_bind(struct task_struct *k, unsigned int cpu)
-
{
-
/* Must have done schedule() in kthread() before we set_task_cpu */
-
if (!wait_task_inactive(k, TASK_UNINTERRUPTIBLE)) {
-
WARN_ON(1);
-
return;
-
}
-
set_task_cpu(k, cpu);
-
k->cpus_allowed = cpumask_of_cpu(cpu);
-
k->rt.nr_cpus_allowed = 1;
-
k->flags |= PF_THREAD_BOUND;
-
}
其次再说说软中断的执行函数do_softirq,在do_softirq一开始就禁用了本地中断的。这样主要是防止do_softirq本身的嵌套,如果在do_softirq开始到软中断服务函数执行这段时间内又发生了中断,在中断的下半部分里还会调用do_softirq来处理之前没有来的及处理的软中断,这样造成了竞争的。而一旦到了软中断服务函数开始执行的时候,内核会开启本地中断同时禁用软中断,一方面允许内核抢占,一方面防止软中断的嵌套。
就do_softirq函数本身来说,除了在中断服务函数里调用(请区分中断服务函数和哈中断服务函数执行完后进入的中断的下半部分)之外,是可以在其他内核线程里调用。因为是满足不被软中断和内核抢占的要求,而其处理的软中断请求而也是执行do_softirq的cpu的软中断,在内核线程里使用是没有问题的。我们可以看到在内核里很多内核线程使用local_bh_enable来启用do_softirq的例子。那为什么ksoftirqd要绑定cpu,而其他的使用local_bh_enable的内核线程不需要绑定cpu呢。
要注意,内核线程调local_bh_enable启用do_softirq是一个主动操作。local_bh_enable对应的是local_bh_disable,后者是禁用软中断,前者是重新开启软中断,当开启软中断的时候,会检查是否有软中断请求,一旦有就调用do_softirq,来执行软中断。可以看出,是因为之前关了软中断,现在开的时候发现该cpu有相应的软中断要处理就开始启用处理函数。而从发现cpu上有软中断请求到执行do_softirq里面只有一步,虽然这里没有禁止内核抢占,但内核刚好在这一步被抢占的概率的微乎其微,而且就算被抢占换到了其他cpu,do_softirq也只是完成这个新的cpu的软中断操作,不会影响原来cpu的软中断请求,原来的cpu请求要么通过中断的下半部分,要么通过ksoftirqd内核线程来处理。
-
if (unlikely(!in_interrupt() && local_softirq_pending()))
-
do_softirq();
但是向cpu发出请求到唤醒ksoftirqd内核线程的时间是很长的,而如果这个ksoftirqd不是运行在要请求的cpu上的话,那么无论执行do_softirq与否,都不会解决原来cpu的软中断请求。这样ksoftirqd不能及时完成对于cpu在中断下半部分未能处理完的软中断请求。因此ksoftirqd必须绑定在对应cpu上,这样才能作为中断下半部分的补充,来更好的完成内核的软中断机制。
阅读(4681) | 评论(0) | 转发(1) |