简洁版本:在什么情况下可以dd
安全地用于复制数据,安全意味着不存在因部分读取或写入而导致损坏的风险?
长版——序言: dd
通常用于复制数据,尤其是从设备复制数据或向设备复制数据(例子)。它有时被认为具有比其他工具更低级别的访问设备的神秘属性(事实上是设备文件发挥了魔力)——但dd if=/dev/sda
与cat /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
指定了操作数并且没有请求除sync
、noerror
、 或之外的任何转换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
未指定操作数,或请求除sync
、noerror
、 或之外的转换notrunc
,则应处理输入并收集到全尺寸输出块中直到到达输入的末尾。
如果没有ibs=
and/orobs=
参数,这并不重要 - 因为默认情况下ibs
和 的obs
大小相同。然而,你可以得到明确的关于输入缓冲,通过指定不同尺寸对于任一和不是指定bs=
(因为它优先)。
例如,如果您这样做:
IN| dd ibs=1| OUT
...然后 POSIXdd
将以write()
512 字节为单位收集将每个read()
字节放入单个输出块中。
否则的话,如果你...
IN| dd obs=1kx1k| OUT
...POSIXdd
将read()
最大一次 512 个字节,但write()
每个兆字节大小的输出块(内核允许和排除可能的最后一个 - 因为那是 EOF)通过收集输入来完整地全尺寸输出块。
不过,也来自规范:
count=n
- 仅复制n输入块。
count=
映射到i?bs=
块,因此为了处理count=
可移植性的任意限制,您需要两个dd
s.使用两个 s 来实现这一点的最实用方法dd
是将一个的输出通过管道传输到另一个的输入中,这无疑使我们进入了读/写 a 的领域。特殊文件无论原始输入类型如何。
IPC 管道意味着,在指定[io]bs=
args 时,为了安全地执行此操作,必须将这些值保持在系统定义的PIPE_BUF
限制内。 POSIX 规定系统内核必须仅保证原子read()
s 和s在 中定义的write()
范围内PIPE_BUF
limits.h
。PIPE_BUF
POSIX保证至少...
{_POSIX_PIPE_BUF}
- 写入管道时保证原子性的最大字节数。
- 价值:512
...(这也恰好是默认的dd
i/o 块大小),但实际值通常至少为 4k。在最新的 Linux 系统上,默认情况下它是 64k。
因此,当您设置dd
流程时,您应该在一个块上进行因素基于三个值:
- bs = ( obs =
PIPE_BUF
或更小 ) - n = 所需读取的总字节数
- 计数 = 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=
.如果除sync
、noerror
、 和之外未notrunc
指定任何转换,则每个输入块应作为单个块复制到输出,而不聚合短块。
- 将输入和输出块大小设置为
您还会发现其中一些内容得到了更好的解释这里。
答案2
对于套接字、管道或 tty,read() 和 write() 可以传输小于请求的大小,因此在这些上使用 dd 时,需要 fullblock 标志。然而,对于常规文件和块设备,只有两次可以进行短暂的读/写:到达 EOF 时,或者出现错误时。这就是为什么不带 fullblock 标志的 dd 的旧实现可以安全地用于磁盘复制。