Как написать файловую систему

Обновлено: 07.07.2024

Это руководство по установке, настройке и разрешению FUSE и AFS для того, чтобы вы могли создать вашу собственную полностью функциональную файловую систему в пользовательской области на Linux. Файловая система может предоставлять расширенные возможности. Она может быть написана как надстройка над существующей файловой системой для управления ее данными и предоставлять расширенную, функционально полную файловую систему (например, cvsfs-fuse, которая обеспечивает интерфейс файловой системы для CVS, или файловая система Wayback, которая обеспечивает механизм резервного копирования для хранения старых копий данных).

круто
вы только вдумайтесь :
любой вменяемый программер может в линуксе создать свою собственную файловую систему
мало ли для чего это может понадобиться - тут широкое поле деятельности
что-либо подобное в оффтопике создать практически невозможно

Каждому онанимусу по файловой системе =)

>любой вменяемый программер может в линуксе создать свою собственную файловую систему Недавно как раз была дискуссия про ФС и изобретение велосипедов. И так уже чуть ли не каждый, кто ставил какой-никакой Linux-сервер, имеет собственное мнение по поводу лучшей ФС. Теперь каждый будет свою ФС писать?

есть еще интересная tagsfs - она на перле написана (через FUSE), но старая и не поддерживается давно, кто б ее передала под амарок..

Как-то один опытный виндовый разработчик, сказал: классно у вас в linux-ах, почти все на файловую систему отображается.

Ну а c фьюзом совсем скоро круто будет в этом плане.

ОФФТОП: Что-то IBM разошлась в плане переводов тех. доков и статей на др.языки :) Не заметили? Приятно однако такую заботу наблюдать.


На англицком это уже боян как год, а вообще пытался кто-нить химичить с этим FUSE?? Как с ним толком работать, где можно взять нормальные, внятные доки в какой последовательности и что надо делать??

>>любой вменяемый программер может в линуксе создать свою собственную файловую систему Недавно как раз была дискуссия про ФС и изобретение велосипедов. И так уже чуть ли не каждый, кто ставил какой-никакой Linux-сервер, имеет собственное мнение по поводу лучшей ФС. Теперь каждый будет свою ФС писать?

дада )) и через инитрд ее в качестве рут фс юзать. ужос

а поповоду сабжа fuse не только в linux ) а также в freebsd и mac os x, так что идея хороша, только дока не очень впечатляет :)

>Как-то один опытный виндовый разработчик, сказал: классно у вас в linux-ах, почти все на файловую систему отображается.

В винде примерно тоже самое (в NT ветке) только та фс на которую все отображается не показана пользователям :) и она отгруппирована по каталогам строже (что ей в минус) всмысе \\.\Pipes\named_pipe что-то типа такого :)

>Ну а c фьюзом совсем скоро круто будет в этом плане.

с фьюзом уже хорошо /me погладил sshfs которой пользуется каждый день

/me любит ntfs-3g за работоспособность

FUSE гг , прям как SUSE

> что-либо подобное в оффтопике создать практически невозможно

Насколько я понял из описания дизайна FUSE, возможно.


а что, FUSE уже проще чем 9P2000 или Styx .

>а что, FUSE уже проще чем 9P2000 или Styx .

А что, протокол, чья реализация на питоне занимает 20кб, уже считается сложным?

Кстати, FUSE это не протокол, так что сравнивать их некорректно, это в некотором роде только API.

В общем случае, для создания своей файловой системы (далее ФС), необходимо написать драйвер этой ФС и зарегистрировать его в ОС. Т.к. драйвер взаимодействует с ядром ОС, то его создание на managed-коде является делом нетривиальным, а производительность такого драйвера будет на невысоком уровне. В связи с этим, целесообразнее иметь драйвер ФС, написанный на native-коде и некий промежуточный слой между этим драйвером и managed-кодом. Такой драйвер существует в проекте Dokan, равно как и набор классов DokanNet для взаимодействия с этим драйвером в managed-коде. Оба проекта распространяются под лицензией GPLv3.

Попробуем разобраться, как это работает

Dokan — это драйвер, работающий на уровне ядра Windows, с которым могут взаимодействовать любые приложения из userspace-а. Драйвер существует как для 32-ух разрядный систем, так и для 64-ех разрядных. Драйвер не несет в себе реализацию какой-либо ФС, а лишь выступает в роли прокси, пропуская через себя все операции ввода-вывода (IO) из ядра ОС в userspace. Реализация функционала ФС лежит на наших плечах, мы создаем базовые функции ФС, такие как открытие/закрытие файла, чтение из файла, запись в файл и т.д., которые будут вызываться драйвером Dokan при возникновении в системе соответствующих событий. В момент регистрации нашей ФС в драйвере Dokan, мы указываем некоторые параметры работы нашей ФС (кол-во потоков-обработчиков операций ввода/вывода, точка монтирования нашей ФС (Dokan поддерживает монтирование в качестве съемного или сетевого диска и только в корень ФС), автоматическое размонтирование нашей ФС, в случае ошибок в работе, и некоторые другие параметры). После этого в ОС появляется новый диск в корне ФС, с которым любые приложения и сама ОС взаимодействует, как и с обычным диском. Какие операции разрешено производить в данной ФС, зависит только от разработчика этой ФС, то есть от нас.

  • DokanNetMirror — ФС-зеркало уже существующего каталога
  • RegistoryFS — ФС, представляющая структуру системного реестра Windows в директории и файлы
  • CreateFile — создание файлов/директорий
  • OpenDirectory — открытие директории
  • CreateDirectory — создание директории
  • Cleanup — удаление файла/пустой директории
  • CloseFile — закрытие файлового дескриптора
  • ReadFile — чтение куска файла указанной длины с указанным смещением
  • WriteFile — запись данных в файл с указанным смещением
  • FlushFileBuffers — очистка буферов (кэша) файла
  • GetFileInformation — получение информации о размере, атрибутах, времени создания/последнего доступа/модификации файла/директории
  • FindFiles — получение списка файлов/директорий в указанной директории
  • SetFileAttributes — установка атрибутов файла/директории
  • SetFileTime — установка времени создания/последнего доступа/модификации файла/директории
  • DeleteFile — пометка файла на удаление (удаление проводится в Cleanup)
  • DeleteDirectory — пометка директории на удаление (удаление проводится в Cleanup)
  • MoveFile — перемещение/переименование файла/директории
  • SetEndOfFile — установка размера файла (используется при создании пустого файла определенной длины)
  • SetAllocationSize — автор не указал для чего необходима данная фун-ция, на практике передача управления в нее не замечена
  • LockFile — блокировка файла в single-доступе
  • UnlockFile — снятие блокировки
  • GetDiskFreeSpace — получение информации о кол-ве доступного/общего/свободного места в ФС
  • Unmount — размонтирование/отключение ФС


Проект FUSE for macOS представляет собой аналогичный набор API (а также Objective-C фреймворк), позволяющий реализовать полноценную файловую систему, которая будет работать в пространстве пользователя на macOS. Так как его API является надмножеством FUSE API из Linux, то существует теоретическая возможность завести многие из существующих файловых систем на macOS. В настоящее время этот проект остается единственной реализацией FUSE для macOS, которая развивается и поддерживается силами сообщества, хотя и активность на GitHub и в Google Groups сейчас довольно низкая.

Установка фреймворка

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

Настройка проекта

Создадим новый проект в Xcode. Это должно быть Cocoa Application (в разделе macOS), я назвал его HelloFuse, язык выберем Swift, остальные параметры можно выбрать на свое усмотрение.

Подключим фреймворк

После установки фреймворк будет расположен по следующему пути: /Library/Frameworks/OSXFUSE.framework . Чтобы добавить его в проект, достаточно просто перетащить его в раздел Linked Frameworks and Libraries на вкладке General настроек сборки.

Подключение фреймворка

Подключение фреймворка

Создадим Bridging Header

Так как мы пишем проект на Swift, а фреймворк реализован на Objective-C, то нам нужно создать и подключить так называемый Bridging Header. Создадим заголовочный файл (File → New → File → macOS → Source → Header File), назовем его HelloFuse-Bridging-Header.h и добавим в него следующую строчку:

Теперь на панели навигации выбираем наш проект, выбираем сборку в разделе Targets, переходим на вкладку Build Settings, находим раздел Swift Compiler → General, в поле Objective-C Bridging Header добавляем

Подключаем Bridging Header в настройках проекта

Подключаем Bridging Header в настройках проекта

Отключим Sandbox

По умолчанию во всех приложениях включена песочница, которая ограничивает возможности приложения, но в отличие от iOS на macOS ее можно отключить. Этим ты потеряешь право распространять приложение через App Store (что тоже не будет проблемой в случае с macOS), но в нашем случае нам нужен полноценный доступ к файловой системе, поэтому выбора нет.

Перейдем на вкладку Capabilities в настройках сборки и поставим переключатель в пункте App Sandbox в положение OFF.

Hello world

Описание файловой системы

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

В методе, отвечающем за отображение файлов, нужно вернуть массив строк с именами файла. В качестве параметра туда приходит путь (path), в более сложных случаях нужно будет его обрабатывать, чтобы показывать контент соответствующей директории. Здесь я просто возвращаю один файл hello.txt .

В метод, который отвечает за отображение пути файла, аналогично приходит путь, в зависимости от которого мы должны решить, какое содержимое отдавать для файла. В нашем же примере мы будем для всех файлов возвращать строку «Hello world!».

В итоге файл HelloFS.swift примет следующий вид:

Продолжение доступно только участникам

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Доброго времени суток!

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

Можно было бы воспользоваться готовой ФС, однако либо они достаточно сложные (NTFS, FAT при использовании имён длиннее 12 символов), либо обладают целым набором ограничений (FAT). Поскольку немаловажным для нашей ОС критерием является наглядность разработки, мы реализуем загрузку с нашей собственной файловой системы. С одной стороны она имеет очень простую структуру, с другой с ней можно достаточно быстро работать и она не имеет ограничений вроде FAT. Её возможности можно будет легко расширить, ну а уже потом, в рабочую систему, можно добавить драйверы для работы с другими ФС, чтобы был возможен обмен данными с другими операционными системами.

Моя файловая система называется ListFS, потому что построена на концепции двунаправленных связанных списков. Рассмотрим её структуру.

Начинается файловая система с заголовка, который располагается в загрузочном секторе, начиная с 4-ого байта (байты до этого хранят команду перехода к настоящей точке входа в загрузчик).

Атрибуты файловой системы.

Таким образом заголовок файловой системы занимает целых 64 байта, зато предусматривает самые разнообразные требования к ФС и оставляет возможность расширения. Битовая карта представляет собой непрерывный массив секторов, доступ осуществляется на уровне отдельных битов (ОС, которая планирует производить операции записи на ListFS, скорее всего просто загрузит всю карту в оперативную память для удобства доступа). Каждый бит соответствует одному сектору ФС. Если бит установлен, значит сектор занят, если сброшен, то свободен. Карта необходима только для изменения данных - для поиска свободного сектора для новых и для освобождения секторов от удалённых файлов.

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

НазваниеРазмерОписание
f_name256 байтИмя файла в кодировке UTF-8. Пока мы не делаем локализованный интерфейс пользователя нас мало волнуют особенности Юникода, но без него будет плохо в будущем.
f_next8 байтНомер сектора со следующим заголовком файла. Если файл последний в каталоге, то -1.
f_prev8 байтНомер сектора с предыдущим заголовком файла. Если файл первый в каталоге, то -2
f_parent8 байтУказатель на родительский каталог. -1, если файл находится в корневом каталоге
f_flags8 байтАтрибуты файла. Пока определён только один - 0-ой бит - признак того, что это не файл, а каталог.
f_data8 байтУказатель на данные файлы. Если это каталог, то это указатель на первый файл каталога или -1 для пустого каталога. В обратном случае это указатель на сектор со списком секторов файла (у пустого файла так же будет -1). Такой сектор хранит sector_size / 8 номеров секторов. Список оканчивается номером -1. Последний элемент списка является указателем на следующий список (если он не равен -1 и до него не встретилось -1). Таким образом можно хранить файлы любой длины.
f_size8 байтРазмер файла в байтах
f_create_time8 байтДата создания
f_modify_time8 байтДата последнего изменения
f_access_time8 байтДата последнего обращения

Остальные байты сектора с заголовком зарезервированы для будущего использования. Например, можно добавить поля для хранения прав доступа POSIX.

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

Для удобного создания образов я написал кросс-платформенную утилиту на Си. Её исходники будут представлены в конце выпуска. Вы можете собрать её как в Windows (с использованием компилятора MinGW), так и в Linux (с использованием GCC). При запуске без параметров эта утилита выводит полную справку о своих аргументах.

А теперь, научим наш начальный загрузчик загружать файлы с нашей файловой системы.

Для начала введём новые переменные:

Все данные мы делаем неинициализированными (с помощью вопросительных знаков вместо значений) - Ассемблер оставит в файле пустое место для них, а заполнять уже будет утилита генерации образа ListFS. Специальная директива virtual позволяет разместить данные "как бы по указанному адресу". На выходной файл это никак не влияет, лишь создаёт метки по желаемым адресам. Заголовок файла мы будем загружать по адресу 0x800 и потом уже анализировать. Это будет специальный 512-байтный буфер для чтения служебных данных файловой системы.

Чтение файла мы разделим на несколько этапов - поиск файла, загрузка данных. Поскольку в 512 байт уместить код чтения с обработкой подкаталогов уже не получится, мы уместим туда лишь код поиска и загрузки, которого хватит, чтобы найти в корневом каталоге файл boot.bin и загрузить его, а уже он будет содержать продолжение загрузчика.

Функция выше позволит нам найти файл в каталоге по имени. В качестве значения DX:AX можно указать либо f_data каталога, либо fs_first_file (для поиска в корневом каталоге). После выполнения этой подпрограммы структура f_info будет содержать всю информацию о файле. После того как файл найден, его можно загрузить в оперативную память. Этим занимается следующая подпрограмма:

; Загрузка текущего файла в память по адресу BX:0. Количество загруженных секторов возвращается в AX load_file_data: push bx cx dx si di ; Сохраняем регистры mov ax, word[f_data] ; Загружаем в DX:AX номер сектора с первым списком mov dx, word[f_data + 2] .load_list: cmp ax, -1 ; Проверяем, не достигли ли мы конца списка jne @f cmp dx, -1 jne @f .file_end: ; Мы полностью загрузили файл pop di si dx cx ; Восстанавливаем все регистры кроме BX mov ax, bx ; Запоминаем BX pop bx ; Восстанавливаем его старое значение sub ax, bx ; Находим разницу - это и будет размер файла в блоках по 16 байт shr ax, 9 - 4 ; Переводим в сектора ret ; Выходим @@: mov di, f_info ; Будем загружать список во временный буфер call load_sector mov si, di ; SI := DI mov cx, 512 / 8 - 1 ; Количество секторов в списке .load_sector: lodsw ; Загружаем очередной номер сектора mov dx, [si] add si, 2 cmp ax, -1 ; Проверяем, что мы не в конце списка jne @f cmp dx, -1 je .file_end ; Если в конце, то выходим @@: push es mov es, bx ; Загружаем очередной сектор куда надо call load_sector add bx, 0x200 / 16 ; Следующий сектор будем загружать на 512 байт дальше pop es loop .load_sector ; Данная команда уменьшает CX на 1 и, если он ещё больше нуля, переходит на метку .load_sector lodsw ; Загружаем в DX:AX номер следующего списка mov dx, [si] jmp .load_list ; Переходим в начало цикла по спискам секторов

Готово, теперь у нас есть всё, чтобы загрузить продолжение загрузчика любой длины (надо только помнить, что лишь первые 640 КБ ОЗУ можно свободно изменять, дальше идут служебные области и BIOS, которые трогать не стоит). Что мы собственно и сделаем:

А в области данных опишем:

boot_file_name db "boot.bin",0

Ну вот наш загрузочный сектор и завершён :-)

Он настраивает содержимое регистров, как нам нужно, определяет параметры загрузочного устройства, находит и загружает в память файл boot.bin и, наконец, передаёт управление на него. При этом корректно обрабатывает все ошибки, кроме нехватки памяти (если boot.bin окажется больше, чем 500 с чем-то килобайт). От вывода названия загрузчика в этом месте пришлось отказаться, потому что иначе уже не получалось уложиться в один сектор.

Теперь продолжим писать код. В этом выпуске мы реализуем полноценную функцию загрузки файла, которая поддерживает подкаталоги. Располагаться она будет, как и весь остальной код во второй половине загрузчика. Мы не будем особо думать, а просто продолжим писать код после db 0x55,0xAA. При создании образа файл boot.bin следует разделить на две части - первые 512 байт подлежат записи в загрузочный сектор, всё остальное - в файл boot.bin уже на диске. Теперь мы не скованы размерами сектора и можно "разгуляться".

Приведу полнофункциональную подпрограмму загрузки файла:

Ну вот теперь работа с ListFS реализована в нашем загрузчике полностью. Можно читать любые файлы с диска, например, как-нибудь так:

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

Читайте также: