Вы наверняка сталкивались с этим эффектом: монтируете sshfs, rclone mount или gocryptfs — и после этого файловый менеджер, редактор, архиватор и бэкапилка работают с появившимся каталогом так, будто перед ними самая обычная файловая система. Хотя байты могут жить на сервере, в S3, внутри шифрованного каталога или вообще рождаться на лету.
Как программа из userspace умудряется встать между open() и данными так, чтобы остальная система ничего особенного не заметила? Сегодня разберёмся. Заваривайте чай покрепче (ядро любит крепкий), устраивайтесь поудобнее — поговорим про FUSE.
Сразу одна оговорка, чтобы не заложить мину в фундамент статьи: не всякая «сетевая папка» или подключённый телефон в файловом менеджере — это FUSE. GNOME GVfs, KDE KIO и MTP умеют показывать похожую картину на уровне приложений. FUSE идёт глубже: он подключается к файловому стеку ядра, поэтому смонтированное дерево видят обычные программы через привычные open(2), read(2), stat(2) и компанию.
Зачем вообще было огород городить
Обычные Linux-файловые системы вроде ext4, XFS и btrfs работают в ядре. Но причина не совсем в том, что «только ядро умеет трогать диск». Процесс с достаточными правами вполне может открыть блочное устройство и читать его напрямую.
Главное другое.
Главное — не доступ к блочному устройству, а единый слой, через который приложения вообще видят файлы.
В Linux эта задача была решена задолго до появления FUSE. Между программой и конкретной файловой системой находится VFS — Virtual File System: общий слой ядра, который позволяет ext4, XFS, NFS, tmpfs и десяткам других файловых систем выглядеть для приложений одинаково.
Программа говорит open(«/home/user/file»), а VFS разбирается, какой mount находится под этим путём и какой конкретной файловой системе передать операцию.
И вот спустя годы разработчики FUSE посмотрели на уже существующую архитектуру и задались интересным вопросом: а обязательно ли реализация, которой VFS передаёт эти операции, должна целиком жить в ядре?
А теперь представьте, что вы придумали супер-файловую систему. Например, хотите показывать почтовый ящик как каталог: письмо — файл, папка IMAP — директория, вложения — ещё файлы. Или хотите сделать файловый интерфейс к архивам, облаку, базе данных, удалённому серверу — да хоть к API кофеварки.
Путь первый — модуль ядра.
А это уже kernel-space: C, никаких привычных libc-шных удобств, аккуратная работа с памятью и блокировками, внутренние API ядра, которые для внешних модулей не обязаны вечно оставаться стабильными, и возможность превратить обычную ошибку в kernel oops или panic.
Знаете, как называется человек, который добровольно так делает?
Правильно: разработчик файловых систем ядра.
Их мало, и теперь вы возможно понимаете почему.
Но хотелось другого: чтобы файловую логику можно было написать обычной программой в userspace, отлаживать как обычную программу и при этом подключить к VFS так, чтобы остальная система видела нормальный mount.
Вот для этого и появился FUSE.
Однажды в Будапеште
История FUSE связана с венгерским разработчиком Миклошем Середи (Miklos Szeredi) и проектом AVFS — Virtual File System, который позволял обращаться к архивам и другим необычным источникам данных как к каталогам.
AVFS сначала экспериментировал с трюками вроде LD_PRELOAD, затем использовал интерфейс Coda. Но у обоих подходов были ограничения. В какой-то момент Середи решил, что нужен отдельный универсальный мост между VFS ядра и файловой системой в пользовательском процессе.
Так появился FUSE — Filesystem in Userspace.
Публичное объявление FUSE датируется 12 ноября 2001 года. Стабильный FUSE 1.0 вышел 20 февраля 2003-го. А 27 октября 2005 года FUSE вошёл в основное ядро Linux 2.6.14.
Это важная последовательность: FUSE не возник внезапно в момент попадания в mainline. К тому времени проект уже несколько лет жил отдельно, развивался и доказывал, что идея вообще работает. Ну знаете, как та история как когда bcachefs выпиздили из mainline, но только наоборот.
Идея была довольно дерзкая: оставить в ядре универсальную часть, необходимую для связи с VFS, а специфическую логику конкретной файловой системы вынести в обычный процесс.
То есть не «вытащить файловые системы из ядра вообще», а провести новую границу ответственности.
С этого момента экзотическую файловую систему стало возможно разрабатывать без собственного kernel-модуля. А обычный пользователь при подходящей конфигурации системы мог монтировать FUSE-файловые системы без полноценного root-доступа.
И вот тут начинается самое интересное.
Как это устроено
Упростим путь одного чтения файла.
Допустим, программа делает:
open("/mnt/cloud/photo.jpg", ...)
read(fd, buf, 4096)
Если /mnt/cloud — FUSE-mount, схема примерно такая:

Происходит следующее.
-
Программа вызывает обычный системный вызов. Никакого специального FUSE API она не знает.
-
VFS определяет, к какому mount относится путь. Часть работы ядро вообще может выполнить без обращения к демону — например, если нужные данные или метаданные уже есть в кэше.
-
Если для операции нужен userspace, FUSE-клиент в ядре формирует запрос. Он помещается в очередь соединения FUSE и становится доступен через специальное символьное устройство /dev/fuse.
-
FUSE-демон читает запрос из /dev/fuse. Например: «прочитай такой-то файл с такого-то смещения». Дальше ядру всё равно, откуда демон возьмёт результат. Он может прочитать локальный файл, сходить по SSH, запросить объект из S3, расшифровать блок или сгенерировать содержимое на лету.
-
Демон записывает ответ обратно в /dev/fuse. Ядро принимает его, обновляет своё состояние и завершает системный вызов приложения.
Получается почти МФЦ файловых систем.
Приложение приносит заявление ядру. Ядро понимает, что вопрос не по его ведомству, выдаёт талончик FUSE-демону, тот бегает по нужным инстанциям и возвращает официальный ответ. А заявитель на выходе получает обычные байты и вообще может не знать, что половина учреждения сидела в userspace.
Важно только не воспринимать метафору слишком буквально: не каждый read() обязательно превращается в отдельный поход через /dev/fuse. VFS, page cache, attribute cache и собственные механизмы FUSE умеют часть обращений обслуживать без нового round trip.
Именно поэтому точнее говорить не «FUSE пересылает в userspace каждый файловый вызов», а «FUSE пересылает туда те операции, для которых ядру нужен ответ файлового сервера».
А если демон упадёт?
Вот здесь у userspace есть огромный практический плюс.
Если ошибка происходит в файловой системе внутри ядра, радиус поражения потенциально велик: повреждение памяти, oops, deadlock, panic — спектр развлечений широкий.
Если падает FUSE-демон, ядро обычно продолжает жить.
Но есть важное «но»: сам mount после этого здоровее не становится. Процессы, которые обращаются к нему, могут получать ошибки, а некоторые незавершённые запросы способны зависнуть до разрыва/abort соединения.
То есть FUSE не превращает плохой код в безопасный. Он прежде всего изолирует значительную часть файловой логики от kernel-space. Ошибка теперь чаще ломает конкретную файловую систему и её клиентов, а не всю ОС.
Это всё ещё намного приятнее, чем отлаживать свежий kernel-модуль на машине, на которой вы параллельно пишете статью о преимуществах userspace.
А как пользователь вообще делает mount без root?
Сам системный вызов mount(2) — привилегированная операция. Поэтому в классической схеме libfuse есть небольшой помощник fusermount, а в современных версиях — fusermount3.
Обычно он устанавливается setuid-root и выполняет строго ограниченную привилегированную часть операции: проверяет условия, открывает нужное FUSE-соединение и помогает создать mount от имени пользователя. Сама файловая система после этого работает обычным процессом с обычными пользовательскими правами.
Отсюда же ещё одна деталь безопасности.
По умолчанию FUSE-mount пользователя не должен внезапно становиться ловушкой для всех соседей по машине. Чтобы разрешить доступ другим пользователям, файловую систему монтируют с опцией:
-o allow_other
А чтобы не-root пользователь вообще имел право запросить allow_other, администратор должен разрешить это в /etc/fuse.conf:
user_allow_otherТо есть user_allow_other сам по себе никому mount не «расшаривает». Он только разрешает обычным пользователям применять опцию allow_other.
Маленькая деталь, но именно на таких деталях обычно и живёт безопасность многопользовательской системы.
Чем это закончилось
А дальше случилось то, что обычно случается с хорошей абстракцией: люди начали запихивать в неё всё подряд.
SSHFS появился в 2004 году и сделал почти неприлично простой следующую вещь: берём SFTP и показываем удалённый сервер как локальный каталог. Никакого отдельного сетевого файлового протокола для приложения — оно просто читает файлы.
NTFS-3G в июле 2006-го вышел как beta, а 21 февраля 2007 года получил стабильный релиз 1.0. Для Linux-пользователей эпохи dual boot это было почти бытовой магией: полноценная запись на NTFS без внедрения самой реализации NTFS-3G в ядро. Сегодня в Linux есть и отдельный in-kernel драйвер ntfs3, поэтому NTFS-3G интересен здесь прежде всего как исторически важный пример возможностей FUSE.
Есть EncFS и gocryptfs, которые показывают расшифрованное представление данных поверх зашифрованного каталога.
Есть s3fs и rclone mount, превращающие объектные и облачные хранилища в дерево файлов — иногда с теми семантическими компромиссами, которые неизбежны, когда API вида «положить объект по ключу» заставляют притворяться POSIX-файловой системой.
Есть GlusterFS. У CephFS есть и kernel-клиент, и userspace-вариант ceph-fuse, так что говорить «CephFS работает через FUSE» целиком было бы неверно — FUSE там один из способов подключения.
А в 2009 году появился ещё один родственник — CUSE, Character device in Userspace. Он вошёл в Linux 2.6.31 и применил ту же идею уже к символьным устройствам: kernel-side прокладка, а логика устройства — в userspace. Один из ранних показательных примеров, OSS Proxy, создавал через CUSE привычные /dev/dsp, /dev/adsp и /dev/mixer, а дальше пересылал звук в userspace-аудиостек.
То есть кто-то посмотрел на FUSE и сказал:
— А файловыми системами ограничиваться обязательно?
Конечно нет.

Android — тоже FUSE, но история там хитрее
С Android легко написать эффектную фразу и почти гарантированно соврать.
История shared/external storage там несколько раз менялась. В старых версиях Android использовался FUSE-слой, который в том числе помогал реализовывать нужную модель доступа к общему хранилищу. В Android 8 ради производительности появился SDCardFS — реализация в ядре. Затем архитектура снова изменилась: в Android 11 SDCardFS для современных устройств был выведен из игры, а эмуляция shared storage вернулась к обновлённому FUSE-подходу вместе с MediaProvider и scoped storage.
В Android 12 появился ещё и Android-специфичный FUSE passthrough для некоторых веток ядер 5.4/5.10.
И это отдельная причина не писать, что «FUSE passthrough появился в Linux 5.15». В Android похожая технология жила в своих kernel-ветках раньше, чем её вариант попал в mainline Linux.
А если вы просто открыли телефон по MTP в Nautilus или Dolphin — это вообще может быть GVfs/KIO, а не FUSE-mount. Внешне похоже, этаж абстракции другой.
И даже виртуалки
В конце 2010-х появился virtiofs — файловая система для быстрого совместного доступа виртуальной машины к дереву каталогов на хосте. Поддержка virtiofs есть в mainline начиная с Linux 5.4 (2019).
Тут особенно интересно, что virtiofs использует протокол FUSE, но классическая схема меняется: вместо обычного обмена guest-демона с ядром через /dev/fuse запросы идут между гостем и хостом через virtio/virtqueues.
То есть FUSE к этому моменту оказался уже не только удобным способом написать userspace-файловуху. Его протокол стал строительным блоком для других архитектур.
Неплохо для идеи, выросшей из желания нормально лазить по архивам.
А что со скоростью?
А вот здесь наступает момент, когда за удобную абстракцию приходит счёт.
Если операция не обслужилась из кэша, классический FUSE-путь требует передать запрос из ядра в userspace, разбудить демон, обработать запрос и передать ответ обратно. Добавьте переключения контекста, копирование/маппинг данных, очереди, планировщик — и на большом количестве мелких операций накладные расходы становятся заметны.
Отсюда многолетняя репутация FUSE как «удобно, но медленно».
За последние годы в ядре появилось как минимум два важных ответа на эту проблему. И они решают разные задачи.
Passthrough: иногда демон можно вообще убрать с data path
В Linux 6.9, вышедшем в мае 2024 года, в mainline появился FUSE passthrough для обычного файлового I/O.
Идея такая: иногда userspace-демон нужен, чтобы решить, какому реальному файлу соответствует FUSE-файл, проверить политику или подготовить отображение. Но после этого нет особого смысла гонять через демон каждый read() и write(), если данные в итоге всё равно лежат в обычном backing file на нижележащей файловой системе.
Демон регистрирует backing file и при открытии говорит ядру примерно: «вот этому FUSE-файлу соответствует вот этот нижний файл». После этого поддерживаемые операции чтения/записи и mmap ядро может направлять непосредственно в backing filesystem.
Это очень мощная оптимизация, но не волшебная кнопка «ускорить любой FUSE».
Если ваши данные на самом деле синтезируются демоном, лежат в S3 или прилетают с другого конца SSH-соединения, реального локального backing file может просто не быть. Пропускать демон тогда некуда.
Кроме того, нынешняя upstream-реализация накладывает ограничения на создание passthrough-отображений: это не механизм, который произвольный непривилегированный FUSE-сервер автоматически получает бесплатно.
Так что правильнее думать о passthrough как об ускоренном маршруте для определённого класса FUSE-файловых систем.
io_uring: демон остаётся, но общаться с ним можно дешевле
В Linux 6.14, вышедшем 24 марта 2025 года, появилась другая оптимизация: FUSE over io_uring. Основную серию патчей для нового транспорта вёл Бернд Шуберт (Bernd Schubert).
Здесь никто не пытается убрать userspace-демон из схемы. Вместо этого меняется транспорт между ядром и демоном.
Классический /dev/fuse-интерфейс исторически означает много отдельных операций чтения и записи запросов. io_uring позволяет организовать очереди эффективнее: обрабатывать несколько запросов с меньшим syscall overhead, совмещать возврат ответа на старый запрос с получением нового и лучше сохранять CPU/NUMA locality.
Условно:
Classic:kernel -> request -> daemonkernel <- reply <- daemonkernel -> request -> daemonkernel <- reply <- daemonio_uring:kernel <==== queues / batches / commit+fetch ====> daemon
На микробенчмарках ранних patch series результаты местами были очень впечатляющими: отдельные сценарии показывали ускорения в два-три раза, а некоторые direct-I/O тесты — ещё больше. Но разброс между workload’ами огромный: другие тесты были близки к прежней скорости, а в одном из более поздних наборов измерений выигрыш составлял около 25%.
Поэтому фраза «в Linux 6.14 FUSE стал в несколько раз быстрее» была бы отличным заголовком и плохим техническим утверждением.
Корректнее так: FUSE-over-io_uring заметно снижает накладные расходы связи kernel -userspace и на подходящих нагрузках способен дать большой выигрыш, но величина ускорения сильно зависит от конкретной нагрузки.
И есть ещё одна важная деталь: поддержка io_uring-пути развивалась постепенно, и не все типы FUSE-запросов обязаны проходить через него — часть служебного обмена по-прежнему может использовать /dev/fuse.

Вот это различие стоит запомнить:
-
passthrough пытается не ходить к демону там, где данные можно отдать напрямую из backing file;
-
io_uring оставляет демон в цепочке, но делает сам обмен с ним дешевле.
Две оптимизации, две разные причины тормозов.
Так что же в FUSE было такого важного?
FUSE не доказал, что «файловые системы не нужны в ядре», и в целом не преследовал такую цель.
В ядре по-прежнему остаются VFS, mount namespace, кэши, проверки доступа, FUSE-клиент и куча другой работы. Из kernel-space вынесли то, что особенно удобно выносить: специфическую логику конкретной файловой системы.
И в этом, пожалуй, главное достижение FUSE.
Без него все перечисленные вещи не были бы физически невозможны — существовали и существуют kernel-модули, сетевые файловые протоколы, Coda, NFS, GVfs/KIO и другие способы решать похожие задачи.
Но FUSE сделал одну конкретную штуку почти рутинной:
обычный процесс может выставить произвольный источник данных в общий файловый namespace так, чтобы обычные программы работали с ним через привычные файловые системные вызовы.
Хотите файловую систему поверх SSH — пожалуйста.
Поверх шифрованного каталога — пожалуйста.
Поверх S3 — с оговорками, но пожалуйста.
Хотите протокол FUSE между виртуалкой и хостом — и до этого дошли.
А началось всё с вопроса: обязательно ли вся специфическая логика файловой системы должна жить в ядре?
Оказалось — нет.
И ядро с этим вполне ужилось: оставило себе контроль над границей, а userspace выдало окошко для приёма заявлений — почти как в МФЦ.
ссылка на оригинал статьи https://habr.com/ru/articles/1080806/