10 | 系统的软中断CPU使用率升高

160 阅读4分钟

1. 概述

中断是一种异步的事件处理机制,用来提高系统的并发处理能力。中断事件发生,会触发执行中断处理程序,而中断处理程序被分为上半部和下半部这两个部分。

  • 上半部对应硬中断,用来快速处理中断;
  • 下半部对应软中断,用来异步处理上半部未完成的工作。

Linux 中的软中断包括网络收发、定时、调度、RCU 锁等各种类型,我们可以查看 proc 文件系统中的 /proc/softirqs ,观察软中断的运行情况。

当软中断事件的频率过高时,内核线程也会因为 CPU 使用率过高而导致软中断处理不及时,进而引发网络收发延迟、调度缓慢等性能问题。

2. 案例

环境准备:

  • Ubuntu 18.04
  • 机器配置:2 CPU、8 GB 内存。
  • 预先安装 docker、sysstat、sar 、hping3、tcpdump 等工具,比如 apt-get install docker.io sysstat hping3 tcpdump。

三个新工具,sar、 hping3 和 tcpdump,先简单介绍一下:

  • sar 是一个系统活动报告工具,既可以实时查看系统的当前活动,又可以配置保存和报告历史统计数据。
  • hping3 是一个可以构造 TCP/IP 协议数据包的工具,可以对系统进行安全审计、防火墙测试等。
  • tcpdump 是一个常用的网络抓包工具,常用来分析各种网络问题。

本次案例用到两台虚拟机(IP仅供参考)

VM 1执行

# 运行Nginx服务并对外开放80端口
$ docker run -itd --name=nginx -p 80:80 nginx

VM 2执行,IP填写VM 1

$ curl http://172.30.104.143/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

继续VM 2执行

# -S参数表示设置TCP协议的SYN(同步序列号),-p表示目的端口为80
# -i u100表示每隔100微秒发送一个网络帧
# 注:如果你在实践过程中现象不明显,可以尝试把100调小,比如调成10甚至1
$ hping3 -S -p 80 -i u100 172.30.104.143

此时会发现VM 1会有点卡(我的机器没有出现,因为我是在我的物理机上部署了两个VM,并且本地SSH连过去,基本上没有网络瓶颈,如果想复现VM 1卡顿现象,可以在远端Server部署两台虚拟机,通过VPN或者其他网络不是很好的方式通过SSH连接,会复现。),不影响分析案例

先用top查看资源整体情况

# top运行后按数字1切换到显示所有CPU
$ top
top - 10:50:58 up 1 days, 22:10,  1 user,  load average: 0.00, 0.00, 0.00
Tasks: 122 total,   1 running,  71 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.0 sy,  0.0 ni, 96.7 id,  0.0 wa,  0.0 hi,  3.3 si,  0.0 st
%Cpu1  :  0.0 us,  0.0 sy,  0.0 ni, 95.6 id,  0.0 wa,  0.0 hi,  4.4 si,  0.0 st
...

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    7 root      20   0       0      0      0 S   0.3  0.0   0:01.64 ksoftirqd/0
   16 root      20   0       0      0      0 S   0.3  0.0   0:01.97 ksoftirqd/1
 2663 root      20   0  923480  28292  13996 S   0.3  0.3   4:58.66 docker-containe
 3699 root      20   0       0      0      0 I   0.3  0.0   0:00.13 kworker/u4:0
 3708 root      20   0   44572   4176   3512 R   0.3  0.1   0:00.07 top
    1 root      20   0  225384   9136   6724 S   0.0  0.1   0:23.25 systemd
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.03 kthreadd
...
  • 平均负载全是 0,就绪队列里面只有一个进程(1 running)。
  • 每个 CPU 的使用率都挺低,最高的 CPU1 的使用率也只有 4.4%,并不算高。
  • 再看进程列表,CPU 使用率最高的进程也只有 0.3%,使用率不高。

唯独ksoftirqd比较可疑。既然是软中断,那就查看/proc/softirqs

$ watch -d cat /proc/softirqs

-d表示高亮变化的部分。 TIMER(定时中断)、NET_RX(网络接收)、SCHED(内核调度)、RCU(RCU 锁)等这几个软中断都在不停变化。其中,NET_RX,也就是网络数据包接收软中断的变化速率最快。

既然与网络相关,那么可以使用今天的新工具 sar

# -n DEV 表示显示网络收发的报告,间隔1秒输出一组数据
$ sar -n DEV 1
15:03:46        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s   %ifutil
15:03:47         eth0  12607.00   6304.00    664.86    358.11      0.00      0.00      0.00      0.01
15:03:47      docker0   6302.00  12604.00    270.79    664.66      0.00      0.00      0.00      0.00
15:03:47           lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00
15:03:47    veth9f6bbcd   6302.00  12604.00    356.95    664.66      0.00      0.00      0.00      0.05

指标介绍:

  • 第一列:表示报告的时间。
  • 第二列:IFACE 表示网卡。
  • 第三、四列:rxpck/s 和 txpck/s 分别表示每秒接收、发送的网络帧数,也就是 PPS。
  • 第五、六列:rxkB/s 和 txkB/s 分别表示每秒接收、发送的千字节数,也就是 BPS。
  • 后面的其他参数基本接近 0,显然跟今天的问题没有直接关系,你可以先忽略掉。

那么从上图中的最后一行可以看到,eth0的接收PPS达到了13264、但是包大小只有60KB。典型的小包传送。此时需要进一步知道是什么样的包,传了什么,就需要用到tcpdump抓包。

# -i eth0 只抓取eth0网卡,-n不解析协议名和主机名
# tcp port 80表示只抓取tcp协议并且端口号为80的网络帧
$ tcpdump -i eth0 -n tcp port 80

对于网络不是很熟悉,借助一下AI

Flags [S] 则表示这是一个 SYN 包。

继续询问

好吧,后面大家可以执行尝试。对于我这种网络小白,有了GPT之后友好都了。。。那如何解决呢?

SYN FLOOD 问题最简单的解决方法,就是从交换机或者硬件防火墙中封掉来源 IP,这样 SYN FLOOD 网络帧就不会发送到服务器中。

回收实验环境

# 停止 Nginx 服务
$ docker rm -f nginx

VM 2上的hping3也要停掉。

附录:

在查看软中断中,每个核是按列来显示的,如果存在及时个核心的场景看起来会比较困难,此时可以借助下面的命令

watch -d "/bin/cat /proc/softirqs | /usr/bin/awk 'NR == 1{printf "%-15s %-15s %-15s %-15s %-15s\n"," ",$1,$2,$3,$4}; NR > 1{printf "%-15s %-15s %-15s %-15s %-15s\n",$1,$2,$3,$4,$5}'"

还有一个需要关注,top命令中,我们可以看到 si 也就是软中断全部发生在CPU 1上面,此时网络转发就会成为瓶颈,在多核处理器上可以通过 配置 smp_affinity 或者开启 irqbalance 服务 将网络中断配置在多个核中。

参考命令如下:

echo "3" > /sys/class/net/eth0/queues/rx-0/rps_cpus
  • 单核 1 表示 CPU 0。
  • 双核 3 表示 CPU 0 和 1。
  • 四核 f 表示 CPU 0、1、2、3。