Является ли добавление файла атомарным в UNIX?

107

В общем, что мы можем считать само собой разумеющимся, когда добавляем к файлу в UNIX несколько процессов? Возможна ли потеря данных (один процесс перезаписывает изменения другого)? Возможно ли искажение данных? (Например, каждый процесс добавляет одну строку за каждое добавление в файл журнала, возможно ли, что две строки будут искажены?) Если добавление не является атомарным в указанном выше смысле, тогда как лучше всего обеспечить взаимное исключение?

Лайош Надь
источник

Ответы:

65

Запись размером меньше «PIPE_BUF» должна быть атомарной. Это должно быть не менее 512 байт, хотя легко может быть больше (похоже, в Linux установлено значение 4096).

Это предполагает, что вы говорите обо всех полностью совместимых с POSIX компонентах. Например, это не так в NFS.

Но при условии, что вы пишете в файл журнала, который вы открыли в режиме «O_APPEND», и сохраняете свои строки (включая новую строку) под байтами «PIPE_BUF», у вас должно быть несколько авторов записи в файл журнала без каких-либо проблем с повреждением. Любые прерывания поступят до или после записи, а не посередине. Если вы хотите, чтобы целостность файла сохранялась после перезагрузки, вам также нужно будет вызывать fsync(2)после каждой записи, но это ужасно для производительности.

Уточнение : прочтите комментарии и ответ Оз Соломона . Я не уверен, что O_APPENDон должен иметь PIPE_BUFатомарность такого размера. Вполне возможно, что так реализовано в Linux write(), или это может быть связано с размерами блоков базовой файловой системы.

Freiheit
источник
11
В нормальных файловых системах fsync(2)дает такую ​​же гарантию, как и sync(2)дает, и не оказывает такого большого влияния на производительность.
ephemient
4
Вы уверены, что? Не могли бы вы дать ссылку на это поведение? Я обнаружил, что это подтверждено, если дескриптор является каналом, но я не смог найти доказательств того, что он работает для любого файла. включая обычные файловые объекты, отличные от NFS.
Алан Франзони
6
Где именно в ... / write.html? Что касается O_APPEND, я не вижу упоминания о PIPE_BUF, и я вижу обещание, что « между изменением смещения файла и операцией записи не должно происходить никакой промежуточной операции модификации файла» , но я не уверен, означает ли это, что сама операция записи непрерывный ...
akavel
6
Как указывает этот ответ , утверждение PIPE_BUFна этой странице применимо только к каналам и FIFO, а не к обычным файлам.
Грег Иноземцев
3
При поступлении сигналов ситуация может стать еще хуже: bugzilla.kernel.org/show_bug.cgi?id=55651 . Почему это вообще помечено как ответ? PIPE_BUF не имеет ничего общего с файлами.
истончился
35

Изменить: обновлено в августе 2017 года с последними результатами Windows.

Я дам вам ответ со ссылками на тестовый код и результаты как автор предложенного Boost.AFIO, который реализует асинхронную файловую систему и библиотеку ввода-вывода файлов C ++.

Во-первых, O_APPEND или эквивалентный FILE_APPEND_DATA в Windows означает, что приращения максимального размера файла («длина» файла) являются атомарными при одновременной записи. Это гарантируется POSIX, и Linux, FreeBSD, OS X и Windows все реализуют это правильно. Samba также реализует это правильно, NFS до v5 - нет, поскольку в нем отсутствует возможность форматирования проводов для атомарного добавления. Таким образом, если вы откроете файл только с добавлением, одновременные записи не будут разрываться относительно друг друга в любой основной ОС, если не задействована NFS.

Однако при одновременном чтении в атомарные добавления могут наблюдаться разорванные записи в зависимости от ОС, файловой системы и того, с какими флагами вы открывали файл - приращение максимального размера файла является атомарным, но видимость записи относительно чтения может или не может быть атомарным. Вот краткое описание флагов, ОС и файловой системы:


Нет O_DIRECT / FILE_FLAG_NO_BUFFERING:

Microsoft Windows 10 с NTFS: атомарность обновления = 1 байт до 10.0.10240 включительно, с 10.0.14393 не менее 1 МБ, вероятно, бесконечно (*).

Linux 4.2.6 с ext4: атомарность обновления = 1 байт

FreeBSD 10.2 с ZFS: атомарность обновления = не менее 1 МБ, возможно, бесконечно (*)

O_DIRECT / FILE_FLAG_NO_BUFFERING:

Microsoft Windows 10 с NTFS: обновите атомарность = до 10.0.10240 включительно до 4096 байт, только если страница выровнена, в противном случае 512 байт, если FILE_FLAG_WRITE_THROUGH выключен, иначе 64 байта. Обратите внимание, что эта атомарность, вероятно, является особенностью PCIe DMA, а не разработкой. Начиная с 10.0.14393, по крайней мере 1 МБ, возможно, бесконечно (*).

Linux 4.2.6 с ext4: атомарность обновления = не менее 1 МБ, вероятно, бесконечность (*). Обратите внимание, что более ранние версии Linux с ext4 определенно не превышали 4096 байт, XFS, безусловно, имела настраиваемую блокировку, но похоже, что последняя версия Linux наконец-то исправила это.

FreeBSD 10.2 с ZFS: атомарность обновления = не менее 1 МБ, возможно, бесконечно (*)


Вы можете видеть необработанные эмпирические результаты испытаний на https://github.com/ned14/afio/tree/master/programs/fs-probe . Обратите внимание, что мы проверяем разорванные смещения только на 512-байтовых кратных, поэтому я не могу сказать, будет ли частичное обновление 512-байтового сектора разорвано во время цикла чтения-изменения-записи.

Итак, чтобы ответить на вопрос OP, записи O_APPEND не будут мешать друг другу, но при одновременном чтении с записями O_APPEND, вероятно, будут отображаться разорванные записи в Linux с ext4, если O_DIRECT не включен, после чего ваши записи O_APPEND должны быть кратны размеру сектора.


(*) «Вероятно, бесконечное» происходит из следующих пунктов спецификации POSIX:

Все следующие функции должны быть атомарными по отношению друг к другу в эффектах, указанных в POSIX.1-2008, когда они работают с обычными файлами или символическими ссылками ... [многие функции] ... read () ... write ( ) ... Если каждый из двух потоков вызывает одну из этих функций, каждый вызов должен либо видеть все указанные эффекты другого вызова, либо ни один из них. [Источник]

и

Записи могут быть сериализованы по отношению к другим операциям чтения и записи. Если можно доказать (любыми способами), что чтение () данных файла произошло после записи () данных, оно должно отражать эту запись (), даже если вызовы выполняются разными процессами. [Источник]

но наоборот:

В этом томе POSIX.1-2008 не указано поведение одновременной записи в файл из нескольких процессов. Приложения должны использовать некоторую форму управления параллелизмом. [Источник]

Вы можете узнать больше об их значении в этом ответе

Найл Дуглас
источник
29

Я написал сценарий для эмпирической проверки максимального размера атомарного присоединения. Сценарий, написанный на bash, порождает несколько рабочих процессов, которые все записывают сигнатуры рабочих в один и тот же файл. Затем он читает файл в поисках перекрывающихся или поврежденных подписей. Вы можете увидеть источник сценария в этом сообщении в блоге .

Фактический максимальный размер атомарного добавления зависит не только от ОС, но и от файловой системы.

В Linux + ext3 размер составляет 4096, а в Windows + NTFS - 1024. Дополнительные размеры см. В комментариях ниже.

Оз Соломон
источник
С какой файловой системой вы тестировали в Linux? Мне интересно, может быть, это основано на размерах блоков файловой системы.
Freiheit
@freiheit Мне кажется, в то время я тестировал его на ext3. Если вы запустите его на другой FS и получите другой результат, оставьте комментарий.
Оз Соломон
3
@OzSolomon, я использовал ваш скрипт в Debian 7.8, и мне удалось получить только атомарные записи размером до 1008 байт включительно (1024 - 16 байт накладных расходов?) Как на моем разделе ext4, так и на монтировании tmpfs. Все остальное каждый раз приводило к коррупции.
Эрик Прюитт
6
Ваш тест, похоже, предполагает, что echo $line >> $OUTPUT_FILEрезультатом будет один вызов writeнезависимо от размера $line.
Tomas
16

Вот что говорится в стандарте: http://www.opengroup.org/onlinepubs/009695399/functions/pwrite.html .

Если установлен O_APPENDфлаг флагов состояния файла, смещение файла должно устанавливаться на конец файла перед каждой записью, и между изменением смещения файла и операцией записи не должно происходить промежуточной операции модификации файла.

Бастьен Леонар
источник
20
«между» - но как насчет вмешательств во время записи, которые, как я понимаю, происходят после «между»? (То есть: <change_offset_action> ... "the_between_period" ... <write_action>) - я понимаю, что нет никаких гарантий по этому поводу?
akavel
@akavel согласился; нет никакой гарантии, что сама запись является атомарной. Но я запутался: исходя из гарантии, предоставленной в вашей цитате, кажется, мы можем сделать вывод, что многопоточное приложение, добавляющее один и тот же файл, не будет смешивать части разных записанных записей. Однако из экспериментов, о которых сообщает OzSolomon, мы видим, что даже это предположение нарушается. Зачем?
max
@Max извините, я боюсь , что я не понимаю ваш вопрос: во - первых, эксперимент OzSolomon является мульти- процесс , а не мульти- резьбовая (один процесс) приложение; во-вторых, я не понимаю, как вы делаете вывод, что «многопоточное приложение [...] не будет смешивать» - это именно то, что я не вижу гарантированной цитатой из Бастиена, как я упоминал в своем комментарии. Вы можете уточнить свой вопрос?
akavel
2
Хм, я не могу восстановить свою собственную логику в то время, когда я писал этот комментарий ... Да, если ваша интерпретация верна, тогда, конечно, разные записи могут быть смешаны. Но теперь, когда я перечитываю цитату Бастьена, я думаю, что это должно означать, что никто не может прерывать «во время записи» - иначе весь абзац в стандарте был бы бесполезен, не давая буквально никаких гарантий (даже если запись произойдет. в конце, так как кто-то еще может сместить смещение во время выполнения шага «записи».
max