Сквозная передача FUSE

Android 12 поддерживает сквозную передачу FUSE, которая минимизирует накладные расходы FUSE для достижения производительности, сравнимой с прямым доступом к нижней файловой системе. Сквозная передача FUSE поддерживается в ядрах android12-5.4, android12-5.10 и android-mainline (только для тестирования). Это означает, что поддержка этой функции зависит от ядра, используемого устройством, и версии Android, на которой работает устройство:

  • Устройства, обновляемые с Android 11 до Android 12, не могут поддерживать сквозную передачу FUSE, поскольку ядра для этих устройств заморожены и они не могут перейти на ядро, которое было официально обновлено с изменениями сквозной передачи FUSE.

  • Устройства с Android 12 могут поддерживать передачу FUSE при использовании официального ядра. На таких устройствах код фреймворка Android, реализующий сквозную передачу FUSE, встроен в основной модуль MediaProvider, который обновляется автоматически. Устройства, на которых MediaProvider не реализован как основной модуль (например, устройства Android Go), также могут получать общедоступные изменения MediaProvider.

FUSE и SDCardFS

Файловая система в пользовательском пространстве (FUSE) – это механизм, который позволяет ядру (драйверу FUSE) передавать операции, выполняемые в файловой системе FUSE, программе в пользовательском пространстве (демону FUSE), которая реализует эти операции. В Android 11 поддержка SDCardFS прекращена, а FUSE стала решением по умолчанию для эмуляции хранилища. В рамках этого изменения в Android был реализован собственный демон FUSE, который перехватывает обращения к файлам, обеспечивает дополнительные функции безопасности и конфиденциальности, а также управляет файлами во время выполнения.

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

Чтобы избежать этого, в приложениях можно использовать сплайсинг, который позволяет сократить копирование данных, и ContentProvider API, чтобы получить прямой доступ к файлам в файловой системе. Даже с учетом этих и других оптимизаций при использовании FUSE скорость операций чтения и записи может быть ниже, чем при прямом доступе к файловой системе более низкого уровня, особенно при случайных операциях чтения, когда не помогают ни кеширование, ни предварительное чтение. Приложения, которые напрямую обращаются к хранилищу через устаревший путь /sdcard/, по-прежнему будут работать медленнее, особенно при выполнении операций ввода-вывода.

Запросы пользовательского пространства SDcardFS

Использование SDcardFS может ускорить эмуляцию хранилища и проверку разрешений FUSE, удалив вызов пользовательского пространства из ядра. Запросы из пользовательского пространства проходят по следующему пути: пользовательское пространство → VFS → sdcardfs → VFS → ext4 → кеш страниц/хранилище.

FUSE Passthrough SDcardFS

Рисунок 1. Запросы пользовательского пространства SDcardFS

Запросы FUSE в пространстве пользователя

Изначально FUSE использовалась для эмуляции хранилища и позволяла приложениям прозрачно использовать внутреннее хранилище или внешнюю SD-карту. Использование FUSE приводит к некоторым накладным расходам, поскольку каждый запрос пользовательского пространства следует по пути: пользовательское пространство → VFS → драйвер FUSE → демон FUSE → VFS → ext4 → кэш страниц/хранилище.

FUSE, сквозная передача FUSE

Рисунок 2. Запросы FUSE в пространстве пользователя

Запросы FUSE passthrough

Большинство разрешений на доступ к файлу проверяются при его открытии, а дополнительные проверки разрешений выполняются при чтении и записи в этот файл. В некоторых случаях при открытии файла можно определить, что у приложения, отправившего запрос, есть полный доступ к нему. В таком случае системе не нужно пересылать запросы на чтение и запись от драйвера FUSE демону FUSE, поскольку это приведет лишь к перемещению данных из одного места в другое.

При сквозной передаче FUSE демон, обрабатывающий открытый запрос, может уведомить драйвер FUSE о том, что операция разрешена и что все последующие запросы на чтение и запись можно напрямую пересылать в нижестоящую файловую систему. Это позволяет избежать дополнительных затрат на ожидание ответа от демона FUSE в пространстве пользователя на запросы драйвера FUSE.

Ниже показано сравнение запросов FUSE и FUSE passthrough.

Сравнение сквозной передачи FUSE

Рисунок 3. Запрос FUSE и сквозной запрос FUSE

Когда приложение обращается к файловой системе FUSE, происходят следующие операции:

  1. Драйвер FUSE обрабатывает и ставит в очередь запрос, а затем передает его демону FUSE, который управляет файловой системой FUSE через определенный экземпляр подключения к файлу /dev/fuse. Демон FUSE не может читать этот файл.

  2. Когда демон FUSE получает запрос на открытие файла, он решает, следует ли использовать для этого файла сквозную передачу FUSE. Если он доступен, демон:

    1. Уведомляет драйвер FUSE об этом запросе.

    2. Включает сквозную передачу FUSE для файла с помощью ioctl FUSE_DEV_IOC_PASSTHROUGH_OPEN, который должен быть выполнен в дескрипторе файла открытого /dev/fuse.

  3. Функция ioctl получает (в качестве параметра) структуру данных, которая содержит следующие элементы:

    • Дескриптор файла в файловой системе нижнего уровня, который является целью функции сквозной передачи.

    • Уникальный идентификатор запроса FUSE, который обрабатывается в данный момент (должен быть открыт или создан и открыт).

    • Дополнительные поля, которые можно оставить пустыми и которые предназначены для будущих реализаций.

  4. Если ioctl выполняется успешно, демон FUSE завершает запрос на открытие, драйвер FUSE обрабатывает ответ демона FUSE, а в файл FUSE в ядре добавляется ссылка на файл из более низкой файловой системы. Когда приложение запрашивает операцию чтения или записи файла FUSE, драйвер FUSE проверяет, доступна ли ссылка на файл более низкого уровня.

    • Если ссылка доступна, драйвер создает новый запрос к виртуальной файловой системе (VFS) с теми же параметрами, но уже к файлу в файловой системе более низкого уровня.

    • Если ссылка недоступна, драйвер перенаправляет запрос демону FUSE.

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

Как реализовать сквозную передачу FUSE

Чтобы включить передачу FUSE на устройствах с Android 12, добавьте следующие строки в файл $ANDROID_BUILD_TOP/device/…/device.mk целевого устройства.

# Use FUSE passthrough
PRODUCT_PRODUCT_PROPERTIES += \
    persist.sys.fuse.passthrough.enable=true

Чтобы отключить передачу FUSE, не вносите указанные выше изменения в конфигурацию или задайте для параметра persist.sys.fuse.passthrough.enable значение false. Если вы ранее включили передачу FUSE, то после отключения этой функции устройство не сможет ее использовать, но продолжит работать.

Чтобы включить или отключить передачу FUSE без перепрошивки устройства, измените системное свойство с помощью команд ADB. Пример приведен ниже.

adb root
adb shell setprop persist.sys.fuse.passthrough.enable {true,false}
adb reboot

Дополнительную информацию можно найти в справочной реализации.

Проверка сквозной передачи FUSE

Чтобы убедиться, что MediaProvider использует FUSE passthrough, проверьте logcat на наличие отладочных сообщений. Пример:

adb logcat FuseDaemon:V \*:S
--------- beginning of main
03-02 12:09:57.833  3499  3773 I FuseDaemon: Using FUSE passthrough
03-02 12:09:57.833  3499  3773 I FuseDaemon: Starting fuse...

Запись FuseDaemon: Using FUSE passthrough в журнале означает, что используется сквозная передача FUSE.

В Android 12 CTS входит CtsStorageTest, в котором есть тесты, запускающие сквозную передачу FUSE. Чтобы запустить тест вручную, используйте atest, как показано ниже.

atest CtsStorageTest