Когда мы говорим файловая система, обычно представляем что-то очень сложное: 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 fuse

Python библиотека:

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 означает обычный файл.

Список файлов

Когда выполняется команда:

ls

Linux вызывает функцию 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.