dd什么时候适合复制数据?(或何时分别使用read()和write())


60

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

长版—前言: dd通常用于复制数据,尤其是从设备复制数据或向设备复制数据(示例)。有时,它被赋予了神秘的属性,即能够以比其他工具更低的级别访问设备(实际上,正是设备文件在发挥作用)-但dd if=/dev/sda与相同cat /dev/sdadd有时被认为是更快,但cat在实践中可以击败它。但是,它dd具有独特的属性,有时使其真正有用

问题: dd if=foo of=bar实际上与并不相同cat <foo >bar。在大多数unices¹上,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始终是安全的。我的问题是不使用这些标志(在其他实现中不存在)的情况)

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


我从未见过任何Unixy系统真的可以在一次read(2)中读取几个MiB ...
vonbrand

3
当使用时count,该iflag=fullblock为必填项(或iflag=count_bytes)。没有oflag=fullblock
弗罗斯特斯2015年

Answers:


10

规格

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

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

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

  • 如果bs=expr未指定操作数,或未请求,或进行转换sync,则应处理输入并将其收集到完整尺寸的输出块中,直到到达输入结尾为止。noerrornotrunc

没有ibs=和/或obs=参数就没有关系-因为ibsobs缺省情况下都是相同的大小。但是,您可以通过为两者指定不同的大小而不指定bs= (因为它具有优先级)明确了解输入缓冲。

例如,如果您这样做:

IN| dd ibs=1| OUT

...然后,通过dd将每个单个字节收集到单个输出块中,POSIX 将以write()512 字节为块。read()

否则,如果您这样做...

IN| dd obs=1kx1k| OUT

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

同样来自规范:

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

count=映射到i?bs=块,因此为了处理count=可移植的任意限制,您需要两个dds。使用2 dds 的最实用方法是将一个输出与另一个输入进行管道传输,这无疑使我们处于读写特殊文件的境界,而与原始输入类型无关。

IPC管道意味着[io]bs=,为了安全地指定args,必须将这些值保持在系统定义的PIPE_BUF限制内。POSIX指出,系统内核只能保证原子read()S和write()限度内小号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

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

这是POSIX规范的摘录:

  • ibs=expr
    • 指定输入块的大小(以字节为单位)(默认值为512)expr
  • obs=expr
    • 指定输出块的大小(以字节为单位)(默认值为512)expr
  • bs=expr
    • 将输入和输出块大小均设置为expr字节,ibs=并替换为和obs=。如果没有转换以外syncnoerrornotrunc被指定时,每一个输入块应被复制到输出为单个块,而不聚集短块。

您还将在这里找到一些更好的解释。


5

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


所有现代大学都是这样吗?(我知道在某些情况下Linux并非如此,可能高达2.0.x或2.2.x。我默默地mke2fs失败了,因为它write()以非2的幂次方(3kB IIRC)调用并且内核取整下降到2的幂。)
Gilles

@Gilles听起来完全像是另一个问题。您始终必须对块设备使用适当块大小的倍数。我可以肯定,所有标准都是如此,而Windows也是如此。
psusi 2011年

除磁带外,设备的块大小纯粹是内核关心或不关心的。cat </dev/sda >/dev/sdb可以很好地克隆磁盘。
Gilles

@Gilles,这是因为猫使用了适当的块大小,正如OrbWeaver在回答中指出的那样。
psusi 2011年

不,没有“适当的块大小”。cat选择性能的缓冲区大小;它不会从内核获取任何与设备有关的信息。除了磁带,你可以read()write()与任何大小的块设备。至少在Linux上,st_blksize仅取决于块设备索引节点所在的文件系统,而不取决于底层设备。
吉尔斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.