Введение в файловые системы UNIX


Dru Lavigne
перевод Станислава Лапшанского
Впервые опубликован: http://www.computerra.ru.

21 июня 2005 г

Оригинал статьи находится по адресу: Understanding Unix Filesystems.

В первой статье цикла мы рассматривали содержимое BIOS'овской и UNIX'овой таблиц разделов при помощи, соответственно, утилит fdisk и disklabel. В этой статье мы продолжим разговор и рассмотрим использование утилиты newfs, а так же поговорим об индексных таблицах.

Программа newfs занимается форматированием вашего слайса в файловую систему, которую вы указали перед этим утилитой disklabel. Давайте начнем с того, что разберемся, что из себя представляют форматирование и файловые системы вообще, для того что бы впоследствии лучше представлять, что делает утилита newfs.

Существуют два способа форматирования жестких дисков. Когда вы приобретаете новый жесткий диск, то, в большинстве случаев, он поставляется уже отформатированным на низком уровне (low level format). При помощи низкоуровневого форматирования на диске создаются треки и сектора, в паре они создают пространство адресуемых физических блоков по 512 байт.

Вторым способом форматирования, как нетрудно догадаться, является высокоуровневое (high level) форматирование. Во время такого форматирования при помощи таких утилит как format (в системе DOS) или newfs (в системе FreeBSD), на разделе жесткого диска как раз и создается определенная файловая система. Примерами файловых систем могут служить FAT16, FAT32, NTFS и FFS. Разные файловые системы могут отличаться по целому ряду показателей - например по производительности, однако, как минимум две функции совпадают у всех файловых систем: Каждая файловая система имеет таблицу для отображения файлов с данными пользователя на физические блоки винчестера. Файловая система может использовать свою "логическую" адресацию блоков, для того что бы пытаться оптимизировать производительность запросов ввода-вывода.

Давайте представим, что вы являетесь файловой системой. Ваша цель - быстро сохранять (записать) и находить (читать) данные. На вас действуют следующие ограничения:
Вы имеете доступ к площади, называемой цилиндром.
Ваш цилиндр имеет 255 горизонтальных строк (треки) и 63 вертикальных столбцов, которые называются секторами.
На пересечении этих строк находятся блоки, в которых вы можете сохранять по 512 байт данных.

Итак, подсчитаем, какое количество блоков содержится на цилиндре:
255 * 63 = 16,065

Если вы поместите по одному файлу в каждый блок, то вы сможете сохранить 16065 файлов. Если вы нарисуете для себя табличку и пронумеруете ее строки от 1 до 16065, то тогда вы просто сможете записывать имя каждого нового файла рядом с любым свободным номером, получается так, как будто вы записываете файл в блок с этим номером. Если вы удаляете файл, не забудьте стереть его имя из вашей таблицы. Когда вы захотите переместить какой-нибудь файл, найдите его в таблице, сотрите его, а затем запишите его имя рядом с новым номером блока. Вы, должно быть, быстро сообразите, что можно не обременять себя проблемами, связанными с физическим перемещением файла из одного блока в другой, поскольку значительно проще изменить запись о файле в вашей таблице.

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

К сожалению, простота приводит к крайне нерациональному использованию дискового пространства. Дело в том, что запись файлов в 512 байтные блоки приводит к отличным результатам, когда все файлы имеют размеры 512 байт. Однако жизненный опыт подсказывает нам, что файлы имеют самые разнообразные размеры, от нескольких байт до сотен мегабайт.

Поставьте себя на место файловой системы и подумайте, как вы запишете файл размером 10 байт. Если просто поместить этот файл в блок жесткого диска, то вы потеряете 412 байт полезного пространства. Сохранив большое количество маленьких файлов, вы отправите в мусор целую кучу свободного места. Что произойдет, если вы запишете на диск 16065 файлов размером 1 байт? Вы используете все имеющиеся у вас блоки жесткого диска, записав всего 16065 байт полезных данных. Несмотря на несколькомегабайтный размер цилиндра на вашем жестком диске, вы не сможете найти ни одного свободного блока для ваших файлов. Это так называемая ситуация отсутствия индексных элементов (или отсутствия места в индексной таблице, что тоже самое). Очевидно, что возникновение такой ситуации в реальных условиях крайне нежелательно.

Продолжая думать как файловая система, можно придти к выводу, что имеет смысл не отдавать целый блок для одного маленького файла. Однако, сейчас самое время подумать, каким образом вы организуете вашу таблицу в условиях того, что в одном блоке может быть более одного файла. Если вы просто начнете набивать в блок столько файлов, сколько можно разместить в 512 байтах, то каким образом вы будете отслеживать, где заканчивается один файл и начинается другой? Что будет, если вы удалите файл размером 10 байт, а на его место запишите файл размером 8 байт и куда вы запишите информацию об оставшихся 2 байтах в случае, если вы захотите записать туда два однобайтовых файла? Сразу видно, что подобная схема быстро теряет работоспособность.

Большинство файловых систем пользуются концепцией фрагментации. Фрагмент - это логическая часть блока. Каждый фрагмент может быть адресован, т.е. о нем может быть сделана запись в индексной таблице. Например, файловая система может разбивать каждый блок на четыре фрагмента. Таким образом, количество доступных блоков умножается на четыре, одновременно уменьшая их размер в четыре раза. Например если изначально было доступно 16065 блоков, по 512 байт в каждом, то после такой фрагментации их количество возрастет до 64260, при размере блока 128 байт. Каждый фрагмент может интерпретироваться как блок для хранения данных и, таким образом, может содержать один файл, следовательно, теперь в индексной таблице 64260 элементов и при этом нет необходимости беспокоиться о том, как отслеживать несколько файлов в одном логическом блоке.

Давайте посмотрим, что у нас находится на другой чаше весов. Что произойдет, если вам понадобится записывать файлы, размер которых больше, чем размер физического или логического блока данных? Для сохранения на диске такого файла, вам, разумеется, придется задействовать более одного блока данных. Предположим, что вы хотите сохранить файл объемом 1000 байт. Для этого потребуются два физических блока по 512 байт, и, соответственно, в вашей индексной таблице будут две записи. Вам также придется подумать над тем, как вносить туда эти записи, поскольку теперь важен порядок их внесения. В данной ситуации недостаточно просто знать, что файл находится, скажем, в блоках 3 и 4. Вам обязательно надо быть уверенными, что первые 512 байт файла находятся именно в блоке номер три, а остальное в четвертом блоке.

До этой точки мы рассуждали только о записи данных. Другим важным параметром файловой системы является производительность операций чтения. Конечной целью записи файлов на диск в первую очередь является обеспечение возможности чтения пользователем необходимого ему в данный момент файла. Для того, чтобы пользователь мог прочитать файл, файловая система должна узнать, какой блок или блоки содержат указанный файл, а после этого скопировать их содержимое в оперативную память, для того, чтобы пользователь мог производить с ними необходимые ему операции.

Вот несколько вещей, которые могут увеличить быстродействие операций чтения. Во-первых, это увеличение логического блока данных таким образом, что бы он включал в себя сразу несколько физических блоков. Когда пользователь захочет обратиться к файлу, который содержится в физическом блоке, в оперативную память загрузится целый логический блок. Таким образом, экономится большое количество обращений к диску за отдельными физическими блоками. Если несколько блоков будет загружено в оперативную память, то весьма вероятно, что все данные, которые запрашивал пользователь, будут переданы за один раз.

Давайте суммируем все то, о чем мы говорили, обсуждая файловую систему:
Она представляет собой конечное количество блоков для хранения данных, которые связаны с адресуемым списком, который в файловых системах для операционных систем UNIX имеет название "индексная таблица".
Если вы исчерпываете число элементов этой таблицы и, тем самым, число доступных блоков данных, то вы не можете создавать новые файлы, несмотря на то, сколько свободного места на вашем диске.
Если вы предполагаете сохранение большого количества маленьких файлов, вам необходимо фрагментировать блоки данных, для того чтобы увеличить размер индексной таблицы.
Однако, если вы хотите увеличить производительность операции чтения, вам необходимо увеличить размер логического блока данных, для того, чтобы за одну операцию чтения в память считывалось больше данных.

Теперь давайте поглядим, как все вышеизложенное применимо к FreeBSD. Когда вы пользуйтесь утилитой newfs, вы форматируете ваш слайс в файловую систему FFS (Berkeley Fast File System - быстрая файловая система Беркли). Давайте посмотрим кусочки из страницы руководства по утилите newfs, для того, чтобы узнать, какие установки "по умолчанию" предусмотрены для этой файловой системы:
man 8 newfs

Нас интересуют только некоторые ключи. Начнем с ключа "-b":
-b размер_блока

Указывает размер блока в файловой системе в байтах. Он должен быть степенью числа 2. По умолчанию размер 8192 байта. Наименьший возможный размер 4096 байтов.

Обратите внимание, что это размер блока файловой системы. Физический блок всегда имеет размер 512 байтов. 8192 байта - это на самом деле 16 физических блоков. Таким образом, этот параметр задает размер логического блока, т.е. какой объем данных будет считан в оперативную память за один раз. Помните, что этот параметр создан для увеличения скорости чтения и является одной из причин, почему в названии файловой системы FreeBSD присутствует слово "быстрый".

Ключ "-f":
-f размер_фрагмента

Указывает размер фрагмента файловой системы в байтах. Значение должно лежать в пределах от размер_блока/8 до размер_блока и быть степенью числа 2. По умолчанию используется размер 1024 байта.

FFS использует фрагментирование, однако фрагментируется логический, а не физический блок. По умолчанию размер фрагмента равен двум физическим блокам.

Теперь рассмотрим ключ "-i":
-i количество_байт_на_элемент_индексной_таблицы

Указывает плотность индексных записей в файловой системе. По умолчанию создается по записи на каждые 4*размер_фрагмента байт пространства данных. Для того, чтобы создать побольше элементов таблицы, надо задавать соответственно большой параметр и наоборот. Для одного файла необходим один элемент таблицы, таким образом, этот параметр определяет средний размер файлов в файловой системе.

По умолчанию одна запись в таблице создается на каждые 4048 байт дискового пространства, и каждый файл имеет свой номер записи. Поскольку каждая запись соответствует одному файлу, то средний размер файла в файловой системе предполагается 4048 байт.

Вот еще два параметра, о которых следует здесь упомянуть, поскольку они влияют на производительность операции чтения/записи файловой системы FFS. Во-первых, ключ -m:
-m свободное_место %

Процент свободного места, резервируемого на файловой системе, т.е. порог минимального свободного пространства. Значение по умолчанию, определяется константой MINFREE модуля <ufs/ffs/fs.h>>. В настоящее время это значение равно 8%. Читайте man 8 tunefs для полного описания этого параметра.

Производительность любой файловой системы начинает "проседать", когда ей не хватает свободных блоков для хранения данных. Дело в том, что файловые системы "предпочитают" сохранять файлы одного каталога в смежных блоках (т.е. рядом друг с другом), а это становится затруднительным, когда количество свободных блоков уменьшается и системе приходится тратить время на их поиск. Создатели FFS указывают, что ее производительность резко падает, когда диск заполняется на более чем 90%. Когда файловая система достигает порога, установленного для объема свободного места (по умолчанию 8%), для обычных пользователей происходит блокирование записи любых файлов. Пользователи будут получать сообщения об ошибке, и, вероятно, напомнят администратору о сложившейся ситуации. Суперпользователь сохраняет возможность записи информации на диск и использования оставшихся свободными блоков, однако в этот момент лучше заняться разработкой плана спасения файловой системы, а не дожидаться, пока на ней кончится место.

Теперь давайте разберемся со вторым ключом, имеющим дело со свободным местом:
-o способ_оптимизации

Варианты - "space" и "time". Файловая система может либо пытаться уменьшить время, необходимое для выделения блоков данных, либо попробовать минимизировать фрагментацию дискового пространства. Если значение параметра minfree (см. выше) меньше 8%, то по умолчанию принимается оптимизация по фрагментации (space), а если больше или равно 8%, файловая система пытается оптимизировать время доступа (time). Для полного описания этого параметра почитайте man 8 tunefs.

Большинство файловых систем используют специальные алгоритмы для того, чтобы определить, какие файлы должны занимать какие блоки на диске. Этот ключ используется для определения, какой стратегии оптимизации должен придерживаться алгоритм FFS для размещения файлов. В ситуации достатка свободного дискового пространства можно использовать оптимизацию по скорости (time). Однако когда объем свободного места на диске переходит предел, определяемый ключом "-m", оптимизация фрагментации (space) становится более важной задачей, чем скорость.

Очевидно, что по умолчанию FFS использует оптимизацию по скорости, в тоже время создавая адекватное количество ячеек в индексной таблице, для записей о сохраняемых в системе файлах. Для большинства применений значения, определенные по умолчанию, будут наилучшими. В дальнейших статьях мы рассмотрим этот вопрос подробнее для того, чтобы научиться определять, являются ли в вашем случае значения по умолчанию оптимальными, а так же как выбирать оптимальные значения самостоятельно.

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

Обновлено: 12.03.2015