Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

dd: почему нельзя просто так взять и поменять значения bs и count местами?

#linux


Создадим парочку файлов с неважно каким содержимым, с размером например 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


От перемены мест множителей произведение измениться не должно, но линукс со мной
почему-то не согласен:

$ ./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?
    


Ответы

Ответ 1



@Alexander Prokoshev по сути уже все описал в своем ответе (исследовании), поэтому постараюсь описать наблюдаемую картину покороче. Итак, первый раз, вызывая dd count=12582912 bs=1, вы просите у dd читать из stdin (в вашем случае это pipe) 12582912 раз (аргумент count) по 1 байту (аргумент bs). Поскольку скрипт, stdout которого читает dd, был запрограммирован на выдачу 12582912 байт dd успешно прочла все данные. Во втором случае, вы просите dd читать из stdin один раз 12582912 байт. Однако, системный вызов read, который использует dd с аргументом bs, устроен так, что при чтении из pipe (или другого символьного устройства) он читает столько байт, сколько в данный момент доступно (в случае pipe это количество байт, которые к моменту вызова read() было записано скриптом в stdout) (естественно, read() никогда не читает больше, чем в нем задано). Поскольку внутреннее состояние ОС все время разное, вы при разных запусках своего теста получаете разные результаты (т.е. вы измеряете количество байт, которые скрипт успел записать в stdout к моменту чтения в dd). P.S. Использование аргумента bs (точнее ibs, obs) было важно при работе с магнитными лентами (сейчас практически забытыми) из-за их довольно специфичной блочной структуры.

Ответ 2



Это не то чтобы ответ, скорее, некоторое исследование и приглашение к обсуждению. На моей системе расклад следующий: 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 «на правой стороне» к этому моменту уже завершился. Выводы: read(), заряженный на чтение 12465440 байт, читает по факту меньше, то есть работает в точности так, как написано в мане: «read() attempts to read up to count bytes...» 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



Немного о том, как всё работает под капотом. cat работает достаточно просто: для каждого файла сначала читает его кусок в свой буфер (в пользовательском пространстве), а затем записывает его в stdout и так пока файл не закончится. В примитивном варианте¹ это выглядит как-то так: while (1) { ssize nreads = read (fd, buff, BUFF_SZ); if (nreads<=0) { break; } write (fd, buff, nreads) } Типовой размер буфера сегодня — 128k. dd работает практически также, только размер блока, который он читает задаётся в аргументах bs, а также по ходу дела он отсчитывает прочитанные блоки пока не дойдёт до count, опять же, в примитивном варианте: for (int i=0; i

Ответ 4



Что происходит? $ ./script.sh | dd bs=12582912 count=1 > something dd считывает один входящий блок данных STDOUT (из ОЗУ) указанного размера. Почему последняя команда не выдаёт 12582912 байт? Потому что script.sh использует три разных вызова для наполнения STDOUT, по одному для каждого считываемого файла посредством cat другой для 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, которое вы ожидаете прочитать за раз с помощью dd bs=X count=1 будет равно X лишь только в том случае, когда эти X будут расположены в одном блоке памяти. Ну и если хватит памяти на само чтение, конечно же: имея в наличии 512Мб ОЗУ, 1Гб за раз вам не осилить. Каково происхождение загадочных чисел 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

Комментариев нет:

Отправить комментарий