在上一篇 LINUX软中断-softirq的描述中,提到过ksoftirqd,这篇文章就介绍ksoftirqd
ksoftirqd 是个内核线程,在创建的时候是绑定cpu的,每一个core对应生成一个ksoftirqd 线程
比如当前系统有4个core
~# ps aux | grep ksoftirqd
root 3 0.0 0.0 0 0 ? S 14:20 0:00 [ksoftirqd/0] //core 0
root 9 0.0 0.0 0 0 ? S 14:20 0:00 [ksoftirqd/1] //core 1
root 12 0.0 0.0 0 0 ? S 14:20 0:00 [ksoftirqd/2] //core 2
root 15 0.0 0.0 0 0 ? S 14:20 0:00 [ksoftirqd/3] //core 3
ksoftirqd 的作用就是处理softirq用,它的本质就是调用 __do_softirq
static int run_ksoftirqd(void * __bind_cpu)
{
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
preempt_disable();
if (!local_softirq_pending()) {
schedule_preempt_disabled();
}
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) {
if (local_softirq_pending())
//do_softirq会再次通过本core的__softirq_pending 变量来遍历softirq_vec数组
__do_softirq();
}
}
return 0;
}
void __tasklet_schedule(struct tasklet_struct *t)
{
unsigned long flags;
local_irq_save(flags);
t->next = NULL;
*__this_cpu_read(tasklet_vec.tail) = t;
__this_cpu_write(tasklet_vec.tail, &(t->next));
raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
//raise_softirq_irqoff
inline void raise_softirq_irqoff(unsigned int nr)
{
__raise_softirq_irqoff(nr);
/*
* If we're in an interrupt or softirq, we're done
* (this also catches softirq-disabled code). We will
* actually run the softirq once we return from
* the irq or softirq.
*
* Otherwise we wake up ksoftirqd to make sure we
* schedule the softirq soon.
*/
if (!in_interrupt())
wakeup_softirqd();
}
static void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __this_cpu_read(ksoftirqd);
if (tsk && tsk->state != TASK_RUNNING)
wake_up_process(tsk);
}
由上面的代码得知,tasklet_schedule如果在中断上下文被调用的话(ISR中调用),则不唤醒ksoftirqd,会交给中断的下半段处理tasklet,如果在普通线程被调用的话,则唤醒ksoftirqd。注意,唤醒的是本core上的ksoftirqd
tasklet感觉很少被用到,特点是执行权限比较高,执行时不能被打断,实时性比较好,缺点也明显,因为tasklet在执行过程中本core不能运行其他程序,如果tasklet运行时间长的话,会导致其他程序久久不被运行或者其他程序会抢别的core运行,导致系统性能下降。
要求display的显示是 60fps, insmod sd卡驱动后,显示会掉帧,导致降到 50fps左右
原因是sd卡驱动在检测sd卡是否插入用的是loop的方式检测gpio的值,而loop是利用mod_timer这种精度定时器来做的(300ms一次检测)
mod_timer实现机制是用tasklet来做的(TIMER_SOFTIRQ),即mod_timer所注册的回调函数会在softirq_vec[TIMER_SOFTIRQ]->action中被调用(run_timer_softirq)
每次tick产生后,就会判断一下timer时间到没到,如果到了,就会raise_softirq触发软中断来处理回调函数。
tick_handle_periodic()
->tick_periodic()
->update_process_times()
->run_local_timers()
->hrtimer_run_queues()
->__run_hrtimer(timer, &base->softirq_time);
->raise_softirq(TIMER_SOFTIRQ)
->run_timer_softirq
->处理 mod_timer所注册timer的回调函数
而在本地问题中,timer的回调函数的处理中用到了如下的做法:
for (i=0; i<5; i++) {
gpio_val += (smc_host->cd_mode == CARD_DETECT_BY_GPIO_IRQ_UP)\
? (!__gpio_get_value(cd->gpio))\
:( __gpio_get_value(cd->gpio));\
mdelay(1);
}
即循环5次检测gpio,并且还用mdelay来进行延迟,(此函数意味着本core不能被切走必须死等, 这也是没办法的,因为回调函数是在softirq上下文运行的,不能使用睡眠类的函数,这里mdelay不会睡眠)
这就造成了一次timer回调的执行至少需要5-6ms,因为系统有两个sd卡卡槽,一个卡槽对应一个timer进行检测,相当于执行一次需要10-12ms
而且softirq上下文导致其他线程不能被执行,从而大大影响系统性能。
注意,在linux中,只要是中断上下文,是不允许调用schedule这种函数切走的,因为中断上下文即原子上下文,所以tasklet中不能用睡眠函数(本身你用了tasklet,就说明要处理的东西优先级高),但我偏偏要主动schedule切走呢?其实实验中发现也没什么问题,但是schedule会报错(scheduling while atomic)
static noinline void __schedule_bug(struct task_struct *prev)
{
if (oops_in_progress)
return;
printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n",
prev->comm, prev->pid, preempt_count());
debug_show_held_locks(prev);
print_modules();
if (irqs_disabled())
print_irqtrace_events(prev);
dump_stack();
}
因为上篇文章讲过,内核调度的原则是preempt_count为0,你偏偏非要preempt_count不为0的时候主动调用sleep等切走的话,内核__schedule函数会给你一个bug,但是为了尽可能的维持系统的运行,还坚持在跑,并且试图修正整个preempt_count (不在本次的讨论范围之内)
注意: 中断服务程序 = ISR + softirq
softirq执行的时候毕竟是开中断的,即便softirq执行中主动切走当前进程A,因为tick中断还是开的,还是会被切回来的。
我们模拟下流程:
所以整个流程都没什么太大问题,但是在ISR中调用__schedule时问题就大了。
因为在执行ISR时,中断是关的,即本core上不会再有中断了,相当于调度器也关了。
ISR中调用__schedule主动切走后,因为调度器也被关了,本core上的其他程序就无法被执行, 当然如果是SMP的场合下,因为其他core没有关中断,即其他core的tick中断还在继续,可能系统不会马上挂掉还会在坚持跑,此时系统的运行一定处在一个不稳定的状态,会产生莫名其妙的bug。