Главная » Статьи » Файловые системы | [ Добавить статью ] |
ВведениеНа момент написания статьи было трудно найти подробное описание реализации файловых систем для Linux. Эта статья – результат работы автора в процессе поиска материалов и реализации mumufs. Статья ставит целью описать в деталях процесс разработки Linux файловой системы, целиком находящейся в памяти, а так же познакомить читателя с особенностями mumufs. Стоить, однако, заметить, что автор не является опытным специалистом в разработке модулей ядра, поэтому в статье возможны неточности. Следующая глава кратко описывает mumufs, а более подробное описание можно найти в [1].
Краткое описание mumufsДля некоторых задач бывает удобной модель обмена данными между процессами со следующими характеристиками:
Такая модель хорошо подойдет для систем, когда история обмена не имеет значения. Примером могут служить приложения отображения данных, когда поставщики и потребители (то есть средства отображения) являются разныими процессами. Представим, что идентификация блоков данных производится способом, принятым для файловых систем, а в качестве измеряемой величины и спользуется скорость ветра. Тогда схема взаимодействия может выглядеть так, как показано на рисунке ниже.
Датчики скорости ветра могут подключаться по различным интерфейсам, которые обслуживаются разными процессами (процедуру выбора датчика, который должен поставлять значение в текущий момент времени, оставим за рамками обсуждения реализации mumufs). Как только новое значение получено, производится его запись в блок данных с идентификатором /mnt/mumu/wind. Процесс 3 может обслуживать интерфейс пользователя и, как только новое значение получено, производить обновление данных на экране. Процесс 4 может использовать значение скорости ветра для каких-либо расчетов. Каждый процесс, открывший и прочитавший из /mnt/mumu/wind получит текущее значение. При этом постоянное наличие ни потребителей, ни поставщиков данных не требуется. Поскольку базовыми операциями с блоком данных являются операции чтения и записи, то интерфейс к ним очень похож на интерфейс, предоставляемый файловыми системами. Удобным является и тот факт, что файловые системы подразумевают уникальность имен файлов. Имена файлов могут служить идентификаторами блоков данных. Еще одним положительным моментом является возможность использования штатных утилит UNIX для работы с файловыми системами, таких как mkdir, ls, rm, cat, touch, ln и т.д. Ядро операционной системы Linux предоставляет удобную подсистему VFS (virtual file system switcher) для разработки новых файловых систем, поэтому оставшаяся часть статьи описывает реализацию файловой системы, поддерживающей блочный обмен данными между процессами, допускающий много поставщиков и потребителей и размещающейся целиком в памяти.
Внешние требования к модулюРассмотрим подробнее, как должна выглядеть файловая система с точки зрения администратора системы. Поскольку файловая система располагается целиком в памяти, то удобно иметь средство контроля за количеством памяти, использованной файловой системой. В терминах mumufs интерес представляют два параметра – максимальный размер блока и максимальное количество блоков данных. Принимая во внимание, что файловая система может быть смонтирована произвольное количество раз, эти параметры должны быть параметрами монтирования, а не параметрами модуля в момент его загрузки. То есть администратор должен иметь возможность запустить такую команду:
Модуль, в свою очередь, может предоставить информацию через /proc файловую систему. Удобно было бы получать информацию о версии модуля, а так же обо всех смонтированных экземплярах mumufs. Структура может быть такой:
К сожалению, на одну и ту же точку монтирования потенциально можно смонтировать несколько разных экземпляров mumufs (хотя будет доступен для использования только последний смонтированный). Из-за этого, в качестве имени <смонтированная mumufs> нельзя использовать просто декорированный путь к точке монтирования. Нужно добавлять что-нибудь еще, например порядковый номер. Для простоты реализации, однако, код использует строковое представление адреса экземпляра структуры, описывающей точку монтирования. Теперь можно приступить к проектированию структур данных.
Структуры данныхДля хранения одного блока данных подойдет такая структура:
Поле Buffer указывает на блок данных, размер которого не должен превышать лимит, указанный в момент монтирования файловой системы. Поле BufferSize содержит текущий размер блока данных. Транзакцией обмена в mumufs является блок, и каждый новый блок не обязан быть точно такого же размера как и предыдущий, поэтому текущий размер надо запоминать. Поле BufferVersion содержит версию блока. Каждая новая запись увеличивает версию, а значение 0 говорит о том, что еще никаких данных нет. Для обеспечения безопасного обновления перечисленных выше полей потребуется блокировка BufferLock. Здесь подойдет rw_semaphore, которая обеспечивает множественное чтение и монопольную запись. При этом записи отдается приоритет. Последнее поле BlockedReaders необходимо для случая, когда используется блокируемое чтение, а данных либо нет, либо через данный файловый дескриптор последняя версия блока уже была прочитана. В этом случае процесс должен быть заблокирован. Поле BlockedReaders содержит список процессов, заблокированных на операции чтения блока данных. Кроме описанной выше структуры потребуется еще одна, в которой будет храниться информация о смонтированном экземпляре mumufs. Подойдет такая структура:
Поле Mount содержит указатель на структуру ядра, описывающую точку монтирования. Указатель на эту структуру не часто появляется в вызовах VFS, в часности он появляется в момент монтирования файловой системы. Без этой информации невозможно узнать, на какую точку была смонтирована файловая система, а модуль mumufs должен показать эту точку через /proc, поэтому указатель надо сохранить. Следующие два поля – MaxBlockSize и MaxEntries – сохраняют текущие параметры монтирования файловой системы. А поле NumberOfEntries хранит текущее количество блоков данных на смонтированном экземпляре mumufs. Последнее поле – parent – необходимо для корректного удаления записей в /proc, созданных для экземпляра mumufs. Удаление понадобится в момент размонтирования экземпляра mumufs. Остальныйе важные структуры данных предоставляются подстемой VFS.
Модуль ядраДля оформления модуля ядра 2.6 потребуются минимальные усилия. Необходимо определить несколько макросов:
И две функции, которые вызываются VFS в момент загрузки и выгрузки модуля:
В момент загрузки модуля потребуется выполнить регистрацию файловой системы в операционной системе, а в момент выгрузки де-регистрацию. Кроме этого нужно создать/удалить корневую запись в /proc (работа со /proc будет описана в отдельной секции). Для регистрации файловой системы требуется подготовить структуру типа file_system_type:
И сделать вызов
Присвоение полю owner значения THIS_MODULE позволяет ядру корректно вести счетчик ссылок на модуль. Функции mumufs_get_sb и mumufs_kill_super должны создавать и уничтожать суперблок файловой системы соответственно. Функция init_mumufs_fs может быть реализована так:
А функция exit_mumufs_fs так:
Монтирование и размонтирование mumufsФункции mumufs_get_sb и mumufs_kill_super, указатели на которые были предоставлены в момент регистрации файловой системы, будут использоваться VFS в момент монтирования и размонтирования mumufs. Для создания суперблока можно вопользоваться функцией VFS get_sb_nodev(), которая предназначена для случаев, когда файловая система не связана ни с каким устройством. Функция mumufs_get_sb должна выполнить еще одно действие – запомнить указатель на vsmount, который понадобится для /proc записей и который больше нигде не встретится. Таким образом функция может выглядеть так:
VFS функция get_sb_nodev принимает в числе прочего указатель на функцию, которая должна заполнить структуру super_block. В случае с mumufs это будет делать mumufs_fill_super(). В этой функции, помимо всего прочего, размещается экземпляр структуры mumu_mount_data, указатель на который записывается в поле s_fs_info структуры super_block. Это поле специально зарезервировано для специфических данных файловой системы. Фунция mumufs_fill_super может быть реализована так (контроль ошибок опущен для краткости):
Концептуально mumufs_fill_super() ответственна за несколько вещей. Во-первых, она размещает экземпляр структуры mumu_mount_point, затем разбирает параметры монтирования файловой системы с помощью функции mumufs_parse_opt() и записывает указатель на эту структуру в поле s_fs_info суперблока. Далее заполняются два важных поля – магический номер mumufs и указатель на структуру, описывающую операции с mumufs. После этого функция создает inode для корневого элемента смонтированной файловой системы с помощью функции mumufs_get_inode(). Поскольку операция создания inode нужна часто – в момент создания файлов, каталогов, ссылок – имеет смысл оформить эту функциональность в виде функции, что и сделано. Последнее, что делает mumufs_fill_super(), она создает записи в /proc с помощью функции CreateMountInfo(). Структура с операциями mumufs и магический номер выглядят так:
Разбор параметров монтирования, создание inode и работа с записями в /proc обсуждаются в деталях в отдельных главах. Функция mumufs_put_super() освободит память, занимаемую структурой, описывающей точку монтипрвания:
В момент размонтирования mumufs подсистема VFS вызовет функцию mumufs_kill_super(). Может оказаться так, что на файловой системе все еще существуют файлы, то есть все еще есть размещенные блоки памяти, ассоциированные с ними. Память нужно освободить, о чем и должна позаботиться mumufs_kill_super(). Здесь надо немного забежать вперед. Информация о блоке данных содержится в структуре Storage, которая будет размещаться в памяти в момент создания обычного файла, а указатель на нее будет записываться в поле i_private структуры inode. Принимая во внимание факт, что супер блок хранит список всех своих inode, имеется достаточно информации для реализации функции:
Разбор параметров монтированияДля разбора параметров монтирования VFS предлагает несколько функций, для работы которых необходимо подготовить структуру данных, описывающую возможные параметры:
Теперь можно реализовать функцию mumufs_parse_opt():
Для корректного отображения параметров монтирования, например по команде mount, необходимо реализовать функцию mumufs_show_options(), указатель на которую был записан в структуру операций mumufs.
Записи в /procВ момент загрузки модуля необходимо создать папку /proc/mumu, в которой будут создаваться все остальные записи. В этот же момент стоит создать файл /proc/mumu/version, чтение из которого выдаст версию mumufs. Это сделает функция InitialiseProcEntries():
Присвоение THIS_MODULE полю owner позволяет ядру корректно вести подсчет ссылок на модуль. Поле read_proc содержит указатель на функцию, которая будет вызвана, когда пользователь сделает попытку прочитать из соответствующего файла. Разумеется, в момент выгрузки модуля необходимо удалить записи из /proc:
Создание и удаление информации о каждой смонтированной mumufs производится аналогичным образом. Реализацию функций чтения из файла рассмотрим на примере функции чтения версии:
Интерес также представляет функция чтения информации о точке монтирования. В ней по полям структуры vfsmount, указатель на которую был сохранен в момент монтирования mumufs, определяется и выдается полный путь к точке монтирования. Для этого используется функция d_path():
Создание и удаление директорий, символических ссылок и файловКогда обсуждался процесс монтирования файловой системы, упоминалась функция mumufs_get_inode(), в обязанности которой входит создание inode. В момент монтирования нужно было создать inode для корневого директория файловой системы. Структура inode, в числе прочего, имеет поле, которое указывает на набор операций, допустимых для inode. Эти операции будут разными для каждого из типов inode: директорий, обычных файлов и символических ссылок. mumufs готовит структуры допустимых операций следующим образом:
Функции, начинающиеся с simple_ нет необходимости реализовывать – они взяты из VFS, их реализация годится для mumufs. А для символических ссылок используется целиком готовая структура inode_operations. Используя описанные структуры, можно реализовать mumufs_get_inode() (обработка ошибок опущена для краткости):
Функция проверяет для какого элемента был запрошен inode и затем вызывает new_inode(), предоставляемую VFS. Далее, в зависимости от типа элемента заполняются поля созданного inode. Для inode, созданного для файла, дополнительно проверяется, не превышен ли лимит на количество блоков данных, и если все в порядке, то размещается еще один блок с помощью AllocateStorage() и увеличивается счетчик блоков данных. Еще одно важное поле в структуре inode называется i_fop. Оно содержит указатель на структуру, описывающую файловые операции. Для символических ссылок и директорий годятся готовые структуры VFS, а для обычных файлов потребуется своя:
Указанные здесь функции и будут использоваться для всех операций с файлами, то есть в случае mumufs с блоками данных. Открытие и закрытие файловЗа открытие и закрытие обычных файлов отвечают функции mumufs_file_open() и mumufs_file_release() соответственно. Здесь важным моментом является то, что ядро подготавливает структуру file, которая соответствует файловому дескриптору в пространстве пользователя. Указатель на эту структуру передается во все функции, реализующие операции с файлами. В структуре file есть поле private_data, которое файловая система может использовать для своих специфических нужд. Mumufs надо хранить версию последнего прочитанного блока и это поле является хорошим кандидатом. В момент открытия файла его нужно обнулить.
В момент закрытия файла ничего делать не требуется. Функция приведена, скорее, с демонстрационными целями.
Запись в файлРеализация функции записи в файл не представляет сложности. Она должна проверить, что размер записываемого блока не превышает лимит, скопировать данные из пространства пользователя в пространство ядра, взять блокировку, заменить указатель, увеличить номер версии блока, отпустить блокировку и наконец уведомить заблокированных читателей о том, что новые данные доступны. Все действия строго последовательны (контроль ошибок опущен для краткости):
Помимо описанных выше действий функция обновляет информацию о текущем размере файла и дату его модификации.
Чтение из файлаРеализация чтения из файла немного сложнее. Здесь необходимо учитывать то, в каком режиме был открыт файл. Если был использован режим с блокированием, то в случае отсутствия новых данных процесс должен быть заблокирован. Если же был использован режим без блокирования, то в таком случае процесс блокировать не надо, а вместо этого возвратить признак отсутствия данных –EAGAIN.
В самом начале функция проверяет, что предоставленный пользователем буфер имеет достаточный размер, чтобы туда поместился весь имеющийся блок данных. Затем она берет блокировку на чтение и проверяет наличие новых данных. Если таковые имеются, то они копируются в пространство пользователя, блокировка отпускается и на этом процесс заканчивается. Если же данных нет, то проверяется режим открытия файла, в зависимости от которого процесс помещается в очередь ожидания или нет. Именно эту очередь использует функция записи для нотификации о доступности новых данных. Select и PollПоследнее, что нужно сделать, это добавить поддержку операций select и poll. Соответствующая функция должна вернуть маску, которая говорит о том, какую операцию операцию с файлом сможет выполнить процесс без блокирования. Неблокируемую запись в mumufs можно выполнить в любой момент, а чтение только если имеются новые данные. Перед тем, как вернуть маску, необходимо зарегестрировать очередь ожидания для системных вызовов select и poll с помощью вызова poll_wait().
Тестирование производительностиТестирование производительности mumufs имеет смысл проводить в сравнении с производительностью какого-либо другого механизма, обеспечивающего такую же функциональность и уже имеющегося в системе. Подходящих кандидатов два – разделяемая память и очередь сообщений. Разделяемая память на первый взгляд кажется ближе к mumufs, потому что не накладывает ограничений на количество поставщиков и потребителей и хранит только одну копию данных. Однако она не предоставляет встроенного механизма обеспечения синхронизации доступа. Это означает, что клиентский код должен самостоятельно реализовать синхронизацию доступа. Это, в свою очередь, привносит дополнительную сложность в клиентский код с повышением вероятности появления ошибок. Такая синхронизация, к тому же, будет исключительно добровольной, что также может сказаться на надежности программного обеспечения. Исходя из этого очереди сообщений оказываются ближе к mumufs с точки зрения похожести клиентского кода и надежности синхронизации. Строго говоря, код с использованием mumufs для обмена данными будет проще, однако ничего ближе, чем очереди сообщений нет, поэтому сравнение будет проводиться именно с ними. Очереди сообщений плохо приспособлены для организации обмена с множественными поставщиками и множественными потребителями данных – для каждой пары нужно создавать отдельную очередь, что приводит к сложному тестовому коду. Поэтому тестовые программы написаны для модели с одним поставщиком и переменным количеством потребителей. Здесь, очевидно, очереди сообщений проигрывают по простоте использования mumufs. Однако такое упрощение позволяет сохранить простоту и краткость тестов, а по результатам упрощенной модели сделать предположения и о полной модели. Далее приводятся графики замера времени, потребовавшегося поставщику данных на отсылку (для очереди сообщений) или запись (для mumufs) и времени, прошедшего с начала отсылки/записи первого пакета до момента приема/чтения последнего пакета всеми потребителями. Время отложено по оси ординат. Соответственно, чем меньше затраченное время, тем быстрее работает механизм обмена данными. Количество пакетов во всех экспериментах одинаково и равно 100000. Количество потребителей варьируется от 1 до 64, оно отложено по оси абсцисс. Каждый эксперимент проводился 3 раза, а полученные результаты затем усреднялись.
График для размера пакета 16384 байта приводится только для mumufs. Для очереди сообщение системное ограничение максимального размера блока данных по умолчанию установлено в значение 8192, поэтому для нее тестовые запуски не проводились. Цель графика для 16384 байт для mumufs посмотреть как она себя ведет с ростом размера пакета.
Можно заметить, что в схеме с одним поставщиком и одним потребителем mumufs проигрывает очереди сообщений. При этом проигрыш может достигать порядка 40%. Однако с ростом количества потребителей mumufs начинает выигрывать. Выигрыш может достигать порядка 120%. С ростом размера пакета затраченное время растет, однако несколько медленнее, чем размер пакета. С точки зрения потребления памяти mumufs в пике потребляет меньше памяти, чем очередь сообщений. В пиковой ситуации очередь сообщений будет хранить все непрочитанные сообщения, помноженные на количество очередей. А mumufs всегда будет хранить только одну копию блока данных. У mumufs есть резерв повышения производительности за счет расхода памяти. Если для каждого блока сразу выделять два буфера максимального размера одного блока, то появляется возможность избежать дорогих Источник: http://satsky.spb.ru/articles/mumufs/mumufsDev.php | |||||||||||||||||||||||||||||||||||||||
Просмотров: 1183 | |
Всего комментариев: 0 | |
Операционные Системы
[61]
ОС Open Source
|
Мобильный Linux [26] |
Сравнение ОС [7] |
Статьи о Linux [16] |
Свободное ПО [10] |
Програмирование [6] |
Не для нубов [5] |
Ядро [13] |
Хранилище данных [9] |
Устройства [1] |
Установка/конфигурирование/планиров [16] |
Файловые системы [3] |
Управление, основанное на политиках [1] |
Управление инфраструктурой [0] |
Серверы [5] |
Биографии [6] |
Прочее [25] |