[TOC]
什么是iowait?
顾名思义,就是系统因为io导致的进程wait。再深一点讲就是:这时候系统在做io,导致没有进程在干活,cpu在执行idle进程空转,所以说iowait的产生要满足两个条件,一是进程在等io,二是等io时没有进程可运行。
Iowait是如何计算的?
先说说用户如何看到iowait吧
我们通常用vmstat就能看到iowat
这个数据是vmstat经过计算文件/proc/stat中的数据获得,所以说大家看到的是能够大概反应一个系统iowait水平的数据表象。关于/proc/stat中的数据都代表了什么意思,大家自己google吧,不再赘述。
那/proc/stat文件中的这些数据是从哪来的呢?
Kernel中有个proc_misc.c文件会专门输出这些数据,这个文件对应的函数是show_stat
部分代码:
for_each_possible_cpu(i) {
int j;
user = cputime64_add(user, kstat_cpu(i).cpustat.user);
nice = cputime64_add(nice, kstat_cpu(i).cpustat.nice);
system = cputime64_add(system, kstat_cpu(i).cpustat.system);
idle = cputime64_add(idle, kstat_cpu(i).cpustat.idle);
iowait = cputime64_add(iowait, kstat_cpu(i).cpustat.iowait);
irq = cputime64_add(irq, kstat_cpu(i).cpustat.irq);
softirq = cputime64_add(softirq, kstat_cpu(i).cpustat.softirq);
steal = cputime64_add(steal, kstat_cpu(i).cpustat.steal);
for (j = 0 ; j < NR_IRQS ; j++)
sum += kstat_cpu(i).irqs[j];
}
….
seq_printf(p,
"\nctxt %llu\n"
"btime %lu\n"
"processes %lu\n"
"procs_running %lu\n"
"procs_blocked %lu\n",
nr_context_switches(),
(unsigned long)jif,
total_forks,
nr_running(),
nr_iowait());
…
这部分代码会输出你在/proc/stat中看到的数据,通过代码我们得知iowait来自
iowait = cputime64_add(iowait, kstat_cpu(i).cpustat.iowait);
那么 cpustat.iowait是谁来修改的呢?
我们找到了这个函数account_system_time
void account_system_time(struct task_struct *p, int hardirq_offset,
cputime_t cputime)
{
struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat;
struct rq *rq = this_rq();//在smp环境下获取当前的run queue
cputime64_t tmp;
p->stime = cputime_add(p->stime, cputime);
/* Add system time to cpustat. */
tmp = cputime_to_cputime64(cputime);
if (hardirq_count() - hardirq_offset)//在做硬中断
cpustat->irq = cputime64_add(cpustat->irq, tmp);
else if (softirq_count())//在做软中断
cpustat->softirq = cputime64_add(cpustat->softirq, tmp);
else if (p != rq->idle)//程序在正常运行,非idle
cpustat->system = cputime64_add(cpustat->system, tmp);
else if (atomic_read(&rq->nr_iowait) > 0)//既不做中断,而且在idle,那么就是iowait
cpustat->iowait = cputime64_add(cpustat->iowait, tmp);
else
cpustat->idle = cputime64_add(cpustat->idle, tmp);
/* Account for system time used */
acct_update_integrals(p);
}
我们可以看出,当某个cpu产生iowait时,那么这个cpu上肯定有进程在进行io,并且在等待io完成(rq->nr_iowait>0),并且这个cpu上没有进程可运行(p == rq->idle),cpu在idle。
谁在产生iowait?
那么是谁修改了rq->nr_iowait呢?
重点终于来了
void __sched io_schedule(void)
{
struct rq *rq = &__raw_get_cpu_var(runqueues);
delayacct_blkio_start();
atomic_inc(&rq->nr_iowait);
schedule();
atomic_dec(&rq->nr_iowait);
delayacct_blkio_end();
}
long __sched io_schedule_timeout(long timeout)
{
struct rq *rq = &__raw_get_cpu_var(runqueues);
long ret;
delayacct_blkio_start();
atomic_inc(&rq->nr_iowait);
ret = schedule_timeout(timeout);
atomic_dec(&rq->nr_iowait);
delayacct_blkio_end();
return ret;
}
所以产生iowait的根源被我们找到了,就是函数io_schedule, io_schedule_timeout,顾名思义,这两个函数是用来做进程切换的,而且切换的原因是有io。只不过io_schedule_timeout还给出了一个sleep的时间,也就是timeout。
Iowait的具体含义
Reports the percentage of time the processor(s) were idle during which the system had outstanding disk/NFS I/O
request(s).
也即iowait其实是一种特殊形式的CPU空闲。特殊之处在于,在此CPU的等待队列上有线程在等待IO完成(我们称之为pendingIO 或者 outstanding IO)。
这是由 IO 的特点决定的,因为 IO 速度较慢,现代操作系统实现 IO 一般是通过异步中断来完成的:即提交 IO 请求,然后线程挂起进入等待队列;IO 完成后,再通过中断通知相关线程转到就绪队列,进行处理。
在相关任务线程提交完 IO 请求,到 IO 中断返回的过程中,此时 IO 主要由存储侧处理,主机侧 CPU 实际上处于空闲状态。如果此时有其他任务线程可调度,系统会直接调度其他线程,这样 CPU 就相应显示为Usr 或 Sys;但是如果此时系统较空闲,无其他任务可以调度,CPU 就会显示为 iowait(实际上与 idle 无本质区别)。
注意 AIX仅仅标记那些触发未完成 IO 任务的空闲CPU为 iowait 状态,不会牵连到系统中其他空闲的CPU(这些CPU 状态依然标记为 IDLE 空闲状态)。这样就有效减少了一部分 iowait 值虚高的情形:比如一个 4 颗物理 CPU 的系统,如果只有其中一颗物理 CPU 上有未完成 IO 请求,则 iowait 最高不会超过 25%.
iowait 的一些事实
%iowait 合理值取决于应用 IO 特点。
比如备份任务往往 iowait 较高;而 cache 命中率高、磁盘读写少的应用负载 iowait 一般不高。从上述说明可以看到,减少%iowait 的方法有两类:
一类是进一步缩减 IO 处理时间,比如采用 SSD 盘,或者甚至内存盘等技术;
另外一类是缩减 IO 处理过程中 CPU 的空闲时间,比如在系统中添加 CPU 密集型任务,可以使得%iowait 比例明显降低甚至
为 0;%iowait 比例与是否存在 IO 性能问题并无直接关系:
低 iowait 也不代表没有磁盘性能问题;参考第二点,完全可能在实际上 IO 服务时间非常长,但由于系统中同时存在 CPU 密集型任务掩盖了 iowait。
高 iowait 不一定代表有磁盘性能问题;因为系统可能比较空闲,而业务类型是 IO 密集型比如备份。