从“取外卖”看中断
中断时系统用来响应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。
你可能要问了,为什么要有中断呢?举个生活中的例子,让你感受下中断的魅力。
比如你订了一份外卖,但是不确定外卖什么时候送到,也没有别的方法了解外卖的进度,但是,配送员送外卖是不等人的,到了你这儿没人取的话,就直接走人了。所以你指能苦苦等着,时不时去门口看看外卖送到没,而不能干其他事情。
不过呢,如果你在订外卖的时候,你就跟配送员约定好,让他送到后给你打个电话,你就不用苦苦等待了,就可以去忙别的事情,直到电话一响,接电话、取外卖就可以了。
这里的"打电话",其实就是一个中断。没接到电话的时候,你可以做其他的事情;只有接到了电话(也就是发生中断),你才要进行另一个动作:取外卖这个例子你就可以发现,中断其实就是一种异步的事件处理机制,可以提高系统的并发处理能力。
由于中断处理程序会打断其他进程的运行,所以,为了减少对正常进程运行调度的影响,中断处理程序需要尽可能快的运行。如由于中断本身要做的事情不多,那么处理起来也不会有太大的问题;但如果 中断要处理的事情很多,中断服务程序就有可能要运行很长时间。
特别是,中断处理程序在响应中断时,还会临时关闭中断。这就会导致上一次中断处理完成之前,其他中断都不能响应,也就是说中断可能会丢失。
假如你定了2分外卖,一份主食和一份饮料,并且是由2个不同的配送员来配送。两份外卖都约定了电话取外卖的方式,但是问题又来了,当第一份外卖送到的,配送员给你打了个长长的电话,商量发票的处理方式,与此同时,第二个配送员也到了,也想给你打电话,但是很明显,因为电话占线(也就是关闭了中断响应),第二个配送员的电话是打不通的。所以,第二个配送员很可能试了几次后就走掉了(也就是丢失了一次中断)。
软中断
如果你弄清楚了“取外卖”的模式,那对系统的中断机制就很容易理解了。事实上,为了解决中断处理程序执行过长和中断丢失的问题,linux将中断处理过程分成了两个阶段,也就是上半部和下半部:
上半部用来快速处理中断:它在中断禁止模式下运行,只要处理跟硬件紧密相关的时间敏感的工作。
下半部分用来延迟处理上半部分未完成的工作:通常一内核线程的方式运。
这两个阶段你可以这样理解:
上半部分直接处理硬件请求,也就是我们常说的硬中断,特点是快速执行
而下半部则是由内核触发,也就是我们常说的软中断,特点是延迟执行
实际上,上半部会打断CPU正在执行的任务,然后立即执行中断处理程序,而下半部以内核线程的方式执行,并且每个CPU都对应一个软中断
内核线程,名字为"ksoftirqd/CPU 编号" 比如0号CPU对应的软中断内核线程的名字就是ksoftirqd/0
注意事项:
软中断不只包括了刚刚所讲的硬件设备中断处理程序的下半部。一些内核自定义的时间也属于软中断,比如内核调度和RCU锁
(Read-Copy Update 的缩写,RCU 是 Linux 内核中最常用的锁之一)等。
查看软中断和内核线程
不知道你还记不记得,前面提到过的proc文件系统。她是一种内核空间和用户空间进行通信的机制,可以用来查看内核的数据结构,或者用来动态修改内核的配置。其中:
/proc/softirqs提供了软中断的运行情况
/proc/interrupts提供了硬中断的运行情况
# cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
HI: 1 0 0 0 0 0 0 0
TIMER: 91465557 75078967 99549386 74936020 92918225 59243402 92919973 58735897
NET_TX: 7 9 8 10 8 11 5 8
NET_RX: 10462869 10402563 10232492 50570620 10442656 49829946 10726373 89459403
BLOCK: 4831 0 11032160 0 0 0 0 0
BLOCK_IOPOLL: 0 0 0 0 0 0 0 0
TASKLET: 5 3 26 4 2 5 2 4
SCHED: 40910608 31925972 51753270 18248274 39602169 16387318 39118080 16326508
HRTIMER: 0 0 0 0 0 0 0 0
RCU: 56137399 38856963 59327894 27917659 56945439 29177149 56783540 29259905
TIMER(定时中断)、NET_RX(网络接收)、SCHED(内核调度)、RCU(RCU锁)
1、TASKLET在不同CPU上的分布并不均匀,TASKLET是最常用的软中断实现机制,每TASKLET个只运行一次就会结束,并且只在调用它的函数所在的CPU上运行。因为使用TASKLET特别简便,当然也会存在一些问题,比如说由于只在一个cpu上运行导致调度不均衡,再比如因为不能在多个CPU上并行运行带来了性能限制。
2、另外,刚刚提到过,软中断实际上是以内核线程的方式运行的,每个CPU都对应一个软中断内核线程,这个软中断内核线程叫做ksoftirqd/CPU 编号。那要怎么查看这些线程的运行呢?
# ps aux |grep softirq
root 6 0.0 0.0 0 0 ? S 2023 0:10 [ksoftirqd/0]
root 14 0.0 0.0 0 0 ? S 2023 0:10 [ksoftirqd/1]
root 19 0.0 0.0 0 0 ? S 2023 0:11 [ksoftirqd/2]
root 24 0.0 0.0 0 0 ? S 2023 0:11 [ksoftirqd/3]
root 29 0.0 0.0 0 0 ? S 2023 0:10 [ksoftirqd/4]
root 34 0.0 0.0 0 0 ? S 2023 0:13 [ksoftirqd/5]
root 39 0.0 0.0 0 0 ? S 2023 0:10 [ksoftirqd/6]
root 44 0.0 0.0 0 0 ? S 2023 0:15 [ksoftirqd/7]