本文中,我们将探究 Linux 中 cpu 使用率 iowait 定义和计算方式。iowait 很高的系统,一定存在 IO 瓶颈吗?通过实验验证和讨论了综合观测系统的 IO 压力的方式。
Table of Contents
0 tl;dr
- iowait% 是 CPU
空闲且有 disk I/O 任务
所占的时间比例。 - cpu 处于 iowait 状态时,仍然可以处理其他计算密集型任务。
- iowait 高,反映有大量 cpu 空闲时间在等待 IO。
- 如果有 IO 密集型和计算密集型任务同时存在在系统中,iowait 可能很低。
- 系统的 IO 瓶颈,需要结合其他工具 (比如
iostat
) 综合判断。
1 引入和问题
在定位性能瓶颈时,我们首先会看的状态便是 cpu 使用率(user, system, iowait, idle)。
按照经验,iowait 很高的系统,很可能存在 IO 瓶颈。但
- iowait 究竟是怎么计算得来的?
- 一个存在 IO 瓶颈的系统,iowait 一定很高吗?
- 反过来讲,iowait 很高,系统一定不健康吗?
2 iowait
2.1 CPU 使用率
我们可以通过系统工具的手册查看具体 cpu 使用定义。
man top
us, user : time running un-niced user processes
sy, system : time running kernel processes
ni, nice : time running niced user processes
id, idle : time spent in the kernel idle handler
wa, IO-wait : time waiting for I/O completion
hi : time spent servicing hardware interrupts
si : time spent servicing software interrupts
st : time stolen from this vm by the hypervisor
iowait
代表的是用于等待 I/O 完成的时间。
似乎没有解释明白。
man sar
%user
Percentage of CPU utilization that occurred while executing at the user level (application). Note that this field includes time spent running virtual processors.
%usr
Percentage of CPU utilization that occurred while executing at the user level (application). Note that this field does NOT include time spent running virtual processors.
%nice
Percentage of CPU utilization that occurred while executing at the user level with nice priority.
%system
Percentage of CPU utilization that occurred while executing at the system level (kernel). Note that this field includes time spent servicing hardware and software interrupts.
%sys
Percentage of CPU utilization that occurred while executing at the system level (kernel). Note that this field does NOT include time spent servicing hardware or software interrupts.
%iowait
Percentage of time that the CPU or CPUs were idle during which the system had an outstanding disk I/O request.
......
sar
明确指出,iowait 是 CPU 空闲且有 disk I/O 任务
所占的时间比例。
这意味在 cpu 处于 iowait 状态时,仍然可以被调度其他进程。
2.2 iowait 的来源和定义
我们使用的性能工具一般是通过读取系统状态文件获得的。CPU 原始信息可以查看系统文件 cat /proc/stat
获得。
cpu 71312 7195 54602 378230 320199 0 61952 0 0 0
cpu0 19890 1985 15222 92986 91033 0 177 0 0 0
cpu1 11611 1584 10664 91301 53700 0 60426 0 0 0
cpu2 20344 1776 13989 90313 95029 0 92 0 0 0
cpu3 19466 1849 14727 103627 80436 0 1256 0 0 0
......
通过阅读 proc 手册可以清晰地得到各列的定义:
man proc
/proc/stat
kernel/system statistics. Varies with architecture. Common entries include:
cpu 10132153 290696 3084719 46828483 16683 0 25195 0 175628 0
cpu0 1393280 32966 572056 13343292 6130 0 17875 0 23933 0
The amount of time, measured in units of USER_HZ (1/100ths of a second on most architectures, use sysconf(_SC_CLK_TCK) to obtain the right value), that the system ("cpu" line) or the specific CPU
("cpuN" line) spent in various states:
user (1) Time spent in user mode.
nice (2) Time spent in user mode with low priority (nice).
system (3) Time spent in system mode.
idle (4) Time spent in the idle task. This value should be USER_HZ times the second entry in the /proc/uptime pseudo-file.
iowait (since Linux 2.5.41)
(5) Time waiting for I/O to complete. This value is not reliable, for the following reasons:
1. The CPU will not wait for I/O to complete; iowait is the time that a task is waiting for I/O to complete. When a CPU goes into idle state for outstanding task I/O, another task will be
scheduled on this CPU.
2. On a multi-core CPU, the task waiting for I/O to complete is not running on any CPU, so the iowait of each CPU is difficult to calculate.
3. The value in this field may decrease in certain conditions.
没有想到,我们关于 iowait 的疑惑可以轻轻松松通过系统手册获得解答:
- CPU 不会等待 IO 完成,iowait 是某个 task 等待 IO 完成的时间。
- 当一个 cpu 因为显著的 task I/O 进入空闲状态时,另一个 task 可以被调度到它。
- 在多核系统中,因为等待 IO 的 task 不在任何 cpu 上运行,所以每个核心精确的 iowait 难以被计算。
2.3 iowait linux 源码小探
为了验证我们的想法,根据 /proc/stat
系统文件实现,我们反向追踪 linux 内核是如何记录 iowait 状态的。
注:本文跟踪的内核版本是 v6.5.1
。
a. stat 系统文件
stat 系统文件实现在 linux/fs/proc/stat.c
。通过 get_iowait_time
获取并打印。
值得注意的是,实现中基本将 idle_time
和 iowait_time
做了相似的处理。
for_each_online_cpu(i) {
struct kernel_cpustat kcpustat;
u64 *cpustat = kcpustat.cpustat;
kcpustat_cpu_fetch(&kcpustat, i);
/* Copy values here to work around gcc-2.95.3, gcc-2.96 */
user = cpustat[CPUTIME_USER];
nice = cpustat[CPUTIME_NICE];
system = cpustat[CPUTIME_SYSTEM];
idle = get_idle_time(&kcpustat, i);
iowait = get_iowait_time(&kcpustat, i);
irq = cpustat[CPUTIME_IRQ];
softirq = cpustat[CPUTIME_SOFTIRQ];
steal = cpustat[CPUTIME_STEAL];
guest = cpustat[CPUTIME_GUEST];
guest_nice = cpustat[CPUTIME_GUEST_NICE];
seq_printf(p, "cpu%d", i);
seq_put_decimal_ull(p, " ", nsec_to_clock_t(user));
//...
seq_putc(p, '\n');
}
b. tick_sched
结构体
从 tick_sched
计时结构体中获得 iowait 时间。结构体如下
linux/kernel/time/tick-sched.h
/**
* struct tick_sched - sched tick emulation and no idle tick control/stats
*
* @inidle: Indicator that the CPU is in the tick idle mode
......
* @idle_sleeptime: Sum of the time slept in idle with sched tick stopped
* @iowait_sleeptime: Sum of the time slept in idle with sched tick stopped, with IO outstanding
......
*/
struct tick_sched {
/* Common flags */
unsigned int inidle : 1;
......
/* Idle exit */
ktime_t idle_exittime;
ktime_t idle_sleeptime;
ktime_t iowait_sleeptime;
......
};
c. 通过 cpu 调度器 runqueue
信息判断 iowait
状态
idle_sleeptime
和 iowait_sleeptime
由内核计算。
判断一个 cpu 核心空闲时,在不在 iowait
状态,可通过 rq->nr_iowait > 0
判断。
kernel/sched/cputime.c
void account_idle_time(u64 cputime)
{
u64 *cpustat = kcpustat_this_cpu->cpustat;
struct rq *rq = this_rq();
if (atomic_read(&rq->nr_iowait) > 0)
cpustat[CPUTIME_IOWAIT] += cputime;
else
cpustat[CPUTIME_IDLE] += cputime;
}
其中,rq
为每个 CPU 的 runqueue 数据结构。
runqueue
部分组成如下,包含很多的计数变量。(nr_iowait: number of iowait)
kernel/sched/sched.h
/*
* This is the main, per-CPU runqueue data structure.
......
*/
struct rq {
/* runqueue lock: */
raw_spinlock_t __lock;
......
unsigned int nr_running;
......
u64 nr_switches;
......
unsigned int nr_uninterruptible;
......
atomic_t nr_iowait;
....
};
其中 nr_iowait
在调度器调度 task 时候更新。每次更新也会更新 IO 延迟状态信息 delayacct_blkio_start
,可用于分析进程的 io 负载。
if (p->in_iowait) {
delayacct_blkio_end(p);
atomic_dec(&task_rq(p)->nr_iowait);
}
......
if (prev->in_iowait) {
atomic_inc(&rq->nr_iowait);
delayacct_blkio_start();
}
通过翻阅源码,我们可以得知,iowait
基本上是一种 CPU 空闲的状态。
3实验
3.1 试着让 iowait 逼近 100%!
目标:尝试让你的系统 iowait 接近 100%!
根据我们上面的分析,只需要在一个空闲的系统中,堆满重度 IO 任务即可。
笔者使用的一个 4 核心的 x86 机器,启动了 6 个重度 IO 任务:
dd if=/dev/sde1 of=/dev/null bs=1MB
sde1
是我的一块机械硬盘,它的速度足够慢。
启动 6 个 io 任务后,查看系统 cpu 使用情况:
mpstat -P ALL 5 # 每 5 秒输出
可以看到,系统 iowait 达到了 87%, idle 状态基本不存在。
因为 sde1
并不是系统盘,现在系统响应未感到卡顿。
这里的高 iowait,更意味着现在系统有大量 cpu 空闲时间在等待 IO。可以和 cpu 密集型应用混布,或尝试优化高 iowait 程序。
3.2 高 io 压力,但系统 iowait 很小
在 3.1 的基础上,我们添加计算密集型任务:
stress --cpu 4 --timeout 600 # 计算密集型压力,压测 4 核心
我们成功抢走了 cpu 的 iowait 时间,cpu 时间大部分集中在计算密集型任务上。
但同时,sde 仍然在机械硬盘的 IO 瓶颈上。
➜ ~ iostat -s 4 -h # 每 4 秒输出 io 状态
Linux 6.2.0-31-generic (pi-HP-t610) 2023年09月03日 _x86_64_ (4 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
93.1% 0.4% 6.6% 0.0% 0.0% 0.0%
tps kB_read/s kB_w+d/s kB_read kB_w+d Device
666.75 81.4M 0.0k 325.7M 0.0k sde
这意味着,我们需要综合判断系统是否存在 IO 瓶颈。
一个高负载的 IO 系统,可能并不会体现出高 iowait。
4 综合观测系统 IO 压力
综上,实际排查 IO 瓶颈时,还要结合多种工具判断。比如 iostat
, iotop
。
iostat -c -h -d -x 2 #显示 CPU 和磁盘使用情况,每 2 秒刷新
笔者会再写一篇文章,分享自己用过的一些工具。
5 小结
- iowait 是 CPU
空闲且有 disk I/O 任务
所占的时间比例。 - cpu 处于 iowait 状态时,仍然可以处理其他计算密集型任务。
- iowait 高,反映有大量 cpu 空闲时间在等待 IO。
- 如果有 IO 密集型和计算密集型任务同时存在在系统中,iowait 可能很低。
- 系统的 IO 瓶颈,需要结合其他工具 (比如
iostat
) 综合判断
我的理解是:
IOWait 高, 說明 IO 一定繁忙, 但是IOwait 低, IO 可能繁忙或者不繁忙.
理由:
從proc的手冊上看, 1,2,3點理由, 都在說少算了CPU在等待 IO 完成的 idle 時間.
這是我的理解,請多多指教.
居然还在坚持更新网站,太牛了,佩服这种持续学习的能力w
@fandy 感谢夸夸