本文是通过学习倪朋飞老师的《Linux性能优化实战》 :怎么理解Linux软中断?
怎么理解Linux软中断?
前言
前面我们知道了iowait(也就是等待 I/O 的 CPU 使 用率)升高时的分析方法。这里你要记住,进程的不可中断状态是系统的一种保护机制, 可以保证硬件的交互过程不被意外打断。所以,短时间的不可中断状态是很正常的。
但是,当进程长时间都处于不可中断状态时,你就得当心了。这时,你可以使用 dstat、 pidstat 等工具,确认是不是磁盘 I/O 的问题,进而排查相关的进程和磁盘设备。关于磁 盘 I/O 的性能问题,你暂且不用专门去背,我会在后续的 I/O 部分详细介绍,到时候理解 了也就记住了。
其实除了 iowait,软中断(softirq)CPU 使用率升高也是最常见的一种性能问题。
中断的含义
中断是系统用来响应硬件设备请求的一种机制,它会打断进程的正常调度和执行,然后调用内核中的中断处理程序来响应设备的请求。
举个例子:
比如说我们打车,之前打车,都是在路边打出租,如果我们不去路边那么就无法打车,但是现在我们用网约车打车,在家打车,只要留好电话,车到了司机师傅给我们打电话,我们下去就行了。这里的打电话其实就是一个”中断“。没有接到电话时,我们可以做其他的事情;只要接到了司机师傅的电话(也就是发生了中断),我们才进行另一个动作:下楼乘车。
中断其实是一种异步的事件处理机制,可以提高系统的并发处理能力。
由于中断处理程序会打断其他进程的运行,所以,为了减少对正常进程运行调度的影响, 中断处理程序就需要尽可能快地运行。如果中断本身要做的事情不多,那么处理起来也不会有太大问题;但如果中断要处理的事情很多,中断服务程序就有可能要运行很长时间。
特别是,中断处理程序在响应中断时,还会临时关闭中断。这就会导致上一次中断处理完成之前,其他中断都不能响应,也就是说中断有可能会丢失。
还是以打车为例:假设在司机给你打电话时,刚好公司同事和你打了一个长长的电话,司机师傅到了,也给你打电话,显然,因为电话占线(也就是关闭了中断响应),司机师傅打不通。所以,司机师傅可能打了几次,然后取消了订单走掉了。
软中断
为了解决中断处理程序执行过长和中断丢失的问题,Linux 将中断处理过程分成了两个阶段, 也就是上半部和下半部:
- 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。
- 下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。
比如说前面打车的例子,上半部就是你接听电话,告诉同事你已经知道了,其他事儿见面再说,然后电话就可以挂断了;下半部才是乘车的动作,以及见面后商量事情的动作。
这样,同事不会占用你太多时间,当司机到了之后,照样能正常打通你的电话。
除了打车,再举个最常见的网卡接收数据包的例子,让我们更好地理解。
网卡接收到数据包后,会通过硬件中断的方式,通知内核有新的数据到了。这时,内核就应该调用中断处理程序来响应它。可以想一下,这种情况下的上半部和下半部分 别负责什么工作呢?
对上半部来说,既然是快速处理,其实就是要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态(表示数据已经读好了),最后再发送一个软中断信号,通知下半部做进一步的处理。
而下半部被软中断信号唤醒后,需要从内存中找到网络数据,再按照网络协议栈,对数据进行逐层解析和处理,直到把它送给应用程序。
所以,这两个阶段你也可以这样理解:
- 上半部直接处理硬件请求,也就是我们常说的硬中断,特点是快速执行;
- 而下半部则是由内核触发,也就是我们常说的软中断,特点是延迟执行。
实际上,上半部会打断 CPU 正在执行的任务,然后立即执行中断处理程序。而下半部以内 核线程的方式执行,并且每个 CPU 都对应一个软中断内核线程,名字为 “ksoftirqd/CPU 编号”,比如说, 0 号 CPU 对应的软中断内核线程的名字就是 ksoftirqd/0。
不过要注意的是,软中断不只包括了刚刚所讲的硬件设备中断处理程序的下半部,一些内核自定义的事件也属于软中断,比如内核调度和 RCU 锁(Read-Copy Update 的缩写, RCU 是 Linux 内核中最常用的锁之一)等。
那要怎么知道的系统里有哪些软中断呢?
查看软中断和内核线程
proc 文件系统。它是一种内核空间和用户空间进行 通信的机制,可以用来查看内核的数据结构,或者用来动态修改内核的配置。其中:
- /proc/softirqs 提供了软中断的运行情况;
- /proc/interrupts 提供了硬中断的运行情况。
运行下面的命令,查看 /proc/softirqs 文件的内容,你就可以看到各种类型软中断在不同 CPU 上的累积运行次数:
cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3
HI: 0 0 0 0
TIMER: 4182865420 3331973505 4167200079 3396782781
NET_TX: 165339 98627 159267 96561
NET_RX: 2919031236 6647107 4244098012 6643117
BLOCK: 0 0 0 5433303
IRQ_POLL: 0 0 0 0
TASKLET: 294 225 340 254
SCHED: 3895363318 3078846722 3458120441 2541211398
HRTIMER: 803 1 11106 1
RCU: 582230863 664533554 592937241 677115643
在查看 /proc/softirqs 文件内容时,要特别注意以下这两点:
- 要注意软中断的类型,也就是这个界面中第一列的内容。从第一列你可以看到,软 中断包括了 10 个类别,分别对应不同的工作类型。比如 NET_RX 表示网络接收中断,而 NET_TX 表示网络发送中断。
- 要注意同一种软中断在不同 CPU 上的分布情况,也就是同一行的内容。正常情况 下,同一种中断在不同 CPU 上的累积次数应该差不多。比如这个界面中,NET_RX 在 CPU0 和 CPU1 上的中断次数基本是同一个数量级,相差不大。
不过,TASKLET在不同 CPU 上的分布并不均匀。TASKLET 是最常用的软中断 实现机制,每个TASKLET 只运行一次就会结束 ,并且只在调用它的函数所在的 CPU 上 运行。
因此,使用 TASKLET 特别简便,当然也会存在一些问题,比如说由于只在一个 CPU 上运 行导致的调度不均衡,再比如因为不能在多个 CPU 上并行运行带来了性能限制。
另外,软中断实际上是以内核线程的方式运行的,每个 CPU 都对应一个软中断内核线程,这个软中断内核线程就叫做 ksoftirqd/CPU 编号。那要怎么查看这些线程的 运行状况呢?
用 ps 命令就可以做到,比如执行下面的指令:
# ps aux | grep softirq
root 9 0.0 0.0 0 0 ? S 3月15 22:12 [ksoftirqd/0]
root 17 0.0 0.0 0 0 ? S 3月15 0:30 [ksoftirqd/1]
root 23 0.0 0.0 0 0 ? S 3月15 14:34 [ksoftirqd/2]
root 29 0.0 0.0 0 0 ? S 3月15 0:26 [ksoftirqd/3]
注意,这些线程的名字外面都有中括号,这说明 ps 无法获取它们的命令行参数 (cmline)。一般来说,ps 的输出中,名字括在中括号里的,一般都是内核线程。
总结
中断是一种异步的事件处理机制,用来提高系统的并发处理能力。中断事件发生,会触发执行中断处理程序,而Linux中断处理程序被分为上半部和下半部这两个部分:
- 上半部对应硬件中断,用来快速处理中断
- 下半部对应软中断,用来异步处理上半部未完成的工作
Linux 中的软中断包括网络收发、定时、调度、RCU 锁等各种类型,可以通过查看 /proc/softirqs 来观察软中断的运行情况。
在 Linux 中,每个 CPU 都对应一个软中断内核线程,名字是 ksoftirqd/CPU 编号。当软中断事件的频率过高时,内核线程也会因为 CPU 使用率过高而导致软中断处理不及时,进 而引发网络收发延迟、调度缓慢等性能问题。