`dd` 正在全速运行,但我只看到 20% 的磁盘利用率。为什么?

sou*_*edi 8 linux atop

  1. sudo dd if=/dev/sda of=/dev/null bs=1M iflag=direct
  2. atopsar -d 5 # in a second terminal
  3. top # in a third terminal

结果来自atopsar

19:18:32  disk           busy read/s KB/read  writ/s KB/writ avque avserv _dsk_
...
19:16:50  sda             18%  156.5  1024.0     0.0     0.0   5.0   1.15 ms
19:16:55  sda             18%  156.3  1024.0     0.0     0.0   4.9   1.15 ms
...
Run Code Online (Sandbox Code Playgroud)

为什么报告的磁盘利用率(“忙”)远低于 100%?

top,该dd进程仅使用 3% 或更少的 CPU。 top还提供系统 CPU 的硬件和软件中断 (hisi) 使用情况的总体报告,显示低于 1%。我有四个 CPU(2 个内核,每个内核 2 个线程)。

/dev/sda是SATA硬盘。它不是 SSD,甚至不是混合 SSHD 驱动器。它的读取速度不能超过每秒 150 兆字节 :-)。所以这部分结果是有意义的:156 read/s * 1024 KB/read = 156 MB/s

内核版本是5.0.9-200.fc29.x86_64(Fedora Workstation 29)。IO 调度程序是mq-deadline. 从内核版本 5.0 开始,Fedora 使用多队列块层。因为单个队列块层已被删除:-)。

我相信,磁盘利用率图中atopsar -d,并atop从一个计算内核iostat的领域。链接的文档提到了“字段 10 -- 花费在 I/O 上的毫秒数”。还有一个更详细的定义,虽然我不确定它提到的功能是否仍然存在于多队列块层中。据我所知,双方atopsar -datop使用通用代码阅读本场10(我相信本场也使用sar -d/ iostat -x/ mxiostat.py

附加测试

变体 2:更改为bs=512k,但保留iflag=direct

dd if=/dev/sda of=/dev/null bs=512k iflag=direct

19:18:32  disk           busy read/s KB/read  writ/s KB/writ avque avserv _dsk_
...
19:18:00  sda             35%  314.0   512.0     0.0     0.0   2.1   1.12 ms
19:18:05  sda             35%  313.6   512.0     0.2     4.0   2.1   1.11 ms
Run Code Online (Sandbox Code Playgroud)

变体 3:使用bs=1M,但删除iflag=directdd使用大约 10% 的 CPU 和 35% 的磁盘。

dd if=/dev/sda of=/dev/null bs=1M

19:18:32  disk           busy read/s KB/read  writ/s KB/writ avque avserv _dsk_
...
19:21:47  sda             35%  242.3   660.2     0.0     0.0   5.4   1.44 ms
19:21:52  sda             31%  232.3   667.8     0.0     0.0   9.5   1.33 ms
Run Code Online (Sandbox Code Playgroud)

如何重现这些结果 - 基本细节

当心最后一个测试,即没有运行dd iflag=direct

这有点像猪。我看到它冻结系统(鼠标光标)十秒钟或更长时间。即使我禁用了交换。(测试用buff/cache填充你的 RAM 。它正在填充非活动的 LRU 列表。我认为周转率会相对较快地驱逐非活动的缓存页面。同时,磁盘忙于顺序读取,所以当你需要时需要更长的时间将某些内容分页。这可能取决于内核最终是否也翻转了活动的 LRU 列表,或者将其缩小太多。即当前“许多不同算法的混合效果如何,并对其进行了大量修改”捕捉角落案例和各种优化”适用于您的案例)。

第一次测试的确切结果很难重现。

有时,KB/read显示为512而不是1024。在这种情况下,其他结果看起来更像是来自 的结果bs=512k。包括它显示大约 35% 的磁盘利用率,而不是大约 20%。我的问题在任何一种情况下都成立。

如果您想了解这种行为,请在此处进行描述:为什么我的 IO 请求的大小被限制为大约 512K?

sou*_*edi 8

这是内核版本 5.0 更改的结果:

阻止:删除 part_round_stats 并切换到不太精确的计数

我们想转换为 per-cpu in_flight 计数器。

函数 part_round_stats 需要每 jiffy 的 in_flight 计数器,每 jiffy 将所有 percpu 变量相加成本太高,因此必须将其删除。part_round_stats 用于计算两个计数器 - time_in_queue 和 io_ticks。

time_in_queue 可以在没有 part_round_stats 的情况下计算,通过在 I/O 结束时添加 I/O 的持续时间(该值几乎与之前计算的值一样精确,除了不计算进行中 I/O 的时间)。

io_ticks 可以通过在 I/O 开始或结束并且 jiffies 值发生变化时增加该值来近似。如果 I/O 占用的时间少于一瞬间,则该值与先前计算的值一样精确。如果 I/O 占用的时间超过一瞬间,io_ticks 可能会落后于先前计算的值。

io_tickspart_stat_show() 中使用,为“字段 10 -- 执行 I/O 所花费的毫秒数”提供内核 IO 统计信息。)

这很好地解释了我的结果。在 Fedora 内核配置中,“ jiffy ”是 1 毫秒。我预计提交的大型读取 IOdd可能会等待超过一两个 jiffies。特别是在我的系统上,它使用老式机械硬盘。

当我回到之前的内核系列 4.20.x 时,它显示了正确的磁盘利用率:

$ uname -r
4.20.15-200.fc29.x86_64
$ atopsar -d 5
...
13:27:19  disk           busy read/s KB/read  writ/s KB/writ avque avserv _dsk_
13:28:49  sda             98%  149.4  1024.0    13.0     5.3   2.2   6.04 ms
13:28:54  sda             98%  146.0  1024.0     7.2     5.7   1.5   6.38 ms
Run Code Online (Sandbox Code Playgroud)

这个旧内核cfq默认使用传统的单队列块层和IO 调度程序。使用deadlineIO调度器时,结果也是一样的。


更新:从内核 5.7 开始,调整了这个近似值。问题中的命令再次显示 100% 磁盘利用率。对于一些更复杂的工作负载,新的近似值预计会失效(尽管我还没有注意到)。

block/diskstats:对于慢速磁盘更准确地近似 io_ticks

如果 jiffies 计数器发生变化,当前 io_ticks 通过在请求的每个开始和结束处添加一个来近似。这适用于短于 jiffy 的请求,或者如果其中一个请求在每个 jiffy 开始/结束。

如果磁盘一次只执行一个请求并且它们比两个 jiffies 长,那么只会计算第一个和最后一个 jiffies。

修复很简单:在请求结束时加起来自上次更新以来传递的 io_ticks jiffy 而不是一个 jiffy。

示例:普通硬盘在 12ms 左右执行随机读取 4k 请求。

fio --name=test --filename=/dev/sdb --rw=randread --direct=1 --runtime=30 & iostat -x 10 sdb

注意 iostat 的 "%util" 8,43% -> 99,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
sdb               0,00     0,00   82,60    0,00   330,40     0,00     8,00     0,96   12,09   12,09    0,00   1,02   8,43
Run Code Online (Sandbox Code Playgroud)

后:

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
sdb               0,00     0,00   82,50    0,00   330,00     0,00     8,00     1,00   12,10   12,10    0,00  12,12  99,99
Run Code Online (Sandbox Code Playgroud)

现在 io_ticks 不会丢失请求开始和结束之间的时间,但是对于队列深度 > 1,相邻启动之间的某些 I/O 时间可能会丢失。

对于负载估计,“%util”不如平均队列长度有用,但它清楚地显示了磁盘队列完全空的频率。

修复:5b18b5a(“阻止:删除 part_round_stats 并切换到不太精确的计数”)
签字人:Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
审核人:Ming Lei <ming.lei@redhat.com>
签字-off-by: Jens Axboe <axboe@kernel.dk>