转载自https://zhuanlan.zhihu.com/p/80680484, 作者:兰新宇
ksoftirqd
"ksoftirqd"是一种per-cpu的内核线程,当你用"ps"命令查看的时候,你会发现ksoftirqd的数目刚好等于你机器的CPU的数目。
引入ksoftirqd的初衷是为了避免上文提到的“线程饥饿”,在一定程度上减少任务等待执行的 latency,提高系统的实时性。如果需要更进一步的增强实时性,应该支持在中断处理函数返回之后,率先执行更高优先级的任务,而不是之前被中断打断的那个任务。
ksoftirqd是作为一个线程参与内核调度的,因而是运行在进程上下文,但在ksoftirqd运行期间,通常也是不允许睡眠的,具体原因将在本文的后半部分给出。
softirq的抢占
softirq和top half/hardirq的一个重要区别是,在hardirq执行期间,硬件中断是被屏蔽的,这样hardirq就不会嵌套(因而hardirq不需要考虑可重入的问题),但是在softitq执行期间,硬件中断是打开的,也就是说,softirq可能被hardirq抢占。
自从Linux 2.6.32之后,中断处理函数不再使用被其打断的线程的栈,而是使用独立的per-cpu的栈,并且softirq和hardirq会有不同的栈,这意味着每个CPU都对应一个softirq的栈和一个hardirq的栈。
DEFINE_PER_CPU(struct irq_stack *, hardirq_stack);
DEFINE_PER_CPU(struct irq_stack *, softirq_stack);
一个softirq在被硬件中断打断后,softirq_stack会记录当前softirq的上下文(入栈),然后CPU转去执行hardirq里的程序,同时指向softirq_stack的栈指针也会转而指向hardirq_stack。
hardirq在执行完毕后,会触发其对应的softirq,但并不会立即执行这个softirq,而只是将这个softirq在pending位图中对应的bit置位。然后CPU会跳回之前被打断的那个softirq继续执行,softirq_stack保存的上下文也将被恢复(出栈),直到这个被打断的softirq执行完毕,内核才会根据softirq的pending位图重新选择待处理的softirq来执行。
可见,softirq只会被硬件中断抢占,而不会被另一个softirq抢占。如果不考虑内核的实时特性和中断线程化,那么softirq也不会被更高优先级的线程所抢占。
在多核系统中,同一种softirq(跟信号一样,编号相同的就是同一种)的处理函数可以在不同的CPU上运行,这样可以利用多核的并行特性,带来更好的性能,但是如果sofirq的处理函数中含有全局变量,就可能涉及到spinlock等多核同步机制,增加复杂性。对此,下文将要介绍的tasklet会有不同的规则和处理方式。
softirq的屏蔽
线程被硬件中断打断后,如果希望执行完hardirq后就直接返回原来的线程,而不去执行pending的softirq,那么可以选择“屏蔽”softirq。
从实现的角度来看,这个机制跟“被信号打断”的处理手法是基本一样的。因而就像可以用sigprocmask()屏蔽信号一样,调用local_bh_disable()函数来屏蔽softirq。
在local_bh_disable()期间,线程虽然屏蔽了softirq,但可能没有屏蔽硬件中断,而如果发生了硬件中断,又将会产生pending且不能执行的softirq,所以在调用local_bh_enable()打开softirq时,需要检测当前是否有处于pending状态的待处理的softirq。
执行被local_bh_disable()和local_bh_enable()包围的代码区域时,由于softirq是被屏蔽的,因而在这段时间里也是不能睡眠的,也是处在“softirq上下文”。虽然都是在softirq上下文,但还是有办法将这种情况和softirq正在运行的情况区分开来,靠的也是这篇文章介绍的preempt_count()。
local_bh_disable();
/* critical section */
local_bh_enable();
前面在介绍ksoftirqd的时候留了一个问题,为什么在ksoftirqd线程执行期间也不允许睡眠?因为进入ksoftirqd之后,softirq也是被屏蔽的,相当于是执行了local_bh_disable()。
这样设计的目的是考虑到同一段处理softirq的代码,既可能在__do_softirq()
中执行,又可能由于强制线程化,或者超过执行时间/次数的限制,被挪到ksoftirqd中执行。如果两者使用不一样的规则,将会增加代码移植的开销(参考这个回答)。
线程既可能被softirq抢占,也可能被硬件中断抢占,而softirq通常只会被硬件中断抢占,如果在线程或者softirq执行期间不希望被硬件中断抢占,那么可以使用local_irq_disable()/local_irq_save()函数。
至此,softirq就介绍完了,下文将开始介绍tasklet和workqueue。
评论 (0)