性能分析小案例系列,可以通過下面連結檢視哦
https://www.cnblogs.com/poloyy/category/1814570.html
前面兩個案例講的都是上下文切換導緻的 CPU 使用率升高
這一篇就來講講等待 I/O 導緻的 CPU 使用率升高的案例
https://www.cnblogs.com/poloyy/p/13413770.html
當 iowait 升高時,程序很可能因為得不到硬體的響應,而長時間處于不可中斷狀态
不可中斷也是為了保護程序資料和硬體狀态一緻,并且正常情況下,不可中斷狀态在很短時間内就會結束
是以,短時的不可中斷程序,一般可以忽略
但如果系統或硬體發生了故障,程序可能會在不可中斷狀态保持很久,甚至導緻系統中出現大量不可中斷程序。這時,就得注意下,系統是不是出現了 I/O 等性能問題
多程序引用很容易碰到的問題
一個程序建立了子程序後,它應該通過系統調用 wait() 或 waitpid() 等待子程序結束,回收子程序的資源
而子程序在結束時,會向它的父程序發送 SIGCHLD 信号
是以,父程序還可以注冊 SIGCHLD 信号的處理函數,異步回收資源
如果父程序沒有回收資源,或是子程序執行太快,父程序還沒來得及處理子程序狀态,子程序就已經提前退出,那這時的子程序就會變成僵屍程序
形象比喻:父親應該一直對兒子負責, 善始善終,如果不作為或者跟不上,都會導緻“問題少年”的出現
僵屍程序持續的時間都比較短,在父程序回收它的資源後就會消亡,或者在父程序退出後,由 init 程序回收後也會消亡
一旦父程序沒有處理子程序的終止,還一直保持運作狀态,那麼子程序就會一直處于僵屍狀态
大量的僵屍程序會用盡 PID 程序号,導緻新程序不能建立
Ubuntu 18.04, 2 CPU,2GB 記憶體
前置條件:已運作案例應用
多個 app 程序已啟動
狀态有 Ss+、D+、R+
小s:表示這個程序是一個會話的上司程序
+:表示前台程序組
它們是用來管理一組互相關聯的程序
程序組:比如每個子程序都是父程序所在組的成員
會話:共享同一個控制終端的一個或多個程序組
通過 SSH 登入伺服器,就會打開一個控制終端(TTY),這個控制終端就對應 一個會話
而在終端中運作的指令以及它們的子程序,就構成了一個個的程序組
在背景運作的指令,構成背景程序組
在前台運作的指令,構成前台程序組
平均負載,過去 1min、5min、15min 的平均負載依次減少,說明平均負載正在升高
而 1min 内的平均負載已經達到系統 CPU 個數,說明系統很可能存在性能瓶頸
115 zombie 說明僵屍程序比較多,而且在不停增加,有子程序在退出時沒被清理
使用者 CPU 和系統 CPU 都不高,但 iowait 分别是 60.5% 和 94.6%,好像有點兒不正常,導緻系統的平均負載升高
有兩個處于 D 狀态的 app 程序,可能在等待 I/O
一堆 app 僵屍程序
一提到 iowait 升高,首先會想要查詢系統的 I/O 情況
當 iowait 升高(wai)時,磁盤的讀請求(read)都會很大(M)
這說明 iowait 的升高跟磁盤的讀請求有關,很可能就是讀磁盤導緻的
通過 top 找到 D 狀态的兩個 app 程序
不可中斷狀态代表程序在跟硬體進行互動,很可能就是讀磁盤
兩個 app 程序的 PID 分别是12407、12406
-d 展示 I/O 統計資料
-p 指定程序号
間隔 1 秒輸出 5 組資料
kB_rd 表示每秒讀的 KB 數, kB_wr 表示每秒寫的 KB 數,iodelay 表示 I/O 的延遲(機關是時鐘周期)
它們都是 0,那就表示此時沒有任何的讀寫,說明問題不 是 12407 程序導緻的,也并不是12406 程序導緻的
能看到其實的确是 app 程序在讀,隻不過每過幾秒都會有新的 app 程序在讀【pid 在不斷變化】
可以确認,是 app 程序的問題
前面講到讀磁盤的 app 程序 PID 一直在變化,那麼就來看看已經沒在讀磁盤的程序的程序狀态是怎麼樣的
這程序已經是 Z 狀态,就是僵屍程序了
僵屍程序都是已經退出的程序, 是以就沒法兒繼續分析它的系統調用
關于僵屍程序的處理方法,我們一會兒再說,現在還是繼續分析 iowait 的問題
系統 iowait 的問題還在繼續,但是 top、pidstat 這類工具已經不能給出更多的資訊了
此時可以通過 perf 動态跟蹤性能事件
15s 後 ctrl+c 終止錄制
app 的确在通過系統調用 sys_read() 讀取資料
并且從 new_sync_read 和 blkdev_direct_IO 能看出,程序正在對磁盤進行直接讀,也就是繞過了系統緩存,每個讀請求都會從磁盤直接讀,這就可以解釋觀察到的 iowait 升高了

iowait 已經非常低了,隻有 0.3%
說明修改源碼已經成功修複了 iowait 高的問題
不過,仔細觀察僵屍程序的數量,會發現,僵屍程序還在不斷的增長中
僵屍程序是因為父程序沒有回收子程序的資源而出現的
解決僵屍程序需要先找出父程序,然後在父程序裡解決
51780 程序的父程序是 51688,也就是 app 應用
所有僵屍程序的父程序都是 51688,進而确認 51688 就是僵屍程序的父程序
檢視 app 應用程式的代碼,看看子程序結束的處理是否正确
有沒有調用 wait() 或 waitpid()
或有沒有注冊 SIGCHLD 信号的處理函數
把 wait() 放到了 for 死循環的外面,也就是說, wait() 函數實際上并沒被調用到,把它挪到 for 循環的裡面就可以了
僵屍程序(Z 狀态)沒有了, iowait 也是 0,問題終于全部解決了
這個案例是因為磁盤 I/O 導緻了 iowait 升高
不過,iowait 高并不一定代表 I/O 有性能瓶頸
當系統中隻有 I/O 類型的程序在運作時,iowait 也會很高,但實際上,磁盤的讀寫遠沒有達到性能瓶頸的程度
通過 top 檢視系統資源情況
發現平均負載逐漸升高,iowait(wa)比較高,但使用者态和核心态 CPU 使用率并不算高
檢視是否有 CPU 使用率偏高的程序,發現有 D 狀态的程序,可能是在等待 I/O 中
過一陣子會變成 Z 狀态程序,且 CPU 使用率上升,然後會看到 zombie 程序數逐漸增加
可以得到兩個結論:僵屍程序過多,應該是父程序沒有清理已經結束的子程序的資源;iowait 的上升導系統平均負載上升
因為是 iowait 較高,可以通過 dstat 檢視系統的 I/O 情況,會發現每次 iowait 升高,讀磁盤請求都會很大
通過 pidstat -d 檢視 D 狀态程序的 I/O 情況,但發現并沒有有效資訊
通過 pidstat -d 直接檢視系統的 I/O 情況,可以發現不斷有新程序在進行讀磁盤操作
通過 ps 指令檢視剛剛 D 狀态程序目前的程序狀态,發現已經變成僵屍程序
通過 perf record 錄制性能事件,然後通過 perf report 檢視性能報告,可以發現 app 程序都是直接讀磁盤,而不經過系統緩存
通過 pstree 找到 Z 狀态程序的父程序
通過 ps 指令确認所有僵屍程序的父程序
找到父程序源代碼,檢查 wait() / waitpid() 的是否會成功調用,或是 SIGCHLD 信号處理函數的注冊就行了
修改完全部源碼後,重新運作應用,通過 top 驗證是否還有 iowait 過高和出現 zombie 程序的情況