dd什么时候适合复制数据? (或者,什么时候 read() 和 write() 是部分的)

dd什么时候适合复制数据? (或者,什么时候 read() 和 write() 是部分的)

简洁版本:在什么情况下可以dd安全地用于复制数据,安全意味着不存在因部分读取或写入而导致损坏的风险?

长版——序言: dd通常用于复制数据,尤其是从设备复制数据或向设备复制数据(例子)。它有时被认为具有比其他工具更低级别的访问设备的神秘属性(事实上是设备文件发挥了魔力)——但dd if=/dev/sdacat /dev/sda.dd有时被认为更快,但是cat可以在实践中击败它。尽管如此,dd具有独特的特性,有时确实有用

问题: dd if=foo of=bar事实上,与 不同cat <foo >bar。在大多数 unice1 上,dd只需调用一次read()。 (我发现POSIX模糊了 中“读取输入块”的构成dd。)如果read()返回部分结果(根据 POSIX 和其他参考文档,除非实现文档另有说明,否则允许这样做),则复制部分块。存在完全相同的问题write()

观察结果:在实践中,我发现它dd可以处理块设备和常规文件,但这可能只是我没有进行太多练习。对于管道来说,找出dd错误并不难。例如尝试这段代码:

yes | dd of=out bs=1024k count=10

并检查文件的大小out(可能远低于 10MB)。

问题:在什么情况下可以dd安全地用于复制数据?换句话说,块大小、实现、文件类型等的哪些条件可以确保dd复制所有数据?

GNU dd有一个fullblock标志告诉它调用read()write()循环以便传输完整的块。所以dd iflag=fullblock总是安全的。我的问题是关于不使用这些标志(其他实现中不存在)的情况。)

1 我检查过 OpenBSD、GNU coreutils 和 BusyBox。

答案1

来自规格:

  • 如果bs=expr指定了操作数并且没有请求除syncnoerror、 或之外的任何转换notrunc,从每个输入块返回的数据应写入单独的输出块;如果read()返回小于完整块并且sync未指定转换,则生成的输出块应与输入块的大小相同。

所以这可能就是导致您困惑的原因。是的,因为dd设计的对于阻塞,默认情况下,partial read()s将按1:1映射到partial write()s,否则在指定时sync在尾部填充NUL或空格字符到bs=大小。conv=sync

这意味着dd就是可安全用于复制数据(不存在因部分读取或写入而导致损坏的风险)在每种情况下,除了受到参数任意限制的情况外count=,否则它dd会很高兴地write()以与输入相同大小的块输出,read()直到read()完全通过它。甚至这个警告也是唯一真实的bs=指定obs=或是不是指定,正如规范中的下一句话所述:

  • 如果bs=expr未指定操作数,或请求除syncnoerror、 或之外的转换notrunc,则应处理输入并收集到全尺寸输出块中直到到达输入的末尾。

如果没有ibs=and/orobs=参数,这并不重要 - 因为默认情况下ibs和 的obs大小相同。然而,你可以得到明确的关于输入缓冲,通过指定不同尺寸对于任一和不是指定bs= (因为它优先)

例如,如果您这样做:

IN| dd ibs=1| OUT

...然后 POSIXdd将以write()512 字节为单位收集将每个read()字节放入单个输出块中。

否则的话,如果你...

IN| dd obs=1kx1k| OUT

...POSIXddread() 最大一次 512 个字节,但write()每个兆字节大小的输出块(内核允许和排除可能的最后一个 - 因为那是 EOF)通过收集输入来完整地全尺寸输出块

不过,也来自规范:

  • count=n
    • 仅复制n输入块。

count=映射到i?bs=块,因此为了处理count=可移植性的任意限制,您需要两个dds.使用两个 s 来实现这一点的最实用方法dd是将一个的输出通过管道传输到另一个的输入中,这无疑使我们进入了读/写 a 的领域。特殊文件无论原始输入类型如何。

IPC 管道意味着,在指定[io]bs=args 时,为了安全地执行此操作,必须将这些值保持在系统定义的PIPE_BUF限制内。 POSIX 规定系统内核必须仅保证原子read()s 和s在 中定义的write()范围内PIPE_BUFlimits.hPIPE_BUFPOSIX保证至少...

  • {_POSIX_PIPE_BUF}
    • 写入管道时保证原子性的最大字节数。
    • 价值:512

...(这也恰好是默认的ddi/o 块大小),但实际值通常至少为 4k。在最新的 Linux 系统上,默认情况下它是 64k。

因此,当您设置dd流程时,您应该在一个块上进行因素基于三个值:

  1. bs = ( obs =PIPE_BUF或更小 )
  2. n = 所需读取的总字节数
  3. 计数 = n / BS

喜欢:

yes | dd obs=1k | dd bs=1k count=10k of=/dev/null
10240+0 records in
10240+0 records out
10485760 bytes (10 MB) copied, 0.1143 s, 91.7 MB/s

您必须同步 i/ow/dd来处理不可查找的输入。换句话说,使管道缓冲区显式化,它们就不再是问题了。这就是dd目的。这里的未知量是yes的缓冲区大小 - 但如果你将其阻止为已知的与另一个数量dd然后一点点的乘法可以使dd 可安全用于复制数据(不存在因部分读取或写入而导致损坏的风险)即使count=在任何 POSIX 系统上任意限制输入w/ w/ 任意输入类型并且不会丢失单个字节。

这是来自POSIX 规范:

  • ibs=expr
    • 指定输入块大小(以字节为单位)expr (默认为 512)
  • obs=expr
    • 指定输出块大小(以字节为单位)expr (默认为 512)
  • bs=expr
    • 将输入和输出块大小设置为expr字节,取代ibs=obs=.如果除syncnoerror、 和之外未notrunc指定任何转换,则每个输入块应作为单个块复制到输出,而不聚合短块。

您还会发现其中一些内容得到了更好的解释这里

答案2

对于套接字、管道或 tty,read() 和 write() 可以传输小于请求的大小,因此在这些上使用 dd 时,需要 fullblock 标志。然而,对于常规文件和块设备,只有两次可以进行短暂的读/写:到达 EOF 时,或者出现错误时。这就是为什么不带 fullblock 标志的 dd 的旧实现可以安全地用于磁盘复制。

相关内容