Passthrough di FUSE

Android 12 supporta il passthrough di FUSE, che riduce al minimo l'overhead di FUSE per ottenere prestazioni paragonabili all'accesso diretto al file system di livello inferiore. Il passthrough di FUSE è supportato nei kernel android12-5.4, android12-5.10 e android-mainline (solo per test), il che significa che il supporto di questa funzionalità dipende dal kernel utilizzato dal dispositivo e dalla versione di Android su cui il dispositivo è in esecuzione:

  • I dispositivi che eseguono l'upgrade da Android 11 ad Android 12 non possono supportare il passthrough FUSE perché i kernel di questi dispositivi sono bloccati e non possono passare a un kernel di cui è stato eseguito ufficialmente l'upgrade con le modifiche al passthrough FUSE.

  • I dispositivi lanciati con Android 12 possono supportare il passthrough FUSE se si utilizza un kernel ufficiale. Per questi dispositivi, il codice del framework Android che implementa il passthrough FUSE è incorporato nel modulo principale MediaProvider, di cui viene eseguito automaticamente l'upgrade. Anche i dispositivi che non implementano MediaProvider come modulo principale (ad esempio i dispositivi Android Go) possono accedere alle modifiche di MediaProvider man mano che vengono condivise pubblicamente.

FUSE e SDCardFS

Il file system nello spazio utente (FUSE) è un meccanismo che consente di esternalizzare le operazioni eseguite su un file system FUSE dal kernel (driver FUSE) a un programma nello spazio utente (demone FUSE), che le implementa. Android 11 ha ritirato la scheda SD e ha reso FUSE la soluzione predefinita per l'emulazione dello spazio di archiviazione. Nell'ambito di questa modifica, Android ha implementato il proprio daemon FUSE per intercettare gli accessi ai file, applicare ulteriori funzionalità di sicurezza e privacy e manipolare i file in fase di runtime.

Sebbene FUSE sia efficace per la gestione di informazioni memorizzabili nella cache come pagine o attributi, introduce una regressione delle prestazioni quando si accede a dispositivi di archiviazione esterni, particolarmente visibili sui dispositivi di fascia media e bassa. Queste regressioni sono causate da una catena di componenti che collaborano nell'implementazione del file system FUSE, nonché da più passaggi dallo spazio kernel allo spazio utente nelle comunicazioni tra il driver FUSE e il daemon FUSE (rispetto all'accesso diretto al file system inferiore, più snello e completamente implementato nel kernel).

Per mitigare queste regressioni, le app possono utilizzare l'unione per ridurre la copia dei dati e utilizzare l'API ContentProvider per ottenere l'accesso diretto ai file del file system di livello inferiore. Anche con queste e altre ottimizzazioni, le operazioni di lettura e scrittura potrebbero registrare una larghezza di banda ridotta quando si utilizza FUSE rispetto all'accesso diretto al file system di livello inferiore — in particolare con le operazioni di lettura random, dove non è possibile utilizzare la memorizzazione nella cache o la lettura anticipata. Inoltre, le app che accedono direttamente allo spazio di archiviazione tramite il percorso /sdcard/ precedente continuano a registrare cali di prestazioni evidenti, soprattutto quando eseguono operazioni con un'intensità di I/O elevata.

Richieste dello spazio utente SDcardFS

L'utilizzo di SDcardFS può velocizzare l'emulazione dello spazio di archiviazione e i controlli delle autorizzazioni di FUSE rimuovendo la chiamata allo spazio utente dal kernel. Le richieste nello spazio utente seguono il percorso: spazio utente → VFS → sdcardfs → VFS → ext4 → cache di pagine/archiviazione.

FUSE Passthrough SDcardFS

Figura 1. Richieste di spazio utente SDcardFS

Richieste dello spazio utente FUSE

FUSE è stato inizialmente utilizzato per abilitare l'emulazione dello spazio di archiviazione e consentire alle app di usare in modo trasparente la memoria interna o una scheda SD esterna. L'utilizzo di FUSE introduce un certo sovraccarico perché ogni richiesta nello spazio utente segue il percorso: spazio utente → VFS → driver FUSE → demone FUSE → VFS → ext4 → cache pagina/spazio di archiviazione.

FUSE Passthrough FUSE

Figura 2. Richieste dello spazio utente FUSE

Richieste passthrough FUSE

La maggior parte delle autorizzazioni di accesso ai file viene controllata al momento dell'apertura del file, con controlli aggiuntivi delle autorizzazioni che si verificano durante la lettura e la scrittura del file. In alcuni casi, è possibile sapere al momento dell'apertura del file che l'app richiedente ha accesso completo al file richiesto, quindi il sistema non deve continuare a inoltrare le richieste di lettura e scrittura dal driver FUSE al daemon FUSE (in quanto questo sposterebbe solo i dati da un luogo all'altro).

Con il passthrough FUSE, il demone FUSE che gestisce una richiesta aperta può notificare al driver FUSE che l'operazione è consentita e che tutte le richieste di lettura e scrittura successive possono essere inoltrate direttamente al file system di livello inferiore. In questo modo si evita il sovraccarico dovuto all'attesa del daemon FUSE dello spazio utente di rispondere alle richieste del driver FUSE.

Di seguito è riportato un confronto tra le richieste FUSE e FUSE passthrough.

Confronto passthrough FUSE

Figura 3. Confronto tra richiesta FUSE e richiesta passthrough FUSE

Quando un'app esegue l'accesso al file system FUSE, si verificano le seguenti operazioni:

  1. Il driver FUSE gestisce e mette in coda la richiesta, poi la presenta al daemon FUSE che gestisce il file system FUSE tramite un'istanza di connessione specifica sul file /dev/fuse, che il daemon FUSE non può leggere.

  2. Quando il daemon FUSE riceve una richiesta di apertura di un file, decide se deve essere disponibile il passthrough di FUSE per quel determinato file. Se è disponibile, il daemon:

    1. Invia una notifica al driver FUSE in merito a questa richiesta.

    2. Consente il passthrough FUSE per il file utilizzando l'ioctl FUSE_DEV_IOC_PASSTHROUGH_OPEN, che deve essere eseguito sul descrittore file dell'/dev/fuse aperto.

  3. ioctl riceve (come parametro) una struttura di dati contenente quanto segue:

    • Descrittore del file del file system di livello inferiore che è la destinazione della funzionalità di passaggio.

    • Identificatore univoco della richiesta FUSE attualmente gestita (deve essere aperta o creata e aperta).

    • Campi aggiuntivi che possono essere lasciati vuoti e sono destinati a implementazioni future.

  4. Se l'ioctl ha esito positivo, il daemon FUSE completa la richiesta di apertura, il driver FUSE gestisce la risposta del daemon FUSE e un riferimento al file del sistema di file inferiore viene aggiunto al file FUSE all'interno del kernel. Quando un'app richiede un'operazione di lettura/scrittura su un file FUSE, il driver FUSE controlla se è disponibile il riferimento a un file system inferiore.

    • Se è disponibile un riferimento, il driver crea una nuova richiesta del file system virtuale (VFS) con gli stessi parametri che hanno come target il file del file system inferiore.

    • Se non è disponibile un riferimento, il driver inoltra la richiesta al daemon FUSE.

Le operazioni precedenti si verificano per le operazioni di lettura/scrittura e di lettura/iterazione di scrittura su file generici e per le operazioni di lettura/scrittura su file con mappatura in memoria. Il passthrough FUSE per un determinato file esiste fino alla chiusura del file.

Implementare il passthrough FUSE

Per attivare il passthrough FUSE sui dispositivi con Android 12, aggiungi le seguenti righe al file $ANDROID_BUILD_TOP/device/…/device.mk del dispositivo di destinazione.

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

Per disattivare il passthrough FUSE, ometti la modifica di configurazione precedente o imposta persist.sys.fuse.passthrough.enable su false. Se in precedenza hai attivato il passthrough FUSE, la disattivazione impedisce al dispositivo di utilizzarlo, ma il dispositivo rimane funzionale.

Per attivare/disattivare il passthrough di FUSE senza eseguire il flashing del dispositivo, modifica la proprietà di sistema utilizzando i comandi ADB. Di seguito è riportato un esempio.

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

Per ulteriore assistenza, consulta l'implementazione di riferimento.

Convalida del passthrough di FUSE

Per verificare che MediaProvider utilizzi il passthrough FUSE, controlla logcat per i messaggi di debug. Ad esempio:

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 voce FuseDaemon: Using FUSE passthrough nel log garantisce che il passthrough FUSE sia in uso.

Il CTS di Android 12 include CtsStorageTest, che include i test che attivano il passthrough FUSE. Per eseguire il test manualmente, utilizza atest come mostrato di seguito:

atest CtsStorageTest