Transferencia de FUSE

Android 12 admite el traspaso de FUSE, que minimiza la sobrecarga de FUSE para lograr un rendimiento comparable al acceso directo al sistema de archivos inferior. La transferencia de FUSE es compatible con los kernels android12-5.4, android12-5.10 y android-mainline (solo para pruebas), lo que significa que la compatibilidad con esta función depende del kernel que usa el dispositivo y de la versión de Android que ejecuta:

  • Los dispositivos que se actualizan de Android 11 a Android 12 no pueden admitir la transferencia de FUSE, ya que los kernels de estos dispositivos están inmovilizados y no pueden cambiar a un kernel que se haya actualizado oficialmente con los cambios de transferencia de FUSE.

  • Los dispositivos que se lanzan con Android 12 pueden admitir la transferencia de FUSE cuando se usa un kernel oficial. Para esos dispositivos, el código del framework de Android que implementa la transferencia de FUSE está incorporado en el módulo de línea principal de MediaProvider, que se actualiza automáticamente. Los dispositivos que no implementan MediaProvider como un módulo principal (por ejemplo, los dispositivos Android Go) también pueden acceder a los cambios de MediaProvider a medida que se comparten de forma pública.

FUSE frente a SDCardFS

El sistema de archivos en el espacio del usuario (FUSE) es un mecanismo que permite que el kernel (controlador de FUSE) subcontrate las operaciones realizadas en un sistema de archivos FUSE a un programa de espacio de usuario (daemon de FUSE), que implementa las operaciones. Android 11 dio de baja a SDCardFS y convirtió a FUSE en la solución predeterminada para la emulación de almacenamiento. Como parte de este cambio, Android implementó su propio daemon FUSE para interceptar el acceso a archivos, aplicar funciones adicionales de seguridad y privacidad, y manipular archivos en el tiempo de ejecución.

Si bien FUSE tiene un buen rendimiento cuando se trata de información almacenable en caché, como páginas o atributos, presenta regresiones de rendimiento cuando se accede al almacenamiento externo, que son especialmente visibles en dispositivos de gama media y baja. Estas regresiones se deben a una cadena de componentes que cooperan en la implementación del sistema de archivos FUSE, así como varios conmutadores del espacio del kernel al espacio del usuario en las comunicaciones entre el controlador FUSE y el daemon de FUSE (en comparación con el acceso directo al sistema de archivos inferior, que es más eficiente y está completamente implementado en el kernel).

Para mitigar estas regresiones, las apps pueden usar la unión para reducir la copia de datos y usar la API de ContentProvider para obtener acceso directo a los archivos del sistema de archivos inferior. Incluso con estas y otras optimizaciones, las operaciones de lectura y escritura pueden ver una reducción del ancho de banda cuando se usa FUSE en comparación con el acceso directo al sistema de archivos inferior, en especial con operaciones de lectura aleatorias, en las que no pueden ayudar el almacenamiento en caché ni la lectura anticipada. Además, las apps que acceden directamente al almacenamiento a través de la ruta de acceso /sdcard/ heredada siguen experimentando caídas de rendimiento notables, en especial cuando realizan operaciones intensivas de E/S.

Solicitudes de espacio de usuario de SDcardFS

El uso de SDcardFS puede acelerar la emulación de almacenamiento y las verificaciones de permisos de FUSE quitando la llamada de espacio de usuario del kernel. Las solicitudes del espacio de usuario siguen la ruta de acceso: Espacio de usuario → VFS → sdcardfs → VFS → ext4 → Almacenamiento/caché de páginas.

Transferencia de SDcardFS a través de FUSE

Figura 1: Solicitudes del espacio de usuario de SDcardFS

Solicitudes de espacio de usuario de FUSE

En un principio, FUSE se usaba para habilitar la emulación de almacenamiento y permitir que las apps usaran de forma transparente el almacenamiento interno o una tarjeta SD externa. El uso de FUSE introduce cierta sobrecarga porque cada solicitud del espacio de usuario sigue la ruta de acceso: espacio de usuario → VFS → controlador de FUSE → daemon de FUSE → VFS → ext4 → caché de página/almacenamiento.

FUSE FUSE de transferencia

Figura 2: Solicitudes del espacio de usuario FUSE

Solicitudes de transferencia de FUSE

La mayoría de los permisos de acceso a archivos se verifican en el momento de la apertura del archivo, y se realizan verificaciones de permisos adicionales cuando se lee y escribe en ese archivo. En algunos casos, es posible saber en el momento de la apertura del archivo que la app solicitante tiene acceso completo al archivo solicitado, por lo que el sistema no necesita continuar reenviando las solicitudes de lectura y escritura del controlador de FUSE al daemon de FUSE (ya que eso solo transferiría datos de un lugar a otro).

Con la transferencia de FUSE, el daemon de FUSE que controla una solicitud abierta puede notificar al controlador de FUSE que se permite la operación y que todas las solicitudes de lectura y escritura posteriores se pueden reenviar directamente al sistema de archivos inferior. De esta manera, se evita la sobrecarga adicional de esperar a que el daemon de FUSE del espacio de usuario responda a las solicitudes del controlador de FUSE.

A continuación, se muestra una comparación de las solicitudes de transferencia FUSE y FUSE.

Comparación de transferencia directa de FUSE

Figura 3: Solicitud de FUSE en comparación con la solicitud de transferencia de FUSE

Cuando una app realiza un acceso al sistema de archivos FUSE, se producen las siguientes operaciones:

  1. El controlador de FUSE controla y pone en cola la solicitud y, luego, la presenta al daemon de FUSE que controla ese sistema de archivos a través de una instancia de conexión específica en el archivo /dev/fuse, que el daemon de FUSE no puede leer.

  2. Cuando el daemon de FUSE recibe una solicitud para abrir un archivo, decide si la transferencia de FUSE debe estar disponible para ese archivo en particular. Si está disponible, el daemon hace lo siguiente:

    1. Notifica al controlador del FUSE sobre esta solicitud.

    2. Habilita el reenvío de FUSE para el archivo con el ioctl FUSE_DEV_IOC_PASSTHROUGH_OPEN, que se debe realizar en el descriptor de archivos del /dev/fuse abierto.

  3. ioctl recibe (como parámetro) una estructura de datos que contiene lo siguiente:

    • Es el descriptor de archivo del archivo del sistema de archivos inferior que es el destino de la función de transferencia.

    • Es el identificador único de la solicitud de FUSE que se está controlando actualmente (debe estar abierta o crearse y abrirse).

    • Son campos adicionales que se pueden dejar en blanco y están destinados a implementaciones futuras.

  4. Si el ioctl se realiza correctamente, el daemon de FUSE completa la solicitud de apertura, el controlador de FUSE controla la respuesta del daemon de FUSE y se agrega una referencia al archivo del sistema de archivos inferior al archivo de FUSE dentro del kernel. Cuando una app solicita una operación de lectura/escritura en un archivo FUSE, el controlador de FUSE verifica si la referencia a un archivo del sistema de archivos inferior está disponible.

    • Si hay una referencia disponible, el controlador crea una nueva solicitud de sistema de archivos virtual (VFS) con los mismos parámetros orientados al archivo del sistema de archivos inferior.

    • Si no hay una referencia disponible, el controlador reenvía la solicitud al daemon de FUSE.

Las operaciones anteriores se producen para operaciones de lectura/escritura y de iteración de lectura/escritura en archivos genéricos y operaciones de lectura/escritura en archivos asignados a memoria. La transferencia de FUSE para un archivo determinado existe hasta que se cierra ese archivo.

Implementa el modo de transferencia de FUSE

Para habilitar la transferencia de FUSE en dispositivos que ejecutan Android 12, agrega las siguientes líneas al archivo $ANDROID_BUILD_TOP/device/…/device.mk del dispositivo de destino.

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

Para inhabilitar el pase de FUSE, omite el cambio de configuración anterior o establece persist.sys.fuse.passthrough.enable en false. Si habilitaste previamente la transferencia de FUSE, inhabilitarla evitará que el dispositivo la use, pero el dispositivo seguirá funcionando.

Para habilitar o inhabilitar la transferencia de FUSE sin escribir en la memoria flash del dispositivo, cambia la propiedad del sistema mediante los comandos de ADB. A continuación, se muestra un ejemplo:

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

Para obtener más ayuda, consulta la implementación de referencia.

Valida la transferencia de FUSE

Para validar que MediaProvider use la transferencia de FUSE, verifica logcat para depurar mensajes. Por ejemplo:

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...

La entrada FuseDaemon: Using FUSE passthrough en el registro garantiza que se esté usando la transferencia de FUSE.

El CTS de Android 12 incluye CtsStorageTest, que incluye pruebas que activan el pase de FUSE. Para ejecutar la prueba de forma manual, usa atest como se muestra a continuación:

atest CtsStorageTest