为解决中断处理程序执行过长和中断丢失问题,Linux 将中断处理过程分成了两个阶段,上半部和下半部:

  • 上半部用来快速处理中断,它在中断禁止模式下运行,主要处理跟硬件紧密相关的或时间敏感的工作。
  • 下半部用来延迟处理上半部未完成的工作,通常以内核线程的方式运行。

也可以这样理解:

  • 上半部直接处理硬件请求,即硬中断,特点是快速执行。
  • 下半部由内核触发,即软中断,特点是延迟执行。

查看软中断:

$ cat /proc/softirqs
                    CPU0       CPU1       
          HI:          3          0
       TIMER:     136402     157658
      NET_TX:        440          3
      NET_RX:        290       4403
       BLOCK:      33045       9203
    IRQ_POLL:          0          0
     TASKLET:          3         93
       SCHED:      35477      38623
     HRTIMER:          0          0
         RCU:      57201      58050

案例

运行基本的 Nginx 应用:

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

查看是否运行成功:
192.168.0.30 为 Nginx 所在虚拟机的 IP。

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

在第二个中端模拟客户端请求:

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

回到第一个终端,简单的 SHELL 命令都变慢了,先查看系统的整体资源:

# 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
...

两个 CPU 的使用率虽然分别只有 3.3% 和 4.4%,但都用在了软中断上;从进程列表上,软中断进程 ksoftirqd 的 CPU 使用率最高。

查看中断次数变化率:

watch -d cat /proc/softirqs
                    CPU0       CPU1
          HI:          0          0
       TIMER:    1083906    2368646
      NET_TX:         53          9
      NET_RX:    1550643    1916776
       BLOCK:          0          0
    IRQ_POLL:          0          0
     TASKLET:     333637       3930
       SCHED:     963675    2293171
     HRTIMER:          0          0
         RCU:    1542111    1590625

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 表示网卡。
  • 第三、四列:每秒接收、发送的网络帧数,即 PPS。
  • 第五、六列:每秒接收、发送的千字节数,即 BPS。

对于网卡 eth0,rxpck/s 大于 txpck/s,rxkB/s 大于 txkB/s。
docker0 和 veth9f6bbcd 的数据量与 eth0 基本一致,只是方向相反,这是 Linux 内部网桥转发导致的。

重点看 eth0,接收的 PPS 比较大,为 12607,而接收的 BPS 却很小,只有 664 KB。
平均每隔网络帧只有 664 * 1024 / 12607 = 54 字节,是比较小的网络帧。

使用 tcpdump 抓取 eth0 上的包:

# -i eth0 只抓取 eth0 网卡,-n 不解析协议名和主机名
# tcp port 80 表示只抓取 tcp 协议并且端口号为 80 的网络帧
$ tcpdump -i eth0 -n tcp port 80
15:11:32.678966 IP 192.168.0.2.18238 > 192.168.0.30.80: Flags [S], seq 458303614, win 512, length 0
...

Flags[S] 表示是一个 SYN 包。再加上前面的 sar 发现 PPS 超过 12000 的现象,可确认是从 192.168.0.2 发来的 SYN FLOOD 攻击。

解决方式之一:从交换机或硬件防火墙封掉来源 IP。

停止 Nginx:

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

参考
倪朋飞. Linux 性能优化实战.