前面一篇文章分析了中断系统的框架,这篇文章主要讲中断的下半部份的机制;
(一)概述
linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情。在执行bottom half的时候,是开中断的。系统可以把实时性的操作或者硬件的一些ACK放在上半部分来做,其他数据处理,上报操作等不是太紧要的都可以放到下半部分来做,这样就大大减少了系统关中断的时间,提高了系统的性能;
常见处理下半部份的机制有softirq、tasklet、workqueue;workqueue运行在process context,而softirq和tasklet运行在interrupt context,所以workqueue和softirq、tasklet是有本质区别的;
(二)softirq
(1)softirq和tasklet的区别
soft和irq都用于中断的下半部处理,softirq更倾向于性能,而tasklet更倾向于易用性。但是tasklet是基于softirq机制来实现的;
同一个中断触发然后调用下半部的处理函数,并启用softirq机制,这样相同类型的softirq有可能在不同的CPU上并发执行,所以要考虑并发引入同步机制,当中断处理的softirq同时在Processor A和B上运行的时候,中断又来了。这时候,中断分发给processor A,这时候,processor A上的handler仍然会raise softirq,但是并不会调度该softirq。也就是说,softirq在一个CPU上是串行执行的。这种情况下,系统性能瓶颈是CPU资源,需要增加更多的CPU来解决该问题。
同样相同中断触发的tasklet在processor A上被调度执行,那么它永远也不会同时在processor B上执行,也就是说,tasklet是串行执行的(注:不同的tasklet还是会并发的),不需要考虑重入的问题。在检查tasklet的状态的时候,如果发现该tasklet在其他processor上已经正在运行,那么该tasklet不会被处理,一直等到在processor A上的tasklet处理完,在processor B上的这个tasklet才能被执行;
(2)irq context
一个task的thread info数据结构定义如下:
struct thread_info {
……
int preempt_count; /* 0 => preemptable, <0 => bug */
……
};
preempt_count这个成员被用来判断当前进程是否可以被抢占。如果preempt_count不等于0(可能是代码调用preempt_disable显式的禁止了抢占,也可能是处于中断上下文等),说明当前不能进行抢占,如果preempt_count等于0,说明已经具备了抢占的条件(当然具体是否要抢占当前进程还是要看看thread info中的flag成员是否设定了_TIF_NEED_RESCHED这个标记,可能是当前的进程的时间片用完了,也可能是由于中断唤醒了优先级更高的进程);
preempt字段格式如下:
IRQ context其实就是hard irq context,也就是说明当前正在执行中断handler(top half),只要preempt_count中的hardirq count大于0(=1是没有中断嵌套,如果大于1,说明有中断嵌套),那么就是IRQ context。
(3)softirq机制
a. 注册softirq,注册一个软中断,即在软中断向量数据中,增加软中断number为nr的处理函数:
442 void open_softirq(int nr, void (*action)(struct softirq_action *))
443 {
444 softirq_vec[nr].action = action;
445 }
softirq_vec为软中断向量表,针对不同的softirq number有不同的entry:
58 static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
softirq number:
387 enum
388 {
389 HI_SOFTIRQ=0,//高优先级的tasklet;
390 TIMER_SOFTIRQ,//基于系统tick的software timer
391 NET_TX_SOFTIRQ,//网卡数据发送;
392 NET_RX_SOFTIRQ,//网卡数据接收;
393 BLOCK_SOFTIRQ,//block device
394 BLOCK_IOPOLL_SOFTIRQ,//block device
395 TASKLET_SOFTIRQ,//用于普通的tasklet;
396 SCHED_SOFTIRQ,
397 HRTIMER_SOFTIRQ,
398 RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
399
400 NR_SOFTIRQS
401 };
b. 触发softirq :如果要启动某个软中断,则需要置位irq_stat[cpu].__softirq_pending中的相应位,而设置__softirq_pending的操作则由函数__raise_softirq来实现。_softirq_pending是per cpu的,因此不需要考虑多个CPU的并发,只要disable本地中断,就可以确保对__softirq_pending操作的原子性。
可以通过以下两种方法启动软中断:
1)在中断上下文中,通过调用函数raise_softirq,置位irq_stat[cpu].__softirq_pending中的相应软中断位;则会在中断结束后在函数irq_exit中调用invoke_softirq,实现软中断处理;
2)在非中断上下文中,通过调用raise_softirq_irqoff,置位irq_stat[cpu].__softirq_pending中的相应软中断位;并唤醒软中断守护进程,通过软中断守护进程实现软中断的处理;
//大多数情况下都是在中断handler被调用,是关cpu中断的,但是也有可能在其他上下文中调用,这个时候是打开cpu中断的,所以要用local_irq_save来保护softirq_pending的状态;
427 void raise_softirq(unsigned int nr)
428 {
429 unsigned long flags;
430
431 local_irq_save(flags);
432 raise_softirq_irqoff(nr);
433 local_irq_restore(flags);
434 }
410 inline void raise_softirq_irqoff(unsigned int nr)
411 {
412 __raise_softirq_irqoff(nr);//设定本CPU上的__softirq_pending的某个bit等于1,具体的bit是由soft irq number(nr参数)指定的。;
413
423 if (!in_interrupt())
424 wakeup_softirqd();
//如果在中断上下文,我们只要set __softirq_pending的某个bit就
//OK了,在中断返回的时候自然会进行软中断的处理。但是,如果在
//context上下文调用这个函数的时候,我们必须要调用
//wakeup_softirqd函数用来唤醒本CPU上的softirqd这个内核线程。
425 }
c. 中断处理:softirq 的执行有两个时刻:在退出中断 irq_exit() 时或者在 softirqd 线程当中,在中断上下文的情况,通过退出中断的时候会调用softirq注册的处理函数,代码如下:
389 void irq_exit(void)
390 {
399 if (!in_interrupt() && local_softirq_pending())
//确保没有在中断上下文,同时local_softirq_pending来判断是否触发了软中断执行;
400 invoke_softirq();
405 }
350 static inline void invoke_softirq(void)
351 {
352 if (!force_irqthreads) {
//如果强制线程化,那么系统中所有的软中断都在sofirq的softirqd进程中被调度执行。
353 #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
359 __do_softirq();
360 #else
366 do_softirq_own_stack();
367 #endif
368 } else {
369 wakeup_softirqd();//内核专门开了一个softirq服务线程来处理软中断;
370 }
371 }
233 asmlinkage __visible void __do_softirq(void)
234 {
235 unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
236 unsigned long old_flags = current->flags;
237 int max_restart = MAX_SOFTIRQ_RESTART;
238 struct softirq_action *h;
239 bool in_hardirq;
240 __u32 pending;
241 int softirq_bit;
242
248 current->flags &= ~PF_MEMALLOC;
249
250 pending = local_softirq_pending();