RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1027414
Accepted
andreymal
andreymal
Asked:2020-09-24 07:27:39 +0000 UTC2020-09-24 07:27:39 +0000 UTC 2020-09-24 07:27:39 +0000 UTC

dd:为什么不能直接取和交换bs和count的值呢?

  • 772

让我们创建几个包含任何内容的文件,例如,大小为 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 的由来是什么?

linux
  • 4 4 个回答
  • 10 Views

4 个回答

  • Voted
  1. Best Answer
    avp
    2020-09-25T03:42:52Z2020-09-25T03:42:52Z

    @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

    • 4
  2. user_587
    2020-09-24T20:30:12Z2020-09-24T20:30:12Z

    这并不完全是一个答案,而是一些研究和讨论邀请。

    在我的系统上,布局如下:

    f1 2048   bytes
    f2 115424 bytes
    

    脚本:

    #!/bin/sh
    cat f1 ff
    dd if=/dev/zero bs=12465440 count=1 2>/dev/null
    

    发射: ./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) = 117472

    strace“在管道的左侧”表明,dd当试图将文件写入stdout时,它收到了SIGPIPE,即dd“在右侧”已经完成了这一点。

    结论:

    1. read(),收费读取 12465440 字节,实际上读取较少,也就是说,它的工作原理与 mana 中写的完全一样:“read() 尝试读取最多count 个字节......”
    2. dd完全按照手册中的说明工作:

      bs=BYTES
            read and write *up to* BYTES bytes at a time
      ...
      count=N
            copy only N input blocks
      

    因此,根据手册,不能期望dd准确地读取块大小和字节块数的乘积。不。它会读取不超过这个产品,并且看起来调用次数read()将等于参数count(除非文件结束或更早发生错误),并且每个欧姆返回的read()字节数不会超过参数bs。

    另一件事是我未能预测“数据块”的大小。此外,如果您将 strace 放在命令的最开头或直接放在管道的右侧,那么在我的系统上它read()会读取不同数量的字节;它要么等于大小 f1+f2 的总和,要么等于 f1 的大小。

    • 3
  3. Fat-Zer
    2020-09-27T07:20:01Z2020-09-27T07:20:01Z

    一点关于一切如何在引擎盖下工作。

    • cat的工作非常简单:对于每个文件,它首先将其中的一部分读入其缓冲区(在用户空间中),然后将其写入stdout,依此类推,直到文件结束。在原始版本中¹它看起来像这样:

      while (1) {
        ssize nreads = read (fd, buff, BUFF_SZ);
        if (nreads<=0) {
          break;
        }
        write (fd, buff, nreads)
      }
      

      今天的典型缓冲区大小为 128k。

    • dd的工作原理几乎相同,只是它读取的块的大小在 arguments 中设置bs,并且在此过程中它会计算读取的块,直到它count再次达到原始版本:

      for (int i=0; i<count; i++) {
        ssize nreads = read (fd, buff, bs);
        if (nreads<=0) {
          break;
        }
        write (fd, buff, nreads)
      }
      
    • 正如其他答案已经说过的那样,系统调用的一个重要方面read()是它可能返回的字节数可能少于请求的字节数。

    • 管道本身(pipe,pipe,|)在内核空间中包含一个缓冲区(在 x86 后和现代内核上大小为 64k)也很重要。当写入管道时,数据被复制到这个缓冲区,如果没有足够的数据,那么试图写入它的进程将被挂起并且调用write()不会返回,直到有人开始从管道读取。同样,如果缓冲区为空,则尝试读取的进程将暂停,直到有人写入管道。

    到底是怎么回事?

    所以,当被调用时cat f1 f2 | dd bs=12582912 count=1

    cat大约进行以下调用序列:

    cat: f1_sz = read (f1_fd, buf, 128*1024); // 2048
    cat:         write (pipefd, buf, f1_sz);  // ok
    cat: f2_sz = read (f2_fd, buf, 128*1024); // 115424
    cat:         write (pipefd, buf, f2_sz);  // block
    

    在这种情况下,第write一个不应该阻塞进程,因为。 cat将保证仅在第二个上被阻止。第一个相当

    同时,与此dd 并行,它执行以下操作:

    dd : rd_sz = read (pipefd, buf, bs); // (?) block
    dd :         write (1 /*stdout*/, buf, rd_sz);
    

    因为 count等于1,则不会出现多个读数。

    再次,cat并行dd工作,这个词只是乞求在进程之间引起竞争条件,因此与管道相关的调用有几种可能的顺序:

    1. dd打败所有人:

      dd : rd_sz = read (pipefd, buf, bs);     // block
      cat:         write (pipefd, buf, f1_sz); // ok
      cat:         write (pipefd, buf, f2_sz); // error
      

      在这种情况下,它将dd阻塞read ()并等待至少一些数据进入管道。在第一次write()从cat'a 调用时,内核将直接将数据从一个进程的缓冲区复制到另一个进程的缓冲区并解锁dd. 之后,它将生成的缓冲区写入stdout,报告2048它已读取的字节,然后退出。

    2. dd设法在两次调用cat'a:

      cat:         write (pipefd, buf, f1_sz); // ok
      dd : rd_sz = read (pipefd, buf, bs);     // ok
      cat:         write (pipefd, buf, f2_sz); // error
      

      cat将内容写入f1管道缓冲区并继续其工作,然后dd从缓冲区中读取它并以与上次相同的结果结束。

    3. cat让它更快:

      cat:         write (pipefd, buf, f1_sz); // ok
      cat:         write (pipefd, buf, f2_sz); // block
      dd : rd_sz = read (pipefd, buf, bs);     // ok
      

      cat再次将内容写入管道缓冲区f1,然后尝试写入内容f2,但由于 缓冲区大小对此不够用,然后它会阻塞,直到dd它开始读取。发生这种情况时,它将dd获得两个缓冲区:内核缓冲区和等待进程的缓冲区。结果,117472将读取所有字节。

    4. 在没有真正的并行操作的单处理器(或只是轻微负载)系统上,cat还有dd另一个有趣的选择:在情况1和3之间(为了感兴趣,我们可以假设传输的文件cat很多大于 128k):

      dd :       → read (pipefd, buf, bs);      // block
      cat:         write (pipefd, buf, BUF_SZ); // ok
      cat:         write (pipefd, buf, BUF_SZ); // ok
      cat: // ...
      cat: // У cat кончается время
      dd : rd_sz = read (/*...*/) → n*BUF_SZ;   // returns
      

      那些。dd开始阅读。并阻塞调用read(),然后处理器时间转移到'y,他设法cat在分配的时间间隔(时间片)的帮助下write()尽可能多地填充缓冲区(假设文件已经在缓存中并且他不需要被阻止从磁盘读取) 。之后,控制权被转移dd,然后才从read().

    该怎么办?

    如果部分读取发生在除最后一个块之外的某个块上(特别是在这种情况下,如果更改count为2),则它会仔细警告

    dd: warning: partial read (2048 bytes); suggest iflag=fullblock
    

    那些。要dd更改算法以使其处理一次调用中未完全读取的块,您只需添加iflag=fullblock:

    ./script.sh | dd bs=12582912 count=1 iflag=fullblock
    

    请参阅 中的详细信息man dd。


    神秘数字 2048 和 117472 的由来是什么?

    显然,这些分别是文件大小f1和f1+f2。


    ¹ 在下文中,为简单起见,将省略所有错误处理、附加标志和异常情况。

    • 2
  4. edem
    2020-09-24T14:00:47Z2020-09-24T14:00:47Z

    发生了什么?

    $ ./script.sh | dd bs=12582912 count=1 > something
    

    dd 读取指定大小的一个传入的 STDOUT 数据块(从 RAM)。

    为什么最后一条命令没有输出 12582912 字节?

    cat因为 script.sh 使用三个不同的调用来填充 STDOUT,一个用于它通过另一个 for读取的每个文件,并且用于存储该数据的连续dd内存块的机会可以忽略不计。它们可以按任何顺序放置。通过一个例子可以很容易地看出这一点。

    让我们分别创建三个文件 1、2 和 3 个字节。

    truncate -s 1 a1; truncate -s 2 a2; truncate -s 3 a3
    

    我们将在帮助下读取它们,cat并且接收到的这三个文件的数据集合(只有 6 个字节:1 + 2 + 3)将被传输dd以进行处理,大小为 10 个字节(应该一次读取)。

    在第一次启动时,只有一个字节成功,其余的显然是“遥远的”。

    cat a1 a2 a3 | dd bs=10 count=1 > something
    0+1 records in
    0+1 records out
    1 byte copied, 0.00144402 s, 0.7 kB/s
    

    在第二次运行中,我们能够一次阅读所有六个。幸运的。

    cat a1 a2 a3 | dd bs=10 count=1 > something
    0+1 records in
    0+1 records out
    6 bytes copied, 0.000348189 s, 17.2 kB/s
    

    好吧,第三次,要么两个文件a1并a2在一起,要么一个a3.

    cat a1 a2 a3 | dd bs=10 count=1 > something
    0+1 records in
    0+1 records out
    3 bytes copied, 0.000632707 s, 4.7 kB/s
    

    因此,您希望一次读取的字节数 X只有在这些 X位于同一内存块中dd bs=X count=1时才等于X。好吧,如果有足够的内存来读取自己,当然:有 512MB 的 RAM 可用,你不能一次超过 1GB。

    神秘数字 2048 和 117472 的由来是什么?

    • 2048 - 文件大小 f1。
    • 117472 是文件 f1 和 f2 的总大小 (2048 + 115424)。

    如果你交换 f1 和 f2,那么你更有可能得到答案,不仅是 2048 和这些文件的总和,还有 f2 的大小。

    cat f2 f1 | dd bs=12582912 count=1 > something
    0+1 records in
    0+1 records out
    117472 bytes (117 kB, 115 KiB) copied, 0.00181811 s, 64.6 MB/s
    
    cat f2 f1 | dd bs=12582912 count=1 > something
    0+1 records in
    0+1 records out
    115424 bytes (115 kB, 113 KiB) copied, 0.00124668 s, 92.6 MB/s
    
    • 0

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    根据浏览器窗口的大小调整背景图案的大小

    • 2 个回答
  • Marko Smith

    理解for循环的执行逻辑

    • 1 个回答
  • Marko Smith

    复制动态数组时出错(C++)

    • 1 个回答
  • Marko Smith

    Or and If,elif,else 构造[重复]

    • 1 个回答
  • Marko Smith

    如何构建支持 x64 的 APK

    • 1 个回答
  • Marko Smith

    如何使按钮的输入宽度?

    • 2 个回答
  • Marko Smith

    如何显示对象变量的名称?

    • 3 个回答
  • Marko Smith

    如何循环一个函数?

    • 1 个回答
  • Marko Smith

    LOWORD 宏有什么作用?

    • 2 个回答
  • Marko Smith

    从字符串的开头删除直到并包括一个字符

    • 2 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5