Я написал файловую систему для Linux за вечер
Когда мы говорим файловая система, обычно представляем что-то очень сложное: ext4, btrfs, десятки тысяч строк кода в ядре, журналирование, кеши, оптимизации. Но если убрать все оптимизации, минимальная файловая система оказывается удивительно простой. В этой статье я попробовал сделать эксперимент: написать минимальную файловую систему для Linux, которую можно смонтировать и использовать как обычную. Без написания кода ядра, без драйверов.Только пользовательский код и FUSE. В итоге получилась работающая файловая система примерно на 200 строках Python, которая умеет:
создавать файлы
читать файлы
записывать данные
показывать содержимое каталога
И её можно смонтировать обычной командой mount.
Почему FUSE
Linux позволяет писать файловые системы в user space через механизм Filesystem in Userspace (FUSE). Идея простая:
программа → FUSE → kernel → VFS → приложенияКогда приложение делает системный вызов:
open()
read()
write()ядро пересылает запрос в нашу программу. Мы возвращаем результат, и для приложений всё выглядит как обычная файловая система.
Установка FUSE
В большинстве дистрибутивов всё уже есть. Ubuntu / Debian:
sudo apt install fusePython библиотека:
pip install fusepyМинимальная файловая система
Начнём с самого простого варианта.
Создадим файл:
simplefs.pyИ подключим нужные модули.
from fuse import FUSE, Operations
import osТеперь создадим класс файловой системы.
class SimpleFS(Operations):
def __init__(self):
self.files = {}Мы будем хранить файлы в обычном словаре Python.
Реализация getattr
Каждая файловая система должна отвечать на вопрос: что это за файл? Для этого используется операция getattr.
def getattr(self, path, fh=None):
if path == '/':
return dict(
st_mode=(0o755 | 0o040000),
st_nlink=2
)
if path in self.files:
return dict(
st_mode=(0o644 | 0o100000),
st_nlink=1,
st_size=len(self.files[path])
)
raise FileNotFoundErrorЗдесь происходит несколько вещей. Если путь /, значит это корневой каталог. Флаг 0o040000 говорит ядру, что это директория. Флаг 0o100000 означает обычный файл.
Список файлов
Когда выполняется команда:
lsLinux вызывает функцию readdir. Добавим её.
def readdir(self, path, fh):
return ['.', '..'] + list(self.files.keys())Мы просто возвращаем список имён файлов.
Создание файла
Теперь реализуем создание файла.
def create(self, path, mode):
self.files[path] = b''
return 0Здесь всё очень просто. Мы просто добавляем запись в словарь.
Запись данных
Когда программа записывает файл, вызывается операция write.
def write(self, path, data, offset, fh):
current = self.files[path]
new_data = (
current[:offset] +
data +
current[offset + len(data):]
)
self.files[path] = new_data
return len(data)Что здесь происходит. Файлы у нас хранятся как bytes. Когда приходит запись: берём старые данные
вставляем новые данные по offset
сохраняем результат
Чтение файла
Теперь чтение.
def read(self, path, size, offset, fh):
data = self.files[path]
return data[offset:offset + size]Здесь всё максимально просто. Берём нужный кусок данных.
Удаление файла
Добавим удаление.
def unlink(self, path):
del self.files[path]Запуск файловой системы
Теперь добавим точку входа.
if __name__ == '__main__':
import sys
mountpoint = sys.argv[1]
FUSE(
SimpleFS(),
mountpoint,
foreground=True
)Монтируем файловую систему
Создадим каталог:
mkdir mountЗапустим:
python simplefs.py mountТеперь откроем второй терминал.
cd mountИ попробуем:
touch test.txt
echo hello > test.txt
cat test.txtФайл будет работать как обычный.
Проверим что происходит
Если добавить логирование:
print("WRITE", path)Можно увидеть, какие операции реально вызывает Linux. Оказывается, даже простая команда:
cat fileвызывает целую серию операций:
getattr
open
read
read
releaseЭто хороший способ понять, как реально работает VFS Linux.
Ограничения нашей файловой системы
Конечно, она очень примитивная. Вот чего в ней нет:
хранения на диске
прав доступа
журналирования
директорий
кеширования
Все файлы хранятся в памяти процесса. После остановки всё исчезает.
Добавим сохранение на диск
Чтобы файлы не пропадали, можно сохранять данные в JSON.
import jsonПри запуске:
if os.path.exists("data.json"):
with open("data.json") as f:
self.files = json.load(f)При изменении файлов:
def save(self):
with open("data.json", "w") as f:
json.dump(self.files, f)Что меня удивило
Когда начинаешь писать файловую систему, ожидаешь огромную сложность. Но минимальный вариант оказывается очень компактным. Основная сложность современных FS — это:
производительность
отказоустойчивость
параллелизм
восстановление после сбоя
А базовая модель хранения файлов довольно проста.
В заключении
За вечер получилась работающая файловая система:
около 200 строк кода
полностью монтируется в Linux
работает через стандартные команды
И самое главное — это отличный способ понять, как работает:
VFS Linux
системные вызовы
взаимодействие ядра и user space
После такого эксперимента команды вроде
open
read
writeперестают быть абстракцией. И начинаешь намного лучше понимать, как на самом деле устроен Linux.