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

Gil*_*il' 73 io dd

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

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

问题: dd if=foo of=bar实际上与cat <foo >bar. 在大多数 unices¹ 上,ddread(). (我发现POSIX在什么构成“读取输入块”方面很模糊dd。)如果read()返回部分结果(根据 POSIX 和其他参考文档,除非实现文档另有说明,否则允许这样做),则复制部分块。存在完全相同的问题write()

观察:在实践中,我发现dd可以处理块设备和常规文件,但这可能只是我没有太多练习。说到管道,不难找dd错;例如试试这个代码

yes | dd of=out bs=1024k count=10
Run Code Online (Sandbox Code Playgroud)

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

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

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

¹ 我已经检查过 OpenBSD、GNU coreutils 和 BusyBox。

mik*_*erv 23

规范

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

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

这意味着在任何情况下都dd可以安全地用于复制数据(没有由于部分读取或写入而导致损坏的风险),但在这种情况下它会受到count=参数的任意限制,否则dd将很高兴地将write()其输出放在相同大小的块中到那些它的输入read()直到它read()完全通过它。即使这需要注意的是唯一真正bs=指定或者obs=指定,在规范的状态非常下一句:

  • 如果bs=expr未指定操作数,或请求除syncnoerror、 或以外的转换,notrunc则应处理输入并将其收集到完整大小的输出块中,直到到达输入的末尾。

没有ibs=和/或obs=参数这无关紧要 - 因为默认情况下ibsobs都是相同的大小。但是,您可以通过为两者指定不同的大小而不指定bs= (因为它优先)明确输入缓冲。

例如,如果你这样做:

IN| dd ibs=1| OUT
Run Code Online (Sandbox Code Playgroud)

...然后 POSIXddwrite()通过将每个单独的字节收集read()到单个输出块中,以 512 字节的块为单位。

否则,如果你...

IN| dd obs=1kx1k| OUT
Run Code Online (Sandbox Code Playgroud)

......一个POSIXddread() 在最大512个字节的时间,但write()每兆字节大小的输出块(内核允许和可能除外最后-因为这是EOF)全部通过收集输入到全尺寸的输出块

不过也来自规范:

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

count=映射到i?bs=块,因此为了count=可移植地处理任意限制,您需要两个dds。用两个dds做到这一点最实用的方法是将一个的输出通过管道传输到另一个的输入,这肯定会让我们处于读取/写入特殊文件的领域,而不管原始输入类型如何。

IPC 管道意味着在指定[io]bs=args 时,为了安全地这样做,您必须将这些值保持在系统定义的PIPE_BUF限制内。POSIX 声明系统内核必须只保证原子read()s 和write()s 在PIPE_BUF定义的范围内limits.h。POSIX保证PIPE_BUF至少...

  • {_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
Run Code Online (Sandbox Code Playgroud)

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

这是POSIX 规范的一个片段:

  • ibs=expr
    • 指定输入块大小,以字节为单位,by (default is 512)expr
  • obs=expr
    • 指定输出块大小,以字节为单位,by (default is 512)expr
  • bs=expr
    • 将输入和输出块大小都设置为expr字节,取代ibs=obs=。如果没有指定除sync,noerror和之外的任何转换,notrunc则每个输入块应作为单个块复制到输出,而不聚合短块。

您还可以在此处找到其中一些更好的解释。


psu*_*usi 6

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