1. IOwait 到底在wait什么
%IOwait 一个迷之参数,top/iostat/mpstat/sar 都会统计的关键数据,字面理解是系统等待IO的时间,经常用于反映系统IO压力。 IOwait时候CPU在干什么?什么时间才算IOwait,到底在wait什么?
2. 数据含义及来源
man mpstat 查看下官方定义
%iowait
Percentage of time that the CPU or CPUs were idle during which the system
had an outstanding disk I/O request.
注:里面还有对%irq %soft %steal的解释。
统计工具对IOwait的定义是CPU idle时系统有IO请求的时间百分比,也就是说要满足两个条件才会被记录为IOwait
(1) CPU idle
(3) 有IO请求在处理
3. %iowait 数据来自/proc/stat 第5个参数,这里的数据又是怎么来的(kernel5.3)
/proc/stat 数据来源于fs/proc/stat.c show_stat() 中找到/proc/stat 获取 iowait的路径 get_iowait_time()->get_cpu_iowait_time_us()。
根据CPU当前状态 online/offline,选择从cpustat.iowait 或者get_cpu_iowait_time_us() 获取iowait
static u64 get_iowait_time(int cpu) { u64 iowait, iowait_time = -1ULL; if (cpu_online(cpu)) iowait_time = get_cpu_iowait_time_us(cpu, NULL); if (iowait_time == -1ULL) /* !NO_HZ or cpu offline so we can rely on cpustat.iowait */ iowait = kcpustat_cpu(cpu).cpustat[CPUTIME_IOWAIT]; else iowait = usecs_to_cputime64(iowait_time); return iowait; }
get_cpu_iowait_time_us()数据来源于每个CPU 的 ts->iowait_sleeptime
u64 get_cpu_iowait_time_us(int cpu, u64 *last_update_time) { struct tick_sched *ts = &per_cpu(tick_cpu_sched, cpu); ktime_t now, iowait; if (!tick_nohz_active) return -1; now = ktime_get(); if (last_update_time) { update_ts_time_stats(cpu, ts, now, last_update_time); iowait = ts->iowait_sleeptime; } else { if (ts->idle_active && nr_iowait_cpu(cpu) > 0) { ktime_t delta = ktime_sub(now, ts->idle_entrytime); iowait = ktime_add(ts->iowait_sleeptime, delta); } else { iowait = ts->iowait_sleeptime; } } return ktime_to_us(iowait); }
即iowait 来源为 cpustat.iowait 或者 CPU 相关的 ts->iowait_sleeptime 。
4. cpustat.iowait/iowait_sleeptime 统计
这两个参数的累加函数为
void account_idle_time(cputime_t cputime) { u64 *cpustat = kcpustat_this_cpu->cpustat; struct rq *rq = this_rq(); if (atomic_read(&rq->nr_iowait) > 0) cpustat[CPUTIME_IOWAIT] += (__force u64) cputime; else cpustat[CPUTIME_IDLE] += (__force u64) cputime; } /* * Updates the per cpu time idle statistics counters */ static void update_ts_time_stats(int cpu, struct tick_sched *ts, ktime_t now, u64 *last_update_time) { ktime_t delta; if (ts->idle_active) { delta = ktime_sub(now, ts->idle_entrytime); if (nr_iowait_cpu(cpu) > 0) ts->iowait_sleeptime = ktime_add(ts->iowait_sleeptime, delta); else ts->idle_sleeptime = ktime_add(ts->idle_sleeptime, delta); ts->idle_entrytime = now; } if (last_update_time) *last_update_time = ktime_to_us(now); }
调用栈为:
do_idle()->tick_nohz_idle_exit()->__tick_nohz_idle_restart_tick()->tick_nohz_account_idle_ticks()->account_idle_ticks()->account_idle_time()
do_idle()->tick_nohz_idle_exit()->tick_nohz_stop_idle()->update_ts_time_stats()
即CPU idle时会触发计数,具体计数哪个根据cpu状态判断。
两个数据的计数逻辑一样,都是根据当前cpu runqueue的nr_iowait 判断时间要累加在idle 或者 iowait 。
5.nr_iowait 哪里统计
5.1 4.14 kernel
sched_init(void) //kernel/sched/core.c atomic_set(&rq->nr_iowait, 0); /*此任务即将在IO上休眠。 递增rq-> nr_iowait,以便进程记帐知道这是处于IO等待状态的任务*/ io_schedule_timeout(long timeout) //kernel/sched/core.c atomic_inc(&rq->nr_iowait); ret = schedule_timeout(timeout); /*sleep until timeout*/ atomic_dec(&rq->nr_iowait); struct address_space_operations nfs_file_aops .releasepage = nfs_release_page, static int nfs_release_page(struct page *page, gfp_t gfp) //fs/nfs/file.c 调用位置1 wait_on_page_bit_killable_timeout(struct page *page, int bit_nr, unsigned long timeout) //mm/filemap.c bit_wait_io_timeout(struct wait_bit_key *word, int mode) //kernel/sched/wait.c io_schedule_timeout(word->timeout - now); //kernel/sched/core.c
5.2 5.3 kernel
调度时若当前task是in_iowait,则当前CPU runqueue 的nr_iowait 加1,表示当前有task在等待IO,参考__schedule() 。
if (!preempt && prev->state) { if (signal_pending_state(prev->state, prev)) { prev->state = TASK_RUNNING; } else { deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK); if (prev->in_iowait) { atomic_inc(&rq->nr_iowait); delayacct_blkio_start(); } } switch_count = &prev->nvcsw; }
task的in_iowait 在 io_schedule_prepare()中设置,调用io_schedule_prepare()的相关函数有io_schedule(), io_schedule_time() , mutex_lock_io() , mutex_lock_io_nested() 等。 即当因这些调用产生调度则标记当前CPU有iowait的task,task重新wakeup时in_iowait恢复,cpu runqueu 的nr_iowait减1。
int io_schedule_prepare(void) { int old_iowait = current->in_iowait; current->in_iowait = 1; blk_schedule_flush_plug(current); return old_iowait; }
总的来说当有task因为IO而被调度出CPU时,标识该CPU有在等待IO的task,当CPU进入idle时如果仍有等待IO的task,就标记这段时间为IOwait,否则标记为idle, 与man mpstat中的定义一致。
4.IO在哪里阻塞
系统中执行IO的流程非常的多,其阻塞点也很多,这里列出两个通常IO操作中最常见的阻塞点。
1. io提交至驱动后,等待数据返回。
2. 有并发IO请求时争抢IO软/硬队列等资源。
5. 结论
IOwait 是指CPU空闲时,且当前有task在等待IO的时间。
因IO阻塞而调度主要出现在 1.等待数据返回; 2.并发IO时竞争资源
影响该时间的因素很多,不只有IO负载,CPU负载也会严重影响该参数,不可一味根据IOwait判断系统的IO压力,还需要结合iostat 等数据综合判断。
6. iowait高排查
(1) top查看iowait占比
# top top - 01:04:06 up 19:15, 2 users, load average: 0.19, 0.14, 0.08 Tasks: 244 total, 2 running, 242 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.1 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem: 3063436 total, 1368276 used, 1695160 free, 164224 buffers KiB Swap: 11532280 total, 0 used, 11532280 free. 653976 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3472 root 20 0 1380924 148232 63860 S 73.3 4.8 8:07.02 compiz 3236 root 20 0 376488 8104 6772 S 12.2 0.3 0:16.84 ibus-daemon
"0.1 wa" 此时io占比0.1
(2) iostat查看iowait发生的块设备
# iostat -x 1 10 // -x显示拓展统计信息,1s更新一次显示,一共显示10次。 avg-cpu: %user %nice %system %iowait %steal %idle 1.75 0.00 1.25 0.00 0.00 96.99 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
man iostat 可以查看每个字段的含义。
(3) iotop查看进程/内核线程对块设备的读写速度
# iotop Total DISK READ : 0.00 B/s | Total DISK WRITE : 0.00 B/s Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 0.00 B/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 1 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % init 2 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [kthreadd] 3 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [ksoftirqd/0] 5 be/0 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [kworker/0:0H] 7 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [rcu_sched] 8 be/4 root 0.00 B/s 0.00 B/s 0.00 % 0.00 % [rcu_bh]
(4) lsof查看进程打开的文件
# lsof -p 1383 //NetworkManager的PID COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME NetworkMa 1383 root cwd DIR 8,1 4096 2 / NetworkMa 1383 root rtd DIR 8,1 4096 2 / NetworkMa 1383 root txt REG 8,1 1124416 2238504 /usr/sbin/NetworkManager
7. 注意AIX仅仅标记那些触发未完成IO任务的空闲CPU为iowait状态,不会牵连到系统中其他空闲的CPU(这些CPU状态依然标记为IDLE空闲状态)。这样就有效减少了一部分iowait值虚高的情形:比如一个4颗物理CPU的系统,如果只有其中一颗物理CPU上有未完成IO请求,则iowait最高不会超过25%.
8. iowait的一些事实
(1) %iowait合理值取决于应用IO特点。比如备份任务往往iowait较高;而cache命中率高、磁盘读写少的应用负载iowait一般不高。
(2) 从上述说明可以看到,减少%iowait的方法有两类:一类是进一步缩减IO处理时间,比如采用SSD盘,或者甚至内存盘等技术;
(3) %iowait比例与是否存在IO性能问题并无直接关系:低iowait也不代表没有磁盘性能问题;参考第二点,完全可能在实际上IO服务时间非常长,但由于系统中同时存在CPU密集型任务掩盖了iowait。高iowait不一定代表有磁盘性能问题;因为系统可能比较空闲,而业务类型是IO密集型比如备份。