对于软中断,实现还是很完美的。每一个中断上半部都对应一个软中断,可以在多核上并发进行。以块设备软中断为例,初始化如下
blk_dev_init
=>for_each_possible_cpu(i)//每个CPU都部署
INIT_LIST_HEAD(&per_cpu(blk_cpu_done, i));
=>open_softirq(BLOCK_SOFTIRQ, blk_done_softirq, NULL);
=>softirq_vec[nr].data = data;
=>softirq_vec[nr].action = action;
上半部交接
blk_complete_request
=>cpu_list = &__get_cpu_var(blk_cpu_done);
=>list_add_tail(&req->donelist, cpu_list);//每个完成的req都加到队列里面
=>raise_softirq_irqoff(BLOCK_SOFTIRQ);//唤醒软中断
=>__raise_softirq_irqoff(nr);
=>or_softirq_pending(1UL << (nr)); //置pending位
=>wakeup_softirqd()
=>struct task_struct *tsk = __get_cpu_var(ksoftirqd);
=>wake_up_process(tsk);//唤醒ksoftirqd
软中断粉墨登场
ksoftirqd
=>set_current_state(TASK_INTERRUPTIBLE);
=>while (!kthread_should_stop())
preempt_disable();//关闭抢占计数
if (!local_softirq_pending()) //如果没有pending计数
schedule();//切换出去
preempt_disable();
__set_current_state(TASK_RUNNING);
while (local_softirq_pending())//有pending计数的话
do_softirq();
=>if (local_softirq_pending())
=>curctx = current_thread_info();//切换到软中断上下文
irqctx = softirq_ctx[smp_processor_id()];
irqctx->tinfo.task = curctx->task;
irqctx->tinfo.previous_esp = current_stack_pointer;
/* build the stack frame on the softirq stack */
isp = (u32*) ((char*)irqctx + sizeof(*irqctx));
=>call __do_softirq
=>h = softirq_vec;
=>do {//依次执行软中断向量
if (pending & 1) {
h->action(h);
=>blk_done_softirq//对于块设备来说,就是该函数
=>cpu_list = &__get_cpu_var(blk_cpu_done);
list_replace_init(cpu_list, &local_list);
=>while (!list_empty(&local_list)) {//依次执行每个req
struct request *rq = list_entry(local_list.next, struct request, donelist);
list_del_init(&rq->donelist);
rq->q->softirq_done_fn(rq);
=>scsi_softirq_done
}
rcu_bh_qsctr_inc(cpu);
}
h++;
pending >>= 1;
} while (pending);
preempt_enable_no_resched();
cond_resched();
preempt_disable();
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE);
=>__set_current_state(TASK_RUNNING);
软中断成本很高,linux也就定义了6种,块设备和网口是典型,中断频繁触发,软中断随之频繁,但是任务本身不重。如果中断不是超级频繁,下半部不是超级复杂。那么,tasklet是个很好的选择。
tasklet不管有多少个cpu,1种类型只有1个,没有队列。以键盘中断为例,定义如下:
DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0);
键盘中断交接给tasklet
request_irq(EC3104_IRQ_SER4, ec3104_keyb_interrupt, 0, "keyboard", NULL);
=>ec3104_keyb_interrupt
=>e5_receive(k);
=>handle_keyboard_event(k->packet[1]);
=>tasklet_schedule(&keyboard_tasklet);
tasklet执行
tasklet_schedule(&keyboard_tasklet);
=>if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))//当tasklet已经处于调度状态但是还没有执行时,新来的同类型的tasklet丢弃掉,因为tasklet只有1个全局变量
__tasklet_schedule(t);
=>t->next = __get_cpu_var(tasklet_vec).list;//将tasklet加入到tasklet里面,这里面的tasklet是不同类型tasklet的集合
__get_cpu_var(tasklet_vec).list = t;
=>raise_softirq_irqoff(TASKLET_SOFTIRQ);
=......=>tasklet_action
=>list = __get_cpu_var(tasklet_vec).list;//把队列里面的tasklet全部摘除
__get_cpu_var(tasklet_vec).list = NULL;
=>while (list)//串行执行所有的tasklet
struct tasklet_struct *t = list;
list = list->next;
if (tasklet_trylock(t))
tasklet_trylock
=>return !test_and_set_bit(TASKLET_STATE_RUN, &(t)->state);//是否处于RUN状态
test_and_clear_bit(TASKLET_STATE_SCHED, &t->state)//取消tasklet的调度状态,这个时候如果新来一个同类型的tasklet,则能挂上去
t->func(t->data);//执行tasklet
tasklet_unlock(t);//解除RUN状态
continue;//回到while (list)继续串行执行下一个tasklet
local_irq_disable();//如果tasklet处于RUN状态,则把队列重新挂回去
t->next = __get_cpu_var(tasklet_vec).list;
__get_cpu_var(tasklet_vec).list = t;
__raise_softirq_irqoff(TASKLET_SOFTIRQ);
local_irq_enable();
由于tasklet只有1个,处于调度态的话只能有1个,所以不需要重入。所以在键盘,鼠标等处理任务不是很繁重的情况下很合适。
如果中断不是很繁重,但是下半部需要处理的任务很重很繁琐,不是那么紧急的话,wq就更合适了。
linux软中断机制分析
https://blog.csdn.net/andylauren/article/details/70233378
linux 内核软中断详解
http://blog.sae.sina.com.cn/archives/4103
总结一下linux软中断的实现
http://blog.chinaunix.net/uid-145571-id-2798845.html
kernel中断分析七——tasklet
https://blog.csdn.net/yin262/article/details/54289502
深入分析Tasklet机制
https://blog.csdn.net/shenwanjiang111/article/details/52247760
Nginx服务器上软中断过高问题如何解决
http://os.51cto.com/art/201310/414482.htm
ORIG_EAX
https://blog.csdn.net/zuihoudebingwen/article/details/9115599
struct pt_regs 中存的内容
https://blog.csdn.net/abo8888882006/article/details/5379727
软中断与硬中断 作者的博客不错
http://blog.chinaunix.net/uid-7396260-id-2056718.html
Linux下软中断过程(详细)源码总结
https://blog.csdn.net/luckiers/article/details/123868625