系統性能分析之|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 狀態,那麼必須滿足以下兩個條件:

  1. 系統中存在等待 I/O 請求完成的進程。

  2. 系統當前正處於空閒狀態,也就是說沒有可運行的進程。

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() 函數的邏輯比較簡單,主要分以下兩種情況進行處理:

  1. 如果當前有進程在等待 I/O 請求的話,那麼增加 iowait 的時間。

  2. 如果當前沒有進程在等待 I/O 請求的話,那麼增加 idle 的時間。

所以,從上面的分析可知,要增加 iowait 的時間需要滿足以下兩個條件:

  1. 當前進程是 idle 進程,也就是說 CPU 處於空閒狀態。

  2. 有進程在等待 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