Файл – логически связанная совокупность данных или программ, для размещения которой во внешней памяти выделяется именованная область.
Файл служит в качестве учетной единицей информации в операционных системах. Все действия с информацией в операционных системах осуществляются над файлами: это может быть ввод с клавиатуры, запись на диск, обработка данных, вывод на экран, печать, копирование и передача информации и пр.
Файл имеет имя и расширение. Имя файла придумывает пользователь. Расширение файла присваивается программой, при помощи которой этот файл создается. По расширению файла определяется формат данных в файле. Файлы могут содержать разнообразные виды и формы представления информации: числа, программы, тексты, рисунки, таблицы, чертежи и т.п. Особенности файлов определяются их форматом.
Открытый файл очень похож на содержащий текст или изображение документ, который можно найти на чьем-либо столе или в канцелярском шкафу. На компьютере файлы отображаются в виде значков, которые помогают легко определить тип файла.
Файлы на компьютере можно создавать, удалять, редактировать, просматривать, хранить, копировать, перемещать. Операционные системы осуществляют помощь пользователю при работе с файлами.
Операционные системы предоставляют графический интерфейс при выполнении команд работы с файлами. При открытии папки с файлами можно изменить представление значков файлов в окне, например, на более мелкие или крупные. Если необходимо видеть данные о файле (вес и дату последнего редактирования файла), то выбирается педставление файлов в виде таблицы, Для такого рода изменений используется кнопка
Готовые работы на аналогичную тему
на панели инструментов.
Для точной настройки размера значков файлов необходимо перетащить ползунок вверх или вниз и остановиться на наиболе удачном представлении значка файла.
Рисунок 2. Меню «Представления»
Существует возможность упорядочения файлов различными способами. Например, по дате или по алфавиту, возможно по тематике и расширению.
Поиск файлов
Для автоматизации поиска файлов существует Поле поиска, где можно вписать критерии поиска.
Поле поиска находится в верхней части каждого окна папки. В результате поиска отображаются файлы, соответствующие заданным условиям в строке поиска.
Поиск файлов можно осуществлять при помощи фильтра поиска (например, по типу файла).
Копирование и перемещение файлов
Для изменения места хранения файлов на компьютере можно использовать контексное меню или воспользоваться методом перетаскивания.
Рисунок 4. Контекстное меню
Для использования метода перетаскивания необходимо открыть две папки, в одной должен находиться исходный файл, а в другую будет происходить перетаскивание. Левой кнопкой мыши файл выделяется и «тащится» в другую.
При использовании метода перетаскивания иногда файл или папка копируются, а иногда перемещаются. Если перетаскивание происходит между папками одного жесткого диска, то файлы перемещаются. Таким образом файл останется только в одной папке, копия его не останется в папке из которой файл перетаскивался. Если перетаскивание происходит в папку сети или на съёмный носитель, то элемент файл копируется.
Создание и удаление файлов
Самый простой способ создать новый файл — воспользоваться установленной на компьютере программой. Например, можно создать текстовый документ в программе обработки текста Word Блокнот, либо создать рисунок в программе Paint.
Некоторые программы создают файл при запуске. Например, при запуске редактора WordPad открывается чистая страница. Она представляет собой пустой (и несохраненный) файл.
По умолчанию большинство программ сохраняет файлы в типовых папках, таких как «Мои документы» и «Мои рисунки», что облегчает поиск этих файлов в дальнейшем.
Ненужный файл можно удалить из компьютера для экономии места. Для удаления файла можно открыть содержащую его папку, выделить файл и нажать клавишу DELETE, либо воспользоваться контексным меню.
Удаляемый файл временно сохраняется в корзине – системной папки (файле). Корзину считают средством безопасности, позволяющей восстановить случайно удаленные файлы и папки.
Открытие существующего файла
Открыть любой файл можно двойным щелчком мыши. Файл обычно открывается в программе, которая использовалась для его создания или изменения. Для открытия файлов программа определяется по расширению файла или из списка программ открытия такого типа файлов. Для изменения программы открытия файлов требуется в контексном меню выбрать пункт Открыть с помощью.
Для удобства обращения информация в запоминающих устройствах хранится в виде файлов.
Файл – именованная область внешней памяти, выделенная для хранения массива данных. Данные, содержащиеся в файлах, имеют самый разнообразный характер: программы на алгоритмическом или машинном языке; исходные данные для работы программ или результаты выполнения программ; произвольные тексты; графические изображения и т. п.
Файловой системой называется функциональная часть операционной системы, обеспечивающая выполнение операций над файлами. Примерами файловых систем являются FAT (FAT – File Allocation Table, таблица размещения файлов), NTFS, UDF (используется на компакт-дисках).
Существуют три основные версии FAT: FAT12, FAT16 и FAT32. Они отличаются разрядностью записей в дисковой структуре, т.е. количеством бит, отведённых для хранения номера кластера. FAT12 применяется в основном для дискет (до 4 кбайт), FAT16 – для дисков малого объёма, FAT32 – для FLASH-накопителей большой емкости (до 32 Гбайт).
Рассмотрим структуру файловой системы на примере FAT32.
Файловая структура FAT32
Устройства внешней памяти в системе FAT32 имеют не байтовую, а блочную адресацию. Запись информации в устройство внешней памяти осуществляется блоками или секторами.
Сектор – минимальная адресуемая единица хранения информации на внешних запоминающих устройствах. Как правило, размер сектора фиксирован и составляет 512 байт. Для увеличения адресного пространства устройств внешней памяти сектора объединяют в группы, называемые кластерами.
Кластер – объединение нескольких секторов, которое может рассматриваться как самостоятельная единица, обладающая определёнными свойствами. Основным свойством кластера является его размер, измеряемый в количестве секторов или количестве байт.
Файловая система FAT32 имеет следующую структуру. Нумерация кластеров, используемых для записи файлов, ведется с 2. Как правило, кластер №2 используется корневым каталогом, а начиная с кластера №3 хранится массив данных. Сектора, используемые для хранения информации, представленной выше корневого каталога, в кластеры не объединяются. Минимальный размер файла, занимаемый на диске, соответствует 1 кластеру.
Загрузочный сектор начинается следующей информацией:
Кроме того, загрузочный сектор содержит следующую важную информацию:
Сектор информации файловой системы содержит:
Таблица FAT содержит информацию о состоянии каждого кластера на диске. Младшие 2 байт таблицы FAT хранят F8 FF FF 0F FF FF FF FF (что соответствует состоянию кластеров 0 и 1, физически отсутствующих). Далее состояние каждого кластера содержит номер кластера, в котором продолжается текущий файл или следующую информацию:
Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:
Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:
В случае работы с длинными именами файлов (включая русские имена) кодировка имени файла производится в системе кодировки UTF-16. При этого для кодирования каждого символа отводится 2 байта. При этом имя файла записывается в виде следующей структуры:
Далее следует запись, включающая имя файла в формате 8.3 в обычном формате.
Работа с файлами в языке Си
Чтение символа из файла:
Запись символа в файл:
Функции fgets() и fputs() предназначены для ввода-вывода строк, они являются аналогами функций gets() и puts() для работы с файлами.
Результат выполнения — 2 файла Работа с файлами в C++ описана здесь.
Имя файла. Имя файла состоит из двух частей, разделенных точкой: собственно имя файла и расширение, определяющее его тип (программа, данные и так далее). Собственно имя файлу дает пользователь, а тип файла обычно задается программой автоматически при его создании.
В различных операционных системах существуют различные форматы имен файлов. В операционной системе MS-DOS собственно имя файла должно содержать не более 8 букв латинского алфавита, цифр и некоторых специальных знаков, а расширение состоит из трех латинских букв, например: proba.txt
В операционной системе Windows имя файла может иметь длину до 255 символов, причем можно использовать русский алфавит, например: Единицы измерения информации.doc
Таблица 1.1. Типы файлов и расширений
Программы на языках программирования
Таблица 1.2. Одноуровневый каталог
Имя файла
Номер начального сектора
Файл_1
56
Файл_2
89
Файл_112
1200
Начальный, корневой каталог содержит вложенные каталоги 1-го уровня, в свою очередь, каждый из последних может содержать вложенные каталоги 2-го уровня и так далее. Необходимо отметить, что в каталогах всех уровней могут храниться и файлы.
Файловая система— это система хранения файлов и организации каталогов.
Рис. 1.3. Иерархическая файловая система
Рис. 1.4. Пример иерархической файловой системы
Путь к файлу вместе с именем файла называют иногда полным именем файла.
Пример полного имени файла:
Однако иерархическая структура этих систем несколько различается. В иерархической файловой системе MS-DOS вершиной иерархии объектов является корневой каталог диска, который можно сравнить со стволом дерева, на котором растут ветки (подкаталоги), а на ветках располагаются листья (файлы).
Рис. 1.5. Иерархическая структура папок
Если мы хотим ознакомиться с ресурсами компьютера, необходимо открыть папку Мой компьютер.
1. В окне Мой компьютер находятся значки имеющихся в компьютере дисков. Активизация (щелчок) значка любого диска выводит в левой части окна информацию о его емкости, занятой и свободной частях.
Я уже много лет не пользовался десктопным клиентом электронной почты. Ни один из них не может справиться с объёмом получаемой мной почты, по крайней мере один раз не повредив мой почтовый ящик. Pine, Eudora, Outlook — все они повреждали мой почтовый ящик, вынуждая восстанавливаться из резервной копии. Как получилось, что десктопные почтовые клиенты менее надёжны, чем Gmail, хотя мой аккаунт в Gmail не только обрабатывает больше писем, чем у меня когда-либо было в десктопных клиентах, но и обеспечивает одновременный доступ из множества точек мира? Распределённые системы имеют нечестное преимущество — они, в отличие от десктопных клиентов, устойчивы к полному отказу диска, однако ни одна из моих проблем повреждения файлов не была связана с полным отказом диска. Почему же мой опыт работы с десктопными приложениями был настолько плохим?
Какие же виды сбоев могут возникать? Вероятно, проще всего начать со свойства постоянства при сбоях (поддержания постоянного состояния даже в случае сбоя), потому что мы можем допустить, что всё, от файловой системы до диска, работает правильно; давайте сначала рассмотрим это свойство.
Постоянство при сбоях
Pillai et al. написали для OSDI ’14 статью и презентацию о том, как сложно сохранять данные без повреждения и утери данных. [Прим. пер.: пост был написан в конце 2015 года.]
Мы можем исправить это, добавив при создании журнала к нему контрольную сумму. Если содержимое log не включает в себя правильную контрольную сумму, то мы будем знать, что попали в описанную выше ситуацию.
Это позволит обеспечить безопасность, по крайней мере, в текущий конфигурациях ext3. Но файловая система вполне может оказаться в состоянии, когда журнал никогда не создаётся, за исключением случаев, когда мы передаём fsync родительской папке.
Это должно предотвратить повреждение любой файловой системы Linux, но если мы хотим гарантировать, что файл на самом деле содержит «bar», нам нужно добавить в конце ещё один fsync.
Даже если мы предположим, что fsync даёт команду сброса на диск, некоторые диски игнорируют директивы сброса по той же причине, по которой fsync модифицирован в OS X и в некоторых версиях ext3 — чтобы выглядеть лучше в бенчмарках. В своём посте я не буду это учитывать, но есть статья Rajimwale et al. для DSN ’11 и другие работы, в которых рассматривается эта проблема.
Семантика файловых систем
После изучения ext2, ext3, ext4, btrfs и xfs авторы статьи выяснили, что есть существенные отличия в написании кода для обеспечения постоянства. Они написали инструмент, собирающий трассировки файловых систем на уровне блоков, и использовали его, чтобы выяснить, какие свойства не учитываются в конкретных файловых системах. Авторы осторожно замечают, что они могли определить, только отсутствие поддержки свойств — если они не обнаружили нарушения свойства, это ещё не значит, что оно поддерживается.
После создания инструмента тестирования свойств файловых систем они создали инструмент для проверки того, используют ли приложения любые потенциально некорректные свойства файловых систем. Так как инварианты зависят от конкретных приложений, авторы составили таблицы для каждого протестированного приложения.
Авторы выявили проблемы у большинства протестированных приложений, в том числе и у таких, на работу которых пользователи надеются, например, LevelDB, HDFS, Zookeeper и git. В докладе один из авторов заметил, что разработчики sqlite имеют очень глубокое понимание этих проблем, но даже этого не хватило для предотвращения всех багов. Этот докладчик также заметил, что особенно плохи были системы контроля версий, и что разработчики довольно небрежны, поэтому авторам очень легко было найти множество проблем в их инструментах. Самым распространённым классом ошибок было некорректное допущение упорядочивания между системными вызовами. Вторым по распространённости классом ошибок было допущение атомарности системных вызовов. [Как известные проблемы задокументированы случаи, когда требуется атомарность перезаписи, и во всех этих случаях предполагается одноблоковая, а не многоблоковая атомарность. Для сравнения: во многих приложениях (LevelDB, Mercurial и HSQLDB) есть серьёзные баги повреждения данных, возникающие из-за того, что атомарным считается дополнение записи (append). Похоже, это косвенный результат использования протокола обновления, при котором модификации журналируются через append, после чего журналированные данные записываются через перезапись. Разработчики приложений тщательно проверяют и обрабатывают ошибки данных, однако ошибки в файле журнала часто остаются незамеченными. В статье обсуждается множество других классов ошибок, и если вы работаете над приложением, которое записывает файлы, я рекомендую прочитать её, чтобы узнать подробности.] По сути, с теми же проблемами люди сталкиваются при реализации многопоточного программирования. Правильно рассуждать о поведении изменения порядка и правильно вставлять ограничения сложно. Но даже несмотря на то, что параллельность общей памяти считается сложной проблемой, требующей большого внимания, запись в файлы не воспринимается аналогичным образом, хотя во многих смыслах она намного сложнее.
Тут также стоит заметить, что хотя семантика btrfs не менее надёжна, чем у ext3/ext4, гораздо больше приложений повреждает данные поверх btrfs, потому что разработчики не привыкли к кодированию для файловых систем, позволяющих переупорядочивать операции с каталогами (наверно, самой свежей и популярной файловой системой, позволяющей такое переупорядочивание, является ext2). Вероятно, мы увидим схожий уровень подверженности багам, когда люди начнут пользоваться накопителями NVRAM, имеющими атомарность на байтовом уровне. Люди почти всегда проводят только несколько тестов, чтобы убедиться, что всё работает, а не проверяют, пишут ли они код для того, что допустимо в файловой системе POSIX.
Семантика упорядочивания аппаратной памяти обычно хорошо задокументирована, что упрощает точное определение того, какие операции могут быть переупорядочены с какими другими операциями, и какие операции являются атомарными. Для сравнения, вот manpage ext про эти три режима данных:
journal: все данные коммитятся в журнал, прежде чем быть записанными в основную файловую систему.
ordered: режим по умолчанию. Все данные принудительно передаются в основную файловую систему до того, как метаданные коммитятся в журнал.
writeback: порядок данных не сохраняется — данные могут записываться в основную файловую систему после того, как их метаданные закоммичены в журнал. По слухам, что этот вариант обеспечивает наибольшую пропускную способность. Он гарантирует внутреннюю целостность файловой системы, однако может привести к тому, что после сбоя и восстановления журнала в файлах будут появляться старые данные.
Страница manpage в буквальном смысле ссылается на слухи. Вот такой у нас уровень документации. Если вернуться к примеру, где нам пришлось добавить fsync между write(/dir/log, “2, 3, foo”) и pwrite(/dir/orig, 2, “bar”) для предотвращения изменения порядка, то я не думаю, что необходимость fsync очевидна из описания на manpage. Если посмотреть на представленную выше manpage об упорядочивании аппаратной памяти, то она конкретно определяет семантику упорядочивания, и совершенно не полагается на слухи.
Не хочу сказать, что семантика файловых систем нигде хорошо не задокументирована. Изучая lwn и LKML, можно получить хорошее представление о том, как всё работает. Но разбираться во всём этом достаточно сложно, поэтому по-прежнему очень популярны долгие неопределённые обсуждения того, как это работает. Большая часть информации там ошибочна, и даже если информация была правильна на момент публикации, часто она является устаревшей.
Просматривая архивы, я часто встречал ссылки пост 2005 года как подтверждение того, что fsync в OS X такая же, как и fsync в Linux, и что fcntl(F_FULLFSYNC) в OS X более безопасна, чем всё, что доступно в Linux. Я не уверен, что это было справедливо даже в те времена для ядра 2.4, однако это было правдой для ядра 2.6. Но примерно с 2008 года Linux 2.6 с ext3 выполнял полный сброс на диск при каждом fsync (если это поддерживает диск, а файловая система не была специально настроена с отключением барьеров).
Ещё одна проблема заключается в том, что часто встречаются подобные диалоги:
Разработчик 1: лично мне важна целостность метаданных, а в документации ext3 написано, что журнал защищает их целостность. Исключением являются повреждённые устройства хранения, и для них всё равно нужно запускать fsck.
Разработчик 2: как заявляли авторы ext3 в течение многих лет, всё равно нужно периодически запускать fsck.
Разработчик 1: где это задокументировано?
Разработчик 2: в архивах списка рассылки linux-kernel.
Разработчик 3: примерно 6-8 лет назад, в моей почтовой рассылке.
Где это задокументировано? А, в каком-то посте списке рассылки, созданном 6-8 лет назад (то есть сегодня это уже 12-14 лет назад). Я не хочу обижать разработчиков файловых систем. Прочитанные мной посты разработчиков ФС довольно вежливы, учитывая репутацию LKML; они уделяли много времени ответам на простые вопросы, и я впечатлён тем, насколько терпеливы опытные разработчики файловых систем с задающими вопросы, однако непосвящённым сложно просмотреть посты в списках рассылки за полтора десятка лет и определить, что по-прежнему справедливо, а что устарело.
В своём докладе на OSDI 2014 авторы обсуждаемой нами статьи говорили, что когда они сообщали о найденных багах, разработчики часто отвечали «POSIX не позволяет файловым системам этого делать», но не могли указать на конкретную документацию по POSIX в подтверждение своих слов. Если вы следили за Jepsen Кайла Кингсбери, то это может показаться вам знакомым, только разработчики отвечают не «сети этого не могут», а «файловые системы этого не могут». Мне кажется, это вполне понятно, учитывая количество ложных сведений. Сам я не разработчик файловых систем, поэтому был бы удивлён, если бы в этом посте не нашёлся хотя бы один баг.
Корректность файловых систем
Мы уже столкнулись с высоким уровнем сложности правильного сохранения данных, но это была только вершина айсберга. Ранее мы предполагали, что диск работает правильно, или, по крайней мере, что файловая система при помощи SMART или какой-то другой системы мониторинга может определить, когда на диске есть ошибки. Я всегда считал, что это так, пока не начал исследовать вопрос, и это предположение оказалось совершенно неверным.
Среди ext3, reiserfs и NTFS лучшей в обработке ошибок оказалась reiserfs; к тому же, похоже, это была единственная файловая система, в которой на этапе проектирования ошибки считались «гражданами первого класса». Она практически всегда сообщала пользователю об ошибках при чтении и вызывала panic при сбоях записи, что приводило к перезапуску и восстановлению. Эта политика позволяет файловой системе элегантно обрабатывать сбои чтения и избегать повреждения данных при сбоях записи. Однако авторы нашли множество несогласованностей и багов. Например, reiserfs некорректно обрабатывает ошибки чтения в непрямых блоках и создаёт утечку места, а один определённый тип сбоя записи не мешает reiserfs обновить журнал и выполнить коммит транзакции, что может привести к повреждению данных.
Reiserfs — это ещё хороший пример. Авторы выяснили, что ext3 в большинстве случаев игнорировала ошибки записи и представляла файловую систему как read-only в большинстве случаев сбоев чтения. Это совершенно непохоже на ту политику, которая нам нужна. Игнорирование сбоев записи легко может привести к повреждению данных, а перемонтирование файловой системы как read-only — слишком сильная реакция, если ошибка чтения была переходной ошибкой (переходные ошибки встречаются часто). Кроме того, из всех трёх файловых систем ext3 обеспечивала наименьшую проверку целостности и с наибольшей вероятностью могла не распознать ошибку. На презентации один из авторов сказал, что в коде ext3 есть множество комментариев наподобие «Сильно надеюсь, что ошибка записи не произойдёт» в тех местах, где ошибки не обрабатываются.
NTFS находится где-то между ext3 и reiserfs. Авторы выяснили, что в ней есть множество встроенных проверок целостности, и она достаточно хорошо сообщает об ошибках пользователю. Однако, как и ext3, она игнорирует сбои записи.
В статье гораздо больше подробностей о конкретных режимах сбоев, однако в основном они представляют исторический интерес, так как многие баги были устранены.
Было бы здорово увидеть дополненную версию статьи, и на одной из презентаций кто-то из аудитории спросил, есть ли более актуальная информация. Выступающий ответил, что им было интересно узнать, как ситуация выглядит сейчас, но подобную работу сложно выполнить в научной среде, потому что аспиранты не хотят повторять уже сделанную ранее работу, что вполне логично, учитывая мотивацию, с которой они сталкиваются. Для повторения необходимо вложить много труда, почти столько же, сколько и для исходного исследования, но повторения работ практически не привлекают научного внимания. Это один из множества случаев, когда уровень мотивации для исследований очень плохо соотносится с влиянием этих исследований на реальную жизнь.
Здорово было бы воспроизвести сегодня ещё одну работу — Gunawi et al. с FAST 08. Эта статья развивает описанную выше, исследуя код обработки ошибок в различных файловых системах при помощи простого инструмента статического анализа, находящего случаи, когда ошибки игнорируются. «Игнорирование» в статье определяется достаточно вольно. Например, следующий код:
не считается игнорированием ошибки. Ошибки считаются игнорируемыми, если поток исполнения программы не зависит от кода ошибки, возвращённого функцией.
При помощи этого инструмента исследователи выяснили, что большинство файловых систем игнорирует множество кодов ошибок:
По % сломанных
По наруш./тыс. строк
Место
ФС
Доля
ФС
наруш./тыс. строк
1
IBM JFS
24,4
ext3
7,2
2
ext3
22,1
IBM JFS
5,6
3
JFFS v2
15,7
NFS Client
3,6
4
NFS Client
12,9
VFS
2,9
5
CIFS
12,7
JFFS v2
2,2
6
MemMgmt
11,4
CIFS
2,1
7
ReiserFS
10,5
MemMgmt
2,0
8
VFS
8,4
ReiserFS
1,8
9
NTFS
8,1
XFS
1,4
10
XFS
6,9
NFS Server
1,2
Рядом с проигнорированными ошибками они нашли подобные комментарии: «Надо ли возвращать какую-нибудь ошибку?», «Ошибка, пропустить блок и надеяться на лучшее», «Нет способа сообщения об ошибке, возвращённой от ext3_mark_inode_dirty() в пространство пользователя. Поэтому игнорируем её», «Примечание: todo: обработчик ошибок лога», «Здесь мы ничего не можем поделать с ошибкой», «На этом этапе просто игнорируем ошибку. Мы ничего не можем поделать, кроме как продолжать работу», «Retval игнорируется» и «Todo: обработать сбой».
Стоит заметить, что во многих случаях игнорирование ошибки — это симптом проблемы архитектуры, а не баг сам по себе (например, ext3 игнорировала ошибки записи при создании контрольных точек, потому что в ней нет никакого механизма восстановления). Но даже при этом авторы статьи обнаружили множество реальных багов.
Восстановление после ошибок
Любая широко используемая файловая система содержит баги, вызывающие проблемы при состояниях ошибок, что приводит нас к двум вопросам. Могут ли инструменты восстановления надёжно устранять ошибки, и как часто происходят ошибки? Как они выполняют восстановление после этих проблем? Эти вопросы рассматриваются в статье Gunawi et al. для OSDI 08. Выяснилось, что стандартная утилита для проверки и восстановления файловых систем fsck «проверяет и чинит определённые указатели в неверном порядке… иногда файловую систему после этого даже невозможно будет смонтировать».
Мы уже знаем, что довольно сложно записывать файлы таким образом, чтобы обеспечить надёжность даже если лежащая в их основе файловая система безошибочна, что файловая система будет иметь баги, и что попытки починки повреждений файловой системы может ещё сильнее повредить или даже уничтожить её. Как часто происходят ошибки?
Частота возникновения ошибок
В статье Bairavasundaram et al. для SIGMETRICS ’07 исследователи выяснили, что от 5% до 20% дисков за двухлетний период будет иметь хотя бы одну ошибку. Любопытно, что многие из них являются изолированными ошибками — 38% дисков с ошибками имеют только одну ошибку, а 80% — меньше 50 ошибок. Дальнейшее исследование повреждений выявило, что незаметное повреждение данных, обнаруживаемое только при проверке контрольных сумм, происходит ежегодно с 0,5% дисков, а одна чрезвычайно плохая модель показала повреждения в 4% дисков ежегодно.
Также стоит заметить, что исследователи обнаружили высокую локальность частотности ошибок дисков в некоторых моделях дисков. Например, была одна модель диска, имевшая высокую частоту ошибок в одном конкретном секторе, из-за чего многие виды RAID оказывались почти бесполезными для избыточного хранения.
Это исследование тоже стоит воспроизвести. Большинство исследований дисков фокусируются на частоте сбоев диска целиком, но если вас волнует повреждение данных, то ошибки на не отказавших дисках серьёзнее, чем поломка диска, которую гораздо проще обнаружить.
Заключение
Файлы — это сложно. Батлер Лэмпсон писал, что когда они придумали в PARC потоки, блокировки и переменные условий, они считали, что создают модель программирования, которую может использовать каждый, но за десятки лет накопилось много доказательств того, что они ошибались. Мы накопили много свидетельств того, что люди плохо могут рассуждать о подобных проблемах, которые очень похожи на проблемы, возникающие при написании корректного кода для взаимодействия с современными файловыми системами. Лэмпсон предполагает, что наилучшим из известных решением было бы упаковать весь параллелизм в как можно меньший ящик, чтобы потом функция-мастер писала код в этом ящике. Если перенести это на файловые системы, то это как сказать разработчику приложения, что безопасная запись файлов настолько сложна, что её нужно реализовать через какую-то библиотеку и/или базу данных, а не прямыми системными вызовами.
Если вам нужно хорошее стандартное решение, то с точки зрения надёжности подойдёт Sqlite. Однако некоторые люди считают её слишком тяжёлой, если им нужно только абстрагирование на уровне файлов. На самом деле им нужно что-то типа polyfill для абстрагирования файлов, который работает поверх всех файловых систем без необходимости понимания разницы между различными конфигурациями (и даже разными версиями) каждой файловой системы. Так как такой библиотеки ещё не существует, если недостаточно уже имеющихся библиотек, то вам нужно будет проверять контрольную сумму данных, потому что будут возникать незамеченные ошибки и повреждения. Единственный вопрос заключается в том, будете ли вы распознавать ошибки и уничтожает ли ваш формат записи только одну запись при повреждении, или уничтожает всю базу данных. Насколько я могу судить, большинство разработчиков десктопных клиентов электронной почты выбрали путь уничтожения всей электронной почты при возникновении повреждения.
Эти исследования также доносят до сознания то, что традиционного тестирования недостаточно. Во многих случаях авторы статьи писали относительно простой инструмент и находили огромное количество ошибок. Для написания инструментов не требуется никакой глубокой магии computer science. Инструмент проверки передачи сообщений об ошибках из статьи, нашедший кучу багов в обработке ошибок файловыми системами, имел размер всего 4 тысячи LOC. Если прочитать статью, то становится видно, что авторы заметили большое количество недостатков инструмента, вызванных его простотой, но несмотря на эти недостатки, он смог найти множество реальных багов. Для своей последней работы я писал очень приблизительно похожий инструмент для тестирования инвариантов, и он в буквальном смысле занимал две страницы кода. В нём даже не было настоящего парсера (он просто построчно проходил по файлам и находил простые ошибки, которые легко обнаружить при помощи стейт-машины и регулярных выражений), но он нашёл достаточное количество багов, что оправдал время, потраченное на его разработку, после первого же запуска.
Почти в каждом встреченном мной программном проекте есть очень много простых в тестировании аспектов. Даже очень простые случайное тестирование, статический анализ и внесение ошибок могут оправдать потраченное на них время практически после первого их запуска.
Приложение
Наверно, я рассказал меньше чем о 20% представленных в упомянутых в статье материалах. Расскажу ещё немного о другой интересной информации, которую можно найти в этих и других статьях.
Pillai et al., OSDI ’14: в этой статье гораздо подробнее рассказывается о том, что необходимо для повторяемости сбоев, чем в моём посте. Также в ней достаточно подробно рассказано о том, как происходят сбои приложений, в том числе диаграммы трассировок, показывающие, какие ложные допущения были внедрены при каждой трассировке.
Chidambara et al., FAST ’12: одни и те же примитивы файловых систем отвечают и за постоянство, и за упорядочивание. Авторы предлагают альтернативные примитивы, разделяющие эти аспекты и позволяющие повысить производительность с сохранением безопасности.
Rajimwale et al. DSN ’01: наверно, не стоит использовать диски, игнорирующие директивы сброса на диск, но если вы ими пользуетесь, то вот протокол, заставляющий эти диски сбрасывать данные на диск при помощи обычных операций файловых систем. Как и можно ожидать, производительность его весьма низка.
Prabhakaran et al. SOSP ’05: это гораздо более подробное описание ответов файловых систем на ошибки, чем это изложено в моём посте. Авторы также рассматривают JFS — файловую систему IBM для AIX. Хотя она была спроектирована для систем с высокой надёжностью,, на самом деле она не особо надёжнее альтернатив. Дополнительная информация рассмотрена, среди прочих источников, в DSN ’08, StorageSS ’06, DSN ’06, FAST ’08, and USENIX ’09.
Gunawi et al. FAST ’08: снова гораздо более подробная статья о ситуациях игнорирования ошибок и о том, как авторы писали свои инструменты. Также в статье есть графы вызовов, дающие приблизительное представление об уровне сложности, связанном с работой файловых систем. Особенно хаотичен граф вызовов XFS: один из авторов рассказал на презентации, что разработчик XFS сообщил ему, что с XFS было интересно работать, потому что в ней использовались любые возможные оптимизации вне зависимости от того, насколько хаотичным становился код.
Bairavasundaram et al. SIGMETRICS ’07: множество информации о локальности дисковых ошибок и их вероятности с течением времени, не раскрытой в моём посте. В последующей статье для FAST08 ещё больше подробностей.
Gunawi et al. OSDI ’08: в этой статье гораздо больше подробностей о том, когда не работает fsck. В презентации один из авторов говорит, что fsck — единственная программа, оскорбившая его. Похоже, что если у вас есть повреждённый указатель на суперблок, fsck уничтожает суперблок (что может привести к немонтируемости диска) и сообщает что-то вроде: «глупый, запускать fsck нужно для смонтированного диска», а потом завершает работу. В статье авторы, по сути, заново реализовали весь fsck на основе декларативной модели, и выяснили, что декларативная версия короче, проще для понимания и гораздо легче расширяема, но ценой небольшого снижения скорости.
Ошибки памяти выходят за рамки этого поста, но повреждение памяти может приводить к повреждению диска. Это особенно раздражает, потому что повреждение памяти может привести к тому, что вы получите контрольную сумму плохих данных и запишете плохую контрольную сумму. Также могут повреждаться указатели памяти, что часто приводит к очень плохим результатам. Подробнее о том, как этому подвержена ZFS, можно прочитать в Zhang et al. FAST ’10 paper. Существует мем, что ZFS безопасна с точки зрения повреждения памяти, потому что проверяет контрольные суммы, но авторы статьи выяснили, что критически важные вещи, хранящиеся в памяти, не подвергаются проверке контрольных сумм, и в реальности ошибки памяти могут привести к повреждению данных.
Разработчики sqlite серьёзно относятся к документации и тестированию. Если бы я хотел написать надёжное десктопное приложение, то начал бы с чтения документации sqlite, а затем поговорил бы с разработчиками ядра. Если бы я хотел написать надёжное распределённое приложение, то начал бы с трудоустройства в Google, а затем прочитал бы документацию по проекту и постмортемы GFS, Colossus, Spanner и т.д. Ладно, я просто шучу.
Мы совершенно не рассматривали формальные методы, но существует множество попыток формальной верификации свойств файловых систем, например, SibylFS.
Этот список неполон, это просто перечень статей, которые показались мне интересными.
Дополнение: многие люди прочитали пост и предложили в первом примере с файлами использовать гораздо более простой протокол копирования файла, который нужно изменить, во временный файл, затем изменить временный файл, а затем переименовать его, чтобы временный файл переписал исходный. На самом деле, это, пожалуй, самый популярный комментарий к моему посту. Если вы считаете, что это решит проблему, то я попрошу вас остановиться на пять секунд и подумать о проблемах этой методики.
Похоже, это косвенный результат использования протокола обновления, при котором модификации журналируются через append, после чего журналированные данные записываются через перезапись. Разработчики приложений тщательно проверяют и обрабатывают ошибки данных, однако ошибки в файле журнала часто остаются незамеченными.
В статье обсуждается множество других классов ошибок, и если вы работаете над приложением, которое записывает файлы, я рекомендую прочитать её, чтобы узнать подробности.