系統性能分析之|iowait 是什麼?
我們對系統性能進行優化時,一般會使用 top
命令來查看系統負載和系統中各個進程的運行情況,從而找出影響系統性能的因素。如下圖所示:
top
命令會輸出很多系統相關的信息,如:系統負載、系統中的進程數、CPU 使用率和內存使用率等,這些信息對排查系統性能問題起着至關重要的作用。
本文主要介紹 top
命令中的 iowait
指標(如上圖中紅色方框所示)的含義和作用。
什麼是 iowait
什麼是 iowait
?我們來看看 Linux 的解釋:
Show the percentage of time that the CPU or CPUs were idle during which the system had an outstanding disk I/O request.
中文翻譯的意思就是:CPU 在等待磁盤 I/O 請求完成時,處於空閒狀態的時間百分比(此時正在運行着 idle
進程)。
可以看出,如果系統處於 iowait
狀態,那麼必須滿足以下兩個條件:
-
系統中存在等待 I/O 請求完成的進程。
-
系統當前正處於空閒狀態,也就是說沒有可運行的進程。
iowait 統計原理
既然我們知道了 iowait
的含義,那麼接下來看看 Linux 是怎麼統計 iowait
的比率的。
Linux 會把 iowait
佔用的時間輸出到 /proc/stat
文件中,我們可以通過一下命令來獲取到 iowait
佔用的時間:
cat /proc/stat
命令輸出如下圖所示:
紅色方框中的數據就是 iowait
佔用的時間。
我們可以每隔一段時間讀取一次 /proc/stat
文件,然後把兩次獲取到的 iowait
時間進行相減,得到的結果是這段時間內,CPU 處於 iowait
狀態的時間。接着再將其除以總時間,得到 iowait
佔用總時間的比率。
現在我們來看看 /proc/stat
文件是怎樣獲取 iowait
的時間的。
在內核中,每個 CPU 都有一個 cpu_usage_stat
結構,主要用於統計 CPU 一些信息,其定義如下:
struct cpu_usage_stat {
cputime64_t user;
cputime64_t nice;
cputime64_t system;
cputime64_t softirq;
cputime64_t irq;
cputime64_t idle;
cputime64_t iowait;
cputime64_t steal;
cputime64_t guest;
cputime64_t guest_nice;
};
cpu_usage_stat
結構的 iowait
字段記錄了 CPU 處於 iowait
狀態的時間。
所以要獲取系統處於 iowait
狀態的總時間,只需要將所有 CPU 的 iowait
時間相加即可,代碼如下(位於源文件 fs/proc/stat.c
):
static int show_stat(struct seq_file *p, void *v)
{
u64 iowait;
...
// 1. 遍歷系統中的所有CPU
for_each_possible_cpu(i) {
...
// 2. 獲取CPU對應的iowait時間,並相加
iowait = cputime64_add(iowait, kstat_cpu(i).cpustat.iowait);
...
}
...
return 0;
}
show_stat()
函數首先會遍歷所有 CPU,然後讀取其 iowait
時間,並且將它們相加。
增加 iowait 時間
從上面的分析可知,每個 CPU 都有一個用於統計 iowait
時間的計數器,那麼什麼時候會增加這個計數器呢?
答案是:系統時鐘中斷
。
在 系統時鐘中斷
中,會調用 account_process_tick()
函數來更新 CPU 的時間,代碼如下:
void account_process_tick(struct task_struct *p, int user_tick)
{
cputime_t one_jiffy_scaled = cputime_to_scaled(cputime_one_jiffy);
struct rq *rq = this_rq();
// 1. 如果當前進程處於用戶態,那麼增加用戶態的CPU時間
if (user_tick) {
account_user_time(p, cputime_one_jiffy, one_jiffy_scaled);
}
// 2. 如果前進程處於內核態,並且不是idle進程,那麼增加內核態CPU時間
else if ((p != rq->idle) || (irq_count() != HARDIRQ_OFFSET)) {
account_system_time(p, HARDIRQ_OFFSET, cputime_one_jiffy,
one_jiffy_scaled);
}
// 3. 如果當前進程是idle進程,那麼調用account_idle_time()函數進行處理
else {
account_idle_time(cputime_one_jiffy);
}
}
我們主要關注當前進程是 idle
進程的情況,這是內核會調用 account_idle_time()
函數進行處理,其代碼如下:
void account_idle_time(cputime_t cputime)
{
struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat;
cputime64_t cputime64 = cputime_to_cputime64(cputime);
struct rq *rq = this_rq();
// 1. 如果當前有進程在等待IO請求的話,那麼增加iowait的時間
if (atomic_read(&rq->nr_iowait) > 0) {
cpustat->iowait = cputime64_add(cpustat->iowait, cputime64);
}
// 2. 否則增加idle的時間
else {
cpustat->idle = cputime64_add(cpustat->idle, cputime64);
}
}
account_idle_time()
函數的邏輯比較簡單,主要分以下兩種情況進行處理:
-
如果當前有進程在等待 I/O 請求的話,那麼增加
iowait
的時間。 -
如果當前沒有進程在等待 I/O 請求的話,那麼增加
idle
的時間。
所以,從上面的分析可知,要增加 iowait
的時間需要滿足以下兩個條件:
-
當前進程是
idle
進程,也就是說 CPU 處於空閒狀態。 -
有進程在等待 I/O 請求完成。
進一步說,當 CPU 處於 iowait
狀態時,說明 CPU 處於空閒狀態,並且系統中有進程因爲等待 I/O 請求而阻塞,也說明了 CPU 的利用率不夠充分。
這時,我們可以使用異步 I/O(如 iouring
)來優化程序,使得進程不會被 I/O 請求阻塞。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/U_1ei72YfIMMFVs-HoW-Zg