軟中斷與軟中斷的排查
軟中斷(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 網路幀就不會發送到伺服器中。