「iowait」究竟是什么?

本文翻译自《What exactly is “iowait”?》,格式与部分内容略有调整。

原文发布于 2004 年 4 月的某 AIX-L 邮件列表上,原始地址已失效,原文可参见备份地址

iowait,也就是 top 中见到的「wa」、Proxmox VE 面板上看到的「IO delay」,指示着 CPU 有多少时间用于 IO 操作上。

等等,现代的计算机的 IO 还需要 CPU 的全程参与吗?难道 CPU 不该进入睡眠状态或者忙其他的吗?iowait 可以指示 IO 是否成为瓶颈,又是真的吗?

搜了一圈,发现这篇文章讲解得比较清晰,故采而译之。

一言以蔽之,「iowait」指的是 CPU 空闲并且存在未完成 I/O 操作的时间,所占总时间的比例。

每个时刻,CPU 都有四种可能的状态:用户(user)、系统(sys)、空闲(idle)、iowait。性能工具诸如 vmstat、iostat、sar,都能显示出这四种状态所占的时间比例。sar(加上 -P 参数)可以显示出每个 CPU 各自的状况,但大部分工具显示的是所有 CPU 的平均值。

这些工具显示的统计信息来源于内核周期性更新的计数器(在 AIX 上,这些计数器在每次时钟中断时更新,10 毫秒一次)。

当某个 CPU 发生时钟中断时,内核会检查 CPU 是否空闲。如果不空闲,内核就会判断当前指令运行在用户态还是内核态。如果在用户态,则将「用户」计数器加一;如果在内核态,则将「系统」指针加一。

如果 CPU 处于空闲状态,内核就会判断是否至少有一个 IO 操作正在执行,不论对象是本地磁盘,还是该 CPU 负责挂载的远程存储(如 NFS)。如果有,则将「iowait」计数器加一;如果没有进行中的 IO 操作,则将「空闲」计数器加一。

当某个性能工具(如 vmstat)启动时,它会读取这四个计数器当前的值,然后等待用户指定的刷新间隔时间,再次读取计数器的值。接着,vmstat 会计算出两次采样区间内各计数器的差值,按时钟中断的频率和采样周期的时长计算出各状态所占的比例。

比如你运行 vmstat 2,vmstat 就会每 2 秒钟采样一次各计数器的值。时钟中断周期是 10 毫秒,那么每秒就有 100 次中断,每个采样周期就有 200 次中断。每个计数器的差值除以周期内的总中断数,再乘以 100,就是周期内各状态的比例值。

在某些场景中,iowait 的值可以指示存储吞吐量是否成为瓶颈;但在其他情况下,iowait 的值可能毫无意义。

下面的一些例子可以说明这一点。在第一个例子中,iowait 是性能问题的直接原因。

例一:

假设某个程序在执行批处理任务,在每个任务中,程序都会执行 10 毫秒的计算,然后将结果同步写到磁盘上。因为写的文件是以同步模式打开的,直到写入磁盘的操作彻底完成之后,write 才会返回。

假设这个磁盘没有缓存,每次物理写入操作耗时 20 毫秒,于是程序每次执行任务需要 30 毫秒。在 1 秒(1000 毫秒)的周期内,每个程序可以处理 33 个任务(33 tps)。如果这个程序在单 CPU 系统上单任务运行,那么 CPU 就会在 1/3 的时间里忙碌,而在剩下的时间里等待 IO 完成——所以 CPU 会是 67% 的 iowait、33% 的忙碌。

如果改进 IO 子系统(例如加入磁盘缓存),一次 IO 写操作只耗时 1 毫秒,一个任务也就只需要 11 毫秒完成,程序就能每秒完成 90~91 个任务。这时 iowait 的时间比例就会是 8% 左右。注意,这里 iowait 的降低直接提升了程序的性能。

例二:

假设系统上运行着 dd 程序,每次从磁盘读取 4 KB 的数据。假设 dd 将逻辑实现在 main() 中,调用 read() 来读取数据,而这两者都是用户空间里的函数。read() 会接着启动 kread() 系统调用并进入内核态。kread() 会启动设备的物理 IO 操作,随后 dd 程序会进入睡眠状态,直至物理 IO 完成。

程序执行 mainreadkread 的时间非常短暂,可能最多 50 微秒。磁盘完成 IO 的时间可能会在 2~20 毫秒之间,取决于磁盘臂寻道的距离。这就意味着,当时钟中断发生时,dd 程序很可能正处于睡眠状态,等待 IO 操作完成,因而 iowait 计数器会加一。假定 IO 操作在 20 毫秒后完成完成,dd 程序恢复运行,再次执行 50 微秒的自身代码。但是 50 微秒比之 20 毫秒(20000 微秒)是如此短暂,很可能当时钟中断发生时,CPU 还是在等待 IO 完成的空闲状态,所以 iowait 计数器依然会增加。

如果你用 sar -P <cpunumber> 查看这个 CPU 的的利用率,很可能会看到 99~100% 的 iowait。如果每次 IO 操作耗时缩短到 2 毫秒,iowait 就会是 97~98% 左右。尽管这两种情况下 iowait 的比例都非常之高,但第二种情况的吞吐量却是前一种的十倍。

例三:

假设一个 CPU 上运行着两个程序。其中一个是 dd 程序,在读取磁盘;另一个不执行 IO 操作,但使用 100% 的时间片进行计算。假设 IO 子系统出现了故障,每次物理 IO 需要超过一秒来完成。每当 dd 处于睡眠状态、等待 IO 操作完成时,另一个程序都能够在这个 CPU 上运行。当时钟中断发生时,永远都会有一个程序,要么在用户态要么在内核态运行,因此,空闲和 iowait 的比例值都会是 0。尽管现在 iowait 是 0,但并不代表不存在 IO 方面的问题。

例四:

假设在一个 4 CPU 的系统上有 6 个程序在运行,其中四个程序使用 70% 的时间等待物理读的 IO 操作,30% 的时间实际使用 CPU 计算。既然这四个程序需要在内核态执行 kread 系统调用,也就必定会消耗一定比例的时间在内核态:假定 25% 的时间在用户态,5% 的时间在内核态。

同时,我们假设另外两个程序使用 100% 的时间片在用户代码里进行计算,不产生 IO 操作,所以两个 CPU 会一直处于 100% 忙碌状态。既然另外四个程序只实际使用 30% 的时间片,它们可以争夺剩余的 CPU 时间。

如果执行 sar -P ALL 1 10,让 sar 以 1 秒为采样周期,采集 10 个周期的信息,则应当看到这样的输出:

cpu    %usr    %sys    %wio   %idle
0       50     10     40       0
1       50     10     40       0
2     100       0       0       0
3     100       0       0       0
-       75       5     20       0

注意,所有 CPU 的平均利用率会是 75% 的用户、5% 的系统和 20% 的 iowait。vmstatiostat 等大多数工具显示的都是所有 CPU 的平均值。

现在,假设我们将相同的工作负载(相同行为的 6 个程序)转移到有 6 个 CPU 的另一台机器(具有相同的 CPU 速度和相同的 IO 子系统),每个程序可以单独运行在一个 CPU 上。这时 CPU 的使用情况将会如下所示:

cpu    %usr    %sys    %wio   %idle
0       25       5     70       0
1       25       5     70       0
2       25       5     70       0
3       25       5     70       0
4     100       0       0       0
5     100       0       0       0
-       50       3     47       0

现在的平均 CPU 利用率就是 50% 用户、3% 系统以及 47% iowait。注意,相同的工作负载转移到另一台机器后,iowait 的值是原来的两倍。

结论:

iowait 的值不一定是有用的 IO 性能指标,但它的确可以告诉我们,这个系统能够处理更多的计算工作。处在 iowait 状态的 CPU 并非不能运行其他线程,iowait 只是空闲的一种形式而已

发表评论

电子邮件地址不会被公开。 必填项已用*标注