让我们创建几个包含任何内容的文件,例如,大小为 2048 和 115424 字节:
$ truncate -s 2048 f1
$ truncate -s 115424 f2
让我们编写一个脚本,打印这些文件和另外 12465440 个来自 /dev/zero 的字节:
#!/bin/sh
cat f1 f2
dd if=/dev/zero bs=12465440 count=1 2>/dev/null
学校的算术知识告诉我们,这个脚本应该输出12582912字节:
$ ./script.sh | dd > something
24575+2 записей получено
24576+0 записей отправлено
12582912 байт (13 MB, 12 MiB) скопирован, 0,268635 s, 46,8 MB/s
如果我们指定 bs=1 count=12582912,那么按照同样的算法,什么都不会改变:
$ ./script.sh | dd bs=1 count=12582912 > something
12582912+0 записей получено
12582912+0 записей отправлено
12582912 байт (13 MB, 12 MiB) скопирован, 36,3194 s, 346 kB/s
该产品不应该改变乘法器的位置,但由于某种原因Linux不同意我的看法:
$ ./script.sh | dd bs=12582912 count=1 > something
0+1 записей получено
0+1 записей отправлено
2048 байт (2,0 kB, 2,0 KiB) скопирован, 0,00340212 s, 602 kB/s
如果您尝试再运行此命令几次,那么算法将开始完全发疯:
$ ./script.sh | dd bs=12582912 count=1 > something
0+1 записей получено
0+1 записей отправлено
117472 байт (117 kB, 115 KiB) скопирован, 0,00399192 s, 29,4 MB/s
发生了什么?为什么最后一条命令没有输出 12582912 字节?神秘数字 2048 和 117472 的由来是什么?
@Alexander Prokoshev 基本上已经在他的回答(研究)中描述了所有内容,因此我将尝试以更短的方式描述观察到的图片。
因此,第一次调用
dd count=12582912 bs=1时,您要求dd从stdin(在您的情况下pipe)读取 12582912 次(参数count)1 个字节(参数bs)。stdout因为它读取的脚本dd被编程为返回 12582912 字节dd,所以它成功读取了所有数据。在第二种情况下,您要求
dd读取stdin一次12582912 个字节。但是,与参数一起使用的read系统调用被设计为当从(或另一个字符设备)读取时,它读取当前可用的字节数(在这种情况下,这是脚本写入的字节数to 在通话时)(自然,永远不会阅读超过给出的内容)。ddbspipepiperead()stdoutread()stdout由于操作系统的内部状态总是不同的,因此在运行测试时会得到不同的结果(即,您测量脚本在读取时设法写入的字节数dd)。附言
由于磁带相当特殊的块结构,在处理 磁带(现在基本被遗忘)时,使用参数
bs(更准确地说ibs, )很重要。obs这并不完全是一个答案,而是一些研究和讨论邀请。
在我的系统上,布局如下:
脚本:
发射:
./script |strace -f -o OUT dd bs=12582912 count=1 >something结果是 117472 字节,即大小
f1和的总和f2。strace 在感兴趣部分的输出是:read(0, "o\\\257\201\201;\337W\207\334\345k\343\231I[\f@\207}E\307\362\252\351%\342\317\\\tS\307"..., 12582912) = 117472strace“在管道的左侧”表明,
dd当试图将文件写入stdout时,它收到了SIGPIPE,即dd“在右侧”已经完成了这一点。结论:
read(),收费读取 12465440 字节,实际上读取较少,也就是说,它的工作原理与 mana 中写的完全一样:“read() 尝试读取最多count 个字节......”dd完全按照手册中的说明工作:因此,根据手册,不能期望
dd准确地读取块大小和字节块数的乘积。不。它会读取不超过这个产品,并且看起来调用次数read()将等于参数count(除非文件结束或更早发生错误),并且每个欧姆返回的read()字节数不会超过参数bs。另一件事是我未能预测“数据块”的大小。此外,如果您将 strace 放在命令的最开头或直接放在管道的右侧,那么在我的系统上它
read()会读取不同数量的字节;它要么等于大小 f1+f2 的总和,要么等于 f1 的大小。一点关于一切如何在引擎盖下工作。
cat的工作非常简单:对于每个文件,它首先将其中的一部分读入其缓冲区(在用户空间中),然后将其写入
stdout,依此类推,直到文件结束。在原始版本中¹它看起来像这样:今天的典型缓冲区大小为 128k。
dd的工作原理几乎相同,只是它读取的块的大小在 arguments 中设置
bs,并且在此过程中它会计算读取的块,直到它count再次达到原始版本:正如其他答案已经说过的那样,系统调用的一个重要方面
read()是它可能返回的字节数可能少于请求的字节数。管道本身(pipe,pipe,
|)在内核空间中包含一个缓冲区(在 x86 后和现代内核上大小为 64k)也很重要。当写入管道时,数据被复制到这个缓冲区,如果没有足够的数据,那么试图写入它的进程将被挂起并且调用write()不会返回,直到有人开始从管道读取。同样,如果缓冲区为空,则尝试读取的进程将暂停,直到有人写入管道。到底是怎么回事?
所以,当被调用时
cat f1 f2 | dd bs=12582912 count=1cat大约进行以下调用序列:在这种情况下,第
write一个不应该阻塞进程,因为。cat将保证仅在第二个上被阻止。第一个相当同时,与此
dd并行,它执行以下操作:因为
count等于1,则不会出现多个读数。再次,
cat并行dd工作,这个词只是乞求在进程之间引起竞争条件,因此与管道相关的调用有几种可能的顺序:dd打败所有人:在这种情况下,它将
dd阻塞read ()并等待至少一些数据进入管道。在第一次write()从cat'a 调用时,内核将直接将数据从一个进程的缓冲区复制到另一个进程的缓冲区并解锁dd. 之后,它将生成的缓冲区写入stdout,报告2048它已读取的字节,然后退出。dd设法在两次调用cat'a:cat将内容写入f1管道缓冲区并继续其工作,然后dd从缓冲区中读取它并以与上次相同的结果结束。cat让它更快:cat再次将内容写入管道缓冲区f1,然后尝试写入内容f2,但由于 缓冲区大小对此不够用,然后它会阻塞,直到dd它开始读取。发生这种情况时,它将dd获得两个缓冲区:内核缓冲区和等待进程的缓冲区。结果,117472将读取所有字节。在没有真正的并行操作的单处理器(或只是轻微负载)系统上,
cat还有dd另一个有趣的选择:在情况1和3之间(为了感兴趣,我们可以假设传输的文件cat很多大于 128k):那些。
dd开始阅读。并阻塞调用read(),然后处理器时间转移到'y,他设法cat在分配的时间间隔(时间片)的帮助下write()尽可能多地填充缓冲区(假设文件已经在缓存中并且他不需要被阻止从磁盘读取) 。之后,控制权被转移dd,然后才从read().该怎么办?
如果部分读取发生在除最后一个块之外的某个块上(特别是在这种情况下,如果更改
count为2),则它会仔细警告那些。要
dd更改算法以使其处理一次调用中未完全读取的块,您只需添加iflag=fullblock:请参阅 中的详细信息
man dd。显然,这些分别是文件大小f1和f1+f2。
¹ 在下文中,为简单起见,将省略所有错误处理、附加标志和异常情况。
dd 读取指定大小的一个传入的 STDOUT 数据块(从 RAM)。
cat因为 script.sh 使用三个不同的调用来填充 STDOUT,一个用于它通过另一个 for读取的每个文件,并且用于存储该数据的连续dd内存块的机会可以忽略不计。它们可以按任何顺序放置。通过一个例子可以很容易地看出这一点。让我们分别创建三个文件 1、2 和 3 个字节。
我们将在帮助下读取它们,
cat并且接收到的这三个文件的数据集合(只有 6 个字节:1 + 2 + 3)将被传输dd以进行处理,大小为 10 个字节(应该一次读取)。在第一次启动时,只有一个字节成功,其余的显然是“遥远的”。
在第二次运行中,我们能够一次阅读所有六个。幸运的。
好吧,第三次,要么两个文件
a1并a2在一起,要么一个a3.因此,您希望一次读取的字节数 X只有在这些 X位于同一内存块中
dd bs=X count=1时才等于X。好吧,如果有足够的内存来读取自己,当然:有 512MB 的 RAM 可用,你不能一次超过 1GB。如果你交换 f1 和 f2,那么你更有可能得到答案,不仅是 2048 和这些文件的总和,还有 f2 的大小。