Чем можно объяснить эту странную редкую обработку файлов в / в tmpfs?

14

На моем ext4разделе файловой системы я могу запустить следующий код:

fs="/mnt/ext4"

#create sparse 100M file on ${fs}
dd if=/dev/zero \
   of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2> /dev/null

#show its actual used size before
echo "Before:"
ls ${fs}/sparse100M -s

#setting the sparse file up as loopback and run md5sum on loopback
losetup /dev/loop0 ${fs}/sparse100M 
md5sum /dev/loop0

#show its actual used size afterwards
echo "After:"
ls ${fs}/sparse100M -s

#release loopback and remove file
losetup -d /dev/loop0
rm ${fs}/sparse100M

который дает

Before:
0 sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
0 sparse100M

Делаем то же самое с tmpfs, как с:

fs="/tmp"

доходность

Before:
0 /tmp/sparse100M
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
After:
102400 /tmp/sparse100M

что в основном означает, что то, что я ожидал, просто прочитав данные, заставило разреженный файл «взорваться, как воздушный шар»?

Я ожидаю, что это из-за менее совершенной поддержки разреженных файлов в tmpfsфайловой системе, и в частности из-за отсутствия ioctl FIEMAP, но я не уверен, что вызывает такое поведение? Можешь мне сказать?

humanityANDpeace
источник
гул. Существует разделяемая (копируемая при записи) нулевая страница, которую можно использовать, например, для разреженной страницы, требующей редактирования mmap (). Поэтому я не уверен, почему любой тип чтения из разреженного файла tmpfs потребовал бы выделения реальной памяти. lwn.net/Articles/517465 . Я задавался вопросом, было ли это каким-то побочным эффектом преобразования цикла в использование прямого ввода-вывода, но, похоже, не должно быть никакой разницы, когда вы пытаетесь использовать новый тип цикла в tmpfs. spinics.net/lists/linux-fsdevel/msg60337.html
sourcejedi
может быть, это могло бы получить ответ, если бы это было на SO? просто мысль
1
Вывод / tmp имеет разные файлы До / После. Это опечатка? До: 0 / tmp / sparse100 (без M в конце) После: 102400 / tmp / sparse100M (с конечным M).
YoMismo
@YoMismo, да, это была лишь небольшая опечатка
человечество и

Ответы:

4

Во-первых, вы не одиноки, ломая голову над такими проблемами.

Это не только ограничено, tmpfsно было проблемой, процитированной с NFSv4 .

Если приложение считывает «дыры» в разреженном файле, файловая система преобразует пустые блоки в «настоящие» блоки, заполненные нулями, и возвращает их в приложение.

Когда md5sumпытается сканировать файл, он явно решает сделать это в последовательном порядке , что имеет большой смысл в зависимости от того, что пытается сделать md5sum.

Так как в файле есть «дыры», это последовательное чтение (в некоторых ситуациях) приведет к операции копирования при записи, чтобы заполнить файл. Затем возникает более глубокий вопрос о том, fallocate()поддерживает ли реализованная в файловой системе система FALLOC_FL_PUNCH_HOLE.

К счастью, это не только tmpfsподдерживает, но и есть механизм, чтобы «выкопать» отверстия обратно.

Используя утилиту CLI, fallocateмы можем успешно обнаружить и заново выкопать эти дыры.

Согласно man 1 fallocate:

-d, --dig-holes
      Detect and dig holes.  This makes the file sparse in-place, without
      using extra disk space.  The minimum size of the hole depends on
      filesystem I/O  block size (usually 4096 bytes).  Also, when using
      this option, --keep-size is implied.  If no range is specified by
      --offset and --length, then the entire file is analyzed for holes.

      You can think of this option as doing a "cp --sparse" and then
      renaming the destination file to the original, without the need for
      extra disk space.

      See --punch-hole for a list of supported filesystems.

fallocateработает на уровне файлов, и когда вы работаете md5sum с блочным устройством (запрашивая последовательное чтение), вы fallocate()попадаете в точный промежуток между тем, как должен работать системный вызов. Мы можем увидеть это в действии:

В действии, используя ваш пример, мы видим следующее:

$ fs=$(mktemp -d)
$ echo ${fs}
/tmp/tmp.ONTGAS8L06
$ dd if=/dev/zero of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2>/dev/null
$ echo "Before:" "$(ls ${fs}/sparse100M -s)"
Before: 0 /tmp/tmp.ONTGAS8L06/sparse100M
$ sudo losetup /dev/loop0 ${fs}/sparse100M
$ sudo md5sum /dev/loop0
2f282b84e7e608d5852449ed940bfc51  /dev/loop0
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 102400 /tmp/tmp.ONTGAS8L06/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ONTGAS8L06/sparse100M

Теперь ... это отвечает на ваш основной вопрос. Мой общий девиз - "Странно", поэтому я продолжил ...

$ fs=$(mktemp -d)
$ echo ${fs}
/tmp/tmp.ZcAxvW32GY
$ dd if=/dev/zero of=${fs}/sparse100M conv=sparse seek=$((100*2*1024-1)) count=1 2>/dev/null
$ echo "Before:" "$(ls ${fs}/sparse100M -s)"
Before: 0 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo losetup /dev/loop0 ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 1036 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 1036 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 520 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 520 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 516 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 512 /tmp/tmp.ZcAxvW32GY/sparse100M
$ fallocate -d ${fs}/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ZcAxvW32GY/sparse100M
$ sudo md5sum ${fs}/sparse100M
2f282b84e7e608d5852449ed940bfc51  /tmp/tmp.ZcAxvW32GY/sparse100M
$ echo "After:" "$(ls ${fs}/sparse100M -s)"
After: 0 /tmp/tmp.ZcAxvW32GY/sparse100M

Вы видите , что только акт выполненияlosetup изменяет размер разреженного файла. Таким образом, это становится интересной комбинацией того, где tmpfsпересекаются механизм HOLE_PUNCH fallocateи блочные устройства.

Брайан Редберд
источник
2
Спасибо за Ваш ответ. Я в курсе, tmpfsподдерживает разреженные файлы и punch_hole. Вот что делает его таким запутанным - tmpfs поддерживает это, так зачем идти и заполнять разреженные отверстия при чтении через петлевое устройство? losetupне изменяет размер файла, но создает блочное устройство, которое в большинстве систем затем сканируется на предмет содержимого: есть ли таблица разделов? Есть ли файловая система с UUID? тогда я должен создать / dev / disk / by-uuid / symlink? И эти чтения уже приводят к тому, что части разреженного файла выделяются, потому что по какой-то таинственной причине tmpfs заполняет дыры в (некоторых) чтениях.
frostschutz
1
Не могли бы вы уточнить, « последовательное чтение будет (в некоторых случаях) вызывать операцию копирования при записи », пожалуйста? Мне любопытно понять, как операция чтения вызовет копирование при действии записи. Благодарность!
Ройма
Это странно В моей системе я выполнял те же действия, но вручную, а не в сценарии. Сначала я сделал файл 100M, как OP. Затем я повторил шаги только с 10 МБ файлом. Первый результат: ls -s sparse100M был 102400. Но ls -s в файле 10 МБ был только 328 блоков. ??
Патрик Тейлор
1
@PatrickTaylor ~ 328K - это то, что использовалось после того, как появились UUID-сканеры, но вы не катили / md5sum циклическое устройство для полного чтения.
frostschutz
1
Я копался в источнике для модуля ядра цикла (in loop.c) и увидел, что есть две соответствующие функции : lo_read_simple& lo_read_transfer. Есть некоторые незначительные различия в том, как они выполняют низкоуровневое выделение памяти ... lo_read_transferфактически запрашивает неблокирующую функцию io из slab.h( GFP_NOIO) во время выполнения alloc_page()вызова. lo_read_simple()с другой стороны, не выполняет alloc_page().
Брайан Редберд