文章总结: 本文深入Linux内核源码,剖析iowait指标的计算逻辑。iowait记录CPU因等待I/O而空闲的时间,而非I/O耗时。内核在进程调用io_schedule时标记状态并更新队列计数,当CPU退出空闲且存在等待I/O的进程时,将时间段计入iowait_sleeptime,最终通过/proc/stat输出。 综合评分: 85 文章分类: 其他
Linux 内核是如何计算 iowait 的
原创
独钓孤舟雪 独钓孤舟雪
MyStackTrace
2026年1月18日 17:31 北京
上一篇文章我们介绍了如何正确理解 Linux 系统中的 iowait 指标,在 Linux 系统中 iowait 是 CPU 空闲(idle)时间的一部分,表示 CPU 因为等待 I/O 操作(主要是块设备 I/O)完成所花时间的百分比,它并不是 I/O 本身占用的 CPU 时间,而是 CPU 因为等待 I/O 而空闲的时间占比。
查看 iowait 的命令有很多,比如:vmstat,iostat,mpstat,这些命令都是通过读取 /proc/stat 伪文件中的原始 CPU 统计信息来计算 iowait 和其他 CPU 统计信息的。
/proc/stat 中的数据完全来自 Linux 内核的内部计数器,由内核在运行时动态统计和更新。下面让我们深入 Linux 内核源代码层面,看看 iowait 具体是怎么来的。
首先找到内核实现 /proc/stat 伪文件的地方,可以看到主要实现在函数 show_stat 中。
函数 show_stat 获取每个 CPU iowait 时间的代码片段如下,可以看到每个 CPU 上都有一个 kernel_cpustat 结构,iowait 时间就记录在这个结构中,除此之外,这个结构中还记录了 CPU 花在 user mode,kernel mode,idle,irq 等方面的时间。用于从 kernel_cpustat 结构上获取 iowait 时间的函数为 get_iowait_time。
函数 get_iowait_time 的代码如下,它会调用函数 get_cpu_iowait_time_us 来获取以 us 为单位的 iowait 时间。
我们继续看 get_cpu_iowait_time_us 的代码,该函数其实就是获取 per cpu 变量 tick_cpu_sched 中(tick_sched 结构)的 iowait_sleeptime 成员的值。
我们在内核中搜索 iowait_sleeptime,可以发现该成员的值是在函数 tick_nohz_stop_idle 中被更新的。函数 tick_nohz_stop_idle 是 Linux 内核中 NO_HZ 机制的一个关键函数,主要作用是停止 CPU 的空闲状态(退出 CPU 的空闲状态)并准备重新开始处理任务(调度进程)。当 CPU 处于空闲状态(没有进程可运行)时,内核会停止周期性的时钟中断(NO_HZ)以节省功耗,当有进程被唤醒或中断发生时,CPU 需要退出这种深度空闲的状态,tick_nohz_stop_idle 就是执行这个“退出空闲”操作的函数,而这个“退出空闲”的时间点正好可以用来更新 iowait 的时间,因为一段等待 I/O 的时间要想被算入 iowait 时间,必须要求 CPU 在这段时间是空闲状态的,那退出空闲的时间点最合适来更新这个 iowait 时间了。这个函数用当前时间 now 减去 CPU 进入空闲状态的时间 idle_entrytime,就得到了这次空闲的时间 delta,如果当前 CPU 的 nr_iowait_cpu 大于 0 的话,这段空闲时间 delta 就可以累加到 iowait_sleeptime 上了,否则这段空闲时间 delta 只能计入 idle_sleeptime 了。所以,一段空闲的 CPU 时间片能否被计入 iowait 时间取决于函数 nr_iowait_cpu 在当前 CPU 上的返回值。
下面我们来看看函数 nr_iowait_cpu 的代码,该函数很简单,其实就是把指定 CPU 的运行队列(Run Queue,struct rq,用于管理该 CPU 上所有可运行的进程)的 nr_iowait 成员返回,我们可以猜测 nr_iowait 表示该 CPU 上有几个正在因为等待 I/O 而被阻塞的进程。
为了验证这个猜想,我们内核代码中搜索 nr_iowait,看看这个字段在哪儿被更新的。可以发现 nr_iowait 是在函数 __block_task 中被更新的。函数 __block_task 是由函数 __schedule 一路调下来的。熟悉 Linux 内核进程调度的同学都知道函数 __schedule 是 Linux 内核调度器的最核心函数,它负责执行真正的任务切换。当进程执行了导致睡眠/阻塞的操作,比如等待 I/O 完成,最终都会调用到函数 __schedule 主动让出 CPU,这个时间点也是更新当前 CPU 运行队列的 nr_iowait 成员的最佳时间点,从 __block_task 的代码可以看出,如果当前进程的 in_iowait 非 0,则把当前 CPU 运行队列的 nr_iowait 值加 1,那么在函数 tick_nohz_stop_idle 中调用 nr_iowait_cpu 的返回值就是大于 0 的,则空闲时间就会被计入 iowait_sleeptime 了。所以这里更新 CPU 运行队列的 nr_iowait 的关键是该 CPU 上有进程的 in_iowait 成员非 0,并且该进程还要处于睡眠/阻塞状态。
下面我们继续搜索进程(struct task_struct)的 in_iowait 字段是在哪儿更新的,可以发现如下代码。在 io_schedule 和 io_schedule_timeout 这些函数中都会调用 io_schedule_prepare 和 io_schedule_finish 这两个函数。函数 io_schedule_prepare 会在当前进程(通过调用 schedule 或者 schedule_timeout,最终都会调到上面那个 __schedule)让出 CPU 之前调用,io_schedule_prepare 会先保存当前进程 in_iowait 字段,然后把 in_iowait 设置为 1,表示当前进程处于等待 I/O 的状态。当进程被再次唤醒时,schedule 或者 schedule_timeout 函数就会返回,函数 io_schedule_finish 会被调用,它会把之前保存的 in_iowait 字段的旧值给恢复回去,通常情况都会把 in_iowait 字段给恢复为 0,因为进程这时已经被唤醒,它所等待的 I/O 极有可能已经被完成了,所以要让进程退出等待 I/O 的状态。
通过上面的分析可以看出要想让进程等待 I/O 的时间被算进 iowait 中,进程等待 I/O 调用的函数需要是 io_schedule 和 io_schedule_timeout 这两个函数,因为这两个函数会把该进程的 in_iowait 字段置 1,把该进程标记为等待 I/O 的状态。
上面的分析是倒着从 /proc/stat 的实现一步一步分析出来 iowait 的值是怎么来的,下面我们正向叙述一遍。首先是进程在需要等待 I/O 的时候,要调用(主要是内核在关键路径上调用的)函数 io_schedule 或者 io_schedule_timeout 把 CPU 让出去,让自己处于阻塞状态,这两个函数会先把当前进程的 in_iowait 字段置 1,然后层层调用到 __schedule 函数让出 CPU,在让出 CPU 之前函数 __schedule 还要检查当前进程的 in_iowait 字段,如果非 0,则将当前 CPU 运行队列(struct rq)的 nr_iowait 字段加 1。如果当前 CPU 上目前也没有其他要运行的进程,该 CPU 会进入空闲状态,同时把进入空闲状态的时间点记录在该 CPU 的 tick_cpu_sched 的 idle_entrytime 成员中。当后面某一刻 CPU 要退出空闲状态时,或者发生中断时,函数 tick_nohz_stop_idle 会被调用,该函数会根据当前 CPU 运行队列的 nr_iowait 是否大于 0,来决定这段空闲时间被计入哪个字段(iowait_sleeptime 还是 idle_sleeptime),如果 nr_iowait 大于 0,则说明在 CPU 这段空闲时间内,有进程在等待 I/O,则这段空闲时间会被累加到 iowait_sleeptime 上,否则这段空闲时间只能被累加到 idle_sleeptime 上。最后当用户在读取伪文件 /proc/stat 的时候,内核会把每个 CPU 上记录的 iowait_sleeptime 和其他统计时间一起输出。
vmstat,iostat 和 mpstat 这些工具会每隔一段时间读取一次伪文件 /proc/stat 的内容,然后算出前后两次读取到的 iowait 的差值,以及这个差值在这段时间占比,这就形成了这些工具实时监控的效果。通过 iowait 的来历我们也能看出为什么这些系统监控工具能几乎无开销地获取精确的 CPU 统计信息 —— 数据已经在内核中维护好了,读取时只是简单的汇总和格式化。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MyStackTrace 独钓孤舟雪 独钓孤舟雪《Linux 内核是如何计算 iowait 的》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论