为什么到 dd 管道的 gunzip 最终会变慢?

cts*_*cts 5 linux gzip dd sd-card iotop

我的命令:

gunzip -c serial2udp.image.gz |
sudo dd of=/dev/mmcblk0 conv=fsync,notrunc status=progress bs=4M
Run Code Online (Sandbox Code Playgroud)

我的输出:

15930949632 bytes (16 GB, 15 GiB) copied, 1049 s, 15.2 MB/s    

0+331128 records in
0+331128 records out
15931539456 bytes (16 GB, 15 GiB) copied, 1995.2 s, 8.0 MB/s
Run Code Online (Sandbox Code Playgroud)

卡:SanDisk Ultra 32GB MicroSDHC Class 10 UHS 存储卡速度高达 30MB/s
发行版:16.0.4 xenial with xfce
内核版本:4.13.0.37-generic

我知道从我读过的内容来看,花 17 分钟似乎是合理的。玩块大小似乎并没有太大区别(bs=100M 仍然表现出类似时间戳的这种行为)。为什么更新挂起,并且在 16 分钟内没有生成完成的报告?

iotop 告诉我 mmcqd/0 此时仍在后台运行(以 99% IO),所以我认为某处有一个缓存占用了最后的 5MB,但我认为 fsync 应该确保不会发生iotop 显示此时 dd 也没有交通路口。ctrl-c 几乎没用,我不想在写入后损坏我的驱动器。

sou*_*edi 15

我认为某处有一个缓存占用了最后的 5MB,但我认为 fsync 应该确保不会发生这种情况

conv=fsync意味着通过调用写回任何缓存fsync- 在dd写入所有数据之后。挂在最后正是它会做的。

当输出文件比输入文件慢时,写入的数据dd会堆积在缓存中。内核缓存有时会填满系统 RAM 的很大一部分。这会导致非常具有误导性的进度信息。您的“最后 5MB”只是dd显示进度的人工制品。

如果您的系统确实缓存了大约 8GB(即 16GB 写入数据的一半),那么我认为您要么必须拥有大约 32GB 的 RAM,要么一直在摆弄某些内核选项。请参阅下面的 lwn.net 链接。我同意 15 分钟没有获得任何进度信息是非常令人沮丧的。

dd您可以使用其他命令。如果您想dd显示更准确的进度,您可能不得不接受更多的复杂性。我希望以下内容不会降低您的表现,尽管现实可能有其他想法。

gunzip -c serial2udp.image.gz |
dd iflag=fullblock bs=4M |
sudo dd iflag=fullblock oflag=direct conv=fsync status=progress bs=4M of=/dev/mmcblk0
Run Code Online (Sandbox Code Playgroud)
  • oflag=direct iflag=fullblock 避免堆积内核缓存,因为它完全绕过它。
  • iflag=fullblock在这样的命令 AFAIK 中是必需的(例如,因为您正在从管道读取并使用直接 IO 写入)。缺失的影响fullblock是 的另一个不幸的复杂性dd。该站点上的一些帖子使用它来论证您应该始终更喜欢使用不同的命令。很难找到另一种方法来进行直接或同步 IO。
  • conv=fsync应该仍然使用,写回设备缓存。
  • 我添加了一个额外的ddafter gunzip, 以与磁盘写入并行缓冲解压缩的输出。这是使性能具有oflag=directoflag=sync有点复杂的问题之一。正常 IO(非直接、非同步)不应该需要这个,因为它已经被内核缓存缓冲了。如果您要写入具有 4M 回写缓存的硬盘驱动器,您也可能不需要额外的缓冲区,但我认为 SD 卡没有那么多。

您也可以使用oflag=direct,sync(而不需要conv=fsync)。如果您有一个带有数百兆字节缓存的奇怪输出设备,这可能对获得良好的进度信息很有用。但通常我认为oflag=sync这是性能的潜在障碍。

有一篇 2013 年的文章https://lwn.net/Articles/572911/,其中提到了像您这样的长达一分钟的延迟。许多人认为这种缓存数分钟的写回数据的能力是不受欢迎的。问题是缓存大小的限制被不加选择地应用于快速和慢速设备。请注意,内核测量设备速度并非易事,因为它因数据位置而异。例如,如果缓存的写入分散在随机位置,硬盘驱动器将需要更长的时间来重复移动写入头。

为什么更新挂起

fsync()是适用于的整个范围的单一系统调用文件设备。它在完成之前不会返回任何状态更新。