Android 12 admite el traspaso de FUSE, lo que minimiza la sobrecarga de FUSE para lograr un rendimiento comparable al acceso directo al sistema de archivos inferior. El paso directo 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 la versión de Android que ejecuta el dispositivo:
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 congelados y no pueden migrar a un kernel que se haya actualizado oficialmente con los cambios de transferencia de FUSE.
Los dispositivos que se lancen con Android 12 pueden admitir la transferencia de FUSE cuando se usa un kernel oficial. En el caso de estos dispositivos, el código del framework de Android que implementa la transferencia de FUSE está integrado en el módulo de la biblioteca principal MediaProvider, que se actualiza automáticamente. Los dispositivos que no implementan MediaProvider como un módulo de Mainline (por ejemplo, los dispositivos Android Go) también pueden acceder a los cambios de MediaProvider, ya que se comparten públicamente.
Comparación entre FUSE y 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 de FUSE a un programa de espacio del usuario (daemon de FUSE) que implementa las operaciones. Android 11 dio de baja 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 de FUSE para interceptar los accesos a los archivos, aplicar funciones adicionales de seguridad y privacidad, y manipular archivos en el tiempo de ejecución.
Si bien FUSE funciona bien cuando se trata de información que se puede almacenar en caché, como páginas o atributos, introduce 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 a varios cambios del espacio del kernel al espacio del usuario en las comunicaciones entre el controlador FUSE y el daemon FUSE (en comparación con el acceso directo al sistema de archivos inferior, que es más eficiente y se implementa por completo en el kernel).
Para mitigar estas regresiones, las apps pueden usar el splicing para reducir la copia de datos y usar la API de ContentProvider para obtener acceso directo a los archivos del sistema de archivos inferiores. Incluso con estas y otras optimizaciones, las operaciones de lectura y escritura pueden experimentar 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 las operaciones de lectura aleatoria, en las que no se puede usar 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 heredada /sdcard/
siguen experimentando disminuciones notables en el rendimiento, en especial cuando realizan operaciones con uso intensivo de E/S.
Solicitudes de espacio del usuario de SDcardFS
El uso de SDcardFS puede acelerar la emulación de almacenamiento y las verificaciones de permisos de FUSE, ya que quita la llamada al espacio del usuario del kernel. Las solicitudes del espacio del usuario siguen la ruta: espacio del usuario → VFS → sdcardfs → VFS → ext4 → caché de páginas/almacenamiento.
Figura 1: Solicitudes de espacio del usuario de SDcardFS
Solicitudes del espacio del usuario de FUSE
Inicialmente, 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 del usuario sigue la siguiente ruta: espacio del usuario → VFS → controlador de FUSE → daemon de FUSE → VFS → ext4 → caché de páginas/almacenamiento.
Figura 2: Solicitudes del espacio del usuario de FUSE
Solicitudes de transferencia directa de FUSE
La mayoría de los permisos de acceso a archivos se verifican en el momento de abrir el 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 abrir el archivo que la app solicitante tiene acceso completo al archivo solicitado, por lo que el sistema no necesita seguir reenviando las solicitudes de lectura y escritura del controlador de FUSE al daemon de FUSE (ya que eso solo moverí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 posteriores de lectura y escritura se pueden reenviar directamente al sistema de archivos inferior. Esto evita la sobrecarga adicional de esperar a que el daemon de FUSE del espacio del usuario responda a las solicitudes del controlador de FUSE.
A continuación, se muestra una comparación de las solicitudes de FUSE y de transferencia de FUSE.
Figura 3: Solicitud de FUSE en comparación con solicitud de transferencia de FUSE
Cuando una app realiza un acceso al sistema de archivos FUSE, ocurren las siguientes operaciones:
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 de FUSE a través de una instancia de conexión específica en el archivo
/dev/fuse
, que el daemon de FUSE no puede leer.Cuando el daemon de FUSE recibe una solicitud para abrir un archivo, decide si el paso directo de FUSE debe estar disponible para ese archivo en particular. Si está disponible, el daemon:
Notifica al controlador de FUSE sobre esta solicitud.
Habilita el paso directo 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.
El 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á procesando (debe estar abierta o crearse y abrirse).
Son campos adicionales que se pueden dejar vacíos y que están destinados a implementaciones futuras.
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 o escritura en un archivo de 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 que apuntan 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 la lectura/escritura y la lectura-iter/escritura-iter en archivos genéricos, y para las operaciones de lectura/escritura en archivos asignados a la memoria. El reenvío de FUSE para un archivo determinado existe hasta que se cierra ese archivo.
Implementa la transferencia de FUSE
Para habilitar la transferencia directa 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 la transferencia directa de FUSE, omite el cambio de configuración anterior o establece persist.sys.fuse.passthrough.enable
en false
. Si habilitaste la transferencia de FUSE anteriormente, inhabilitarla impedirá que el dispositivo la use, pero seguirá funcionando.
Para habilitar o inhabilitar la transferencia directa de FUSE sin escribir la memoria flash del dispositivo, cambia la propiedad del sistema con 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 ayuda adicional, consulta la implementación de referencia.
Valida la transferencia de FUSE
Para validar que MediaProvider esté usando la transferencia directa de FUSE, consulta logcat
para ver los mensajes de depuración. 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 directa de FUSE.
El CTS de Android 12 incluye CtsStorageTest
, que incluye pruebas que activan la transferencia de FUSE. Para ejecutar la prueba de forma manual, usa atest como se muestra a continuación:
atest CtsStorageTest