軟中斷與軟中斷的排查


軟中斷(softirq)CPU 使用率升高也是最常見的一種性能問題。

中斷是系統用來響應硬件設備請求的一種機制,它會打斷進程的正常調度和執行,然后調用內核中的中斷處理程序來響應設備的請求。中斷其實是一種異步的事件處理機制,可以提高系統的並發處理能力。

由於中斷處理程序會打斷其他進程的運行,所以,為了減少對正常進程運行調度的影響,中斷處理程序就需要盡可能快地運行。如果中斷本身要做的事情不多,那么處理起來也不會有太大問題;但如果中斷要處理的事情很多,中斷服務程序就有可能要運行很長時間。特別是,中斷處理程序在響應中斷時,還會臨時關閉中斷。這就會導致上一次中斷處理完成之前,其他中斷都不能響應,也就是說中斷有可能會丟失。

軟中斷

為了解決中斷處理程序執行過長和中斷丟失的問題,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
          HI:          0          0
       TIMER:     811613    1972736
      NET_TX:         49          7
      NET_RX:    1136736    1506885
       BLOCK:          0          0
    IRQ_POLL:          0          0
     TASKLET:     304787       3691
       SCHED:     689718    1897539
     HRTIMER:          0          0
         RCU:    1330771    1354737

  在查看 /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         7  0.0  0.0      0     0 ?        S    Oct10   0:01 [ksoftirqd/0]
root        16  0.0  0.0      0     0 ?        S    Oct10   0:01 [ksoftirqd/1]

  注意,這些線程的名字外面都有中括號,這說明 ps 無法獲取它們的命令行參數(cmline)。一般來說,ps  的輸出中,名字括在中括號里的,一般都是內核線程。

Linux 中的中斷處理程序分為上半部和下半部:

上半部對應硬件中斷,用來快速處理中斷。

下半部對應軟中斷,用來異步處理上半部未完成的工作。

Linux 中的軟中斷包括網絡收發、定時、調度、RCU 鎖等各種類型,可以通過查看 /proc/softirqs 來觀察軟中斷的運行情況。

分析案例

 安裝docker、sysstat、sar 、hping3、tcpdump 等工具

用到了三個工具,sar、  hping3 和 tcpdump,先簡單介紹一下:

sar 是一個系統活動報告工具,既可以實時查看系統的當前活動,又可以配置保存和報告歷史統計數據。

hping3 是一個可以構造 TCP/IP 協議數據包的工具,可以對系統進行安全審計、防火牆測試等。

tcpdump 是一個常用的網絡抓包工具,常用來分析各種網絡問題。

兩台機器:192.168.10.16跑nginx與PHP應用;192.168.10.18跑hping3

 

 docker run -itd --name=nginx -p 80:80 nginx

  檢查是否正常

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

  在第二個終端,我們運行 hping3 命令,來模擬 Nginx 的客戶端請求:

# -S參數表示設置TCP協議的SYN(同步序列號),-p表示目的端口為80
# -i u100表示每隔100微秒發送一個網絡幀
# 注:如果你在實踐過程中現象不明顯,可以嘗試把100調小,比如調成10甚至1
$ hping3 -S -p 80 -i u1 192.168.10.16

第一個終端運行 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%,還是不高呀。

仔細看 top 的輸出,兩個 CPU 的使用率雖然分別只有 3.3% 和 4.4%,但都用在了軟中斷上;而從進程列表上也可以看到,CPU 使用率最高的也是軟中斷進程 ksoftirqd。看起來,軟中斷有點可疑了。

 軟中斷可能有問題, proc 文件系統。觀察 /proc/softirqs 文件的內容,就能知道各種軟中斷類型的次數。

不過,這里的各類軟中斷次數,它是系統運行以來的累積中斷次數。所以我們直接查看文件內容,得到的只是累積中斷次數,對這里的問題並沒有直接參考意義。因為,這些中斷次數的變化速率才是需要關注的。

watch 命令,就可以定期運行一個命令來查看輸出;如果再加上 -d 參數,還可以高亮出變化的部分,從高亮部分我們就可以直觀看出,哪些內容變化得更快。

 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

  通過 /proc/softirqs 文件內容的變化情況,你可以發現, TIMER(定時中斷)、NET_RX(網絡接收)、SCHED(內核調度)、RCU(RCU 鎖)等這幾個軟中斷都在不停變化。其中,NET_RX,也就是網絡數據包接收軟中斷的變化速率最快。而其他幾種類型的軟中斷,是保證 Linux 調度、時鍾和臨界區保護這些正常工作所必需的,所以它們有一定的變化倒是正常的。那么接下來,就從網絡接收的軟中斷着手,繼續分析。既然是網絡接收的軟中斷,第一步應該就是觀察系統的網絡接收情況。sar  可以用來查看系統的網絡收發情況,還有一個好處是,不僅可以觀察網絡收發的吞吐量(BPS,每秒收發的字節數),還可以觀察網絡收發的 PPS,即每秒收發的網絡幀數。第一個終端中運行 sar 命令,並添加 -n DEV 參數顯示網絡收發的報告:

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

  對於 sar 的輸出界面

第一列:表示報告的時間。

第二列:IFACE 表示網卡。

第三、四列:rxpck/s 和 txpck/s 分別表示每秒接收、發送的網絡幀數,也就是  PPS。

第五、六列:rxkB/s 和 txkB/s 分別表示每秒接收、發送的千字節數,也就是  BPS。

后面的其他參數基本接近 0,顯然跟今天的問題沒有直接關系,可以先忽略掉。

對網卡 eth0 來說,每秒接收的網絡幀數比較大,達到了 12607,而發送的網絡幀數則比較小,只有 6304;每秒接收的千字節數只有 664 KB,而發送的千字節數更小,只有 358 KB。docker0 和 veth9f6bbcd 的數據跟 eth0 基本一致,只是發送和接收相反,發送的數據較大而接收的數據較小。這是 Linux 內部網橋轉發導致的,你暫且不用深究,只要知道這是系統把 eth0 收到的包轉發給 Nginx 服務即可。

既然懷疑是網絡接收中斷的問題,還是重點來看 eth0 :接收的 PPS 比較大,達到 12607,而接收的 BPS 卻很小,只有 664 KB。直觀來看網絡幀應該都是比較小的,我們稍微計算一下,664*1024/12607 = 54 字節,說明平均每個網絡幀只有 54 字節,這顯然是很小的網絡幀,也就是通常所說的小包問題。

使用 tcpdump 抓取 eth0 上的包就可以了。 Nginx 監聽在 80 端口,它所提供的 HTTP 服務是基於 TCP 協議的,所以可以指定 TCP 協議和 80 端口精確抓包。

接下來,第一個終端中運行 tcpdump 命令,通過 -i eth0 選項指定網卡 eth0,並通過 tcp port 80 選項指定 TCP 協議的 80 端口:

# -i eth0 只抓取eth0網卡,-n不解析協議名和主機名
# tcp port 80表示只抓取tcp協議並且端口號為80的網絡幀
[root@linux-xingnengyouhua ~]# tcpdump -i eth0 -n tcp port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:48:43.005197 IP 192.168.10.18.59682 > 192.168.10.16.http: Flags [S], seq 662979361, win 512, length 0
10:48:43.005218 IP 192.168.10.18.59683 > 192.168.10.16.http: Flags [S], seq 781851836, win 512, length 0

  192.168.10.18.59682 > 192.168.10.16.80  ,表示網絡幀從 192.168.10.18 的 59682端口發送到 192.168.10.16 的 80 端口,也就是從運行 hping3 機器的 59682端口發送網絡幀,目的為 Nginx 所在機器的 80 端口。Flags [S] 則表示這是一個 SYN 包。

再加上前面用 sar 發現的, PPS 超過 12000 的現象,現在可以確認,這就是從 192.168.10.18 這個地址發送過來的 SYN FLOOD 攻擊。

從系統的軟中斷使用率高這個現象出發,通過觀察 /proc/softirqs 文件的變化情況,判斷出軟中斷類型是網絡接收中斷;再通過 sar 和 tcpdump  ,確認這是一個 SYN FLOOD 問題。SYN FLOOD 問題最簡單的解決方法,就是從交換機或者硬件防火牆中封掉來源 IP,這樣 SYN FLOOD 網絡幀就不會發送到服務器中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM