FUSE-Passthrough

Android 12 unterstützt FUSE-Passthrough, wodurch der FUSE-Overhead minimiert wird und eine Leistung erzielt wird, die mit dem direkten Zugriff auf das untergeordnete Dateisystem vergleichbar ist. FUSE-Passthrough wird in den Kerneln android12-5.4, android12-5.10 und android-mainline (nur zum Testen) unterstützt. Die Unterstützung für diese Funktion hängt also vom Kernel des Geräts und der Android-Version ab, die auf dem Gerät ausgeführt wird:

  • Geräte, die von Android 11 auf Android 12 aktualisiert werden, können FUSE-Passthrough nicht unterstützen, da die Kernel für diese Geräte eingefroren sind und nicht auf einen Kernel umgestellt werden können, der offiziell mit den FUSE-Passthrough-Änderungen aktualisiert wurde.

  • Geräte, die mit Android 12 auf den Markt kommen, können FUSE-Passthrough unterstützen, wenn ein offizieller Kernel verwendet wird. Bei solchen Geräten ist der Android-Framework-Code, der FUSE-Passthrough implementiert, in das Mainline-Modul MediaProvider eingebettet, das automatisch aktualisiert wird. Geräte, auf denen MediaProvider nicht als Mainline-Modul implementiert ist (z. B. Android Go-Geräte), können auch auf MediaProvider-Änderungen zugreifen, da diese öffentlich geteilt werden.

FUSE im Vergleich zu SDCardFS

File system in Userspace (FUSE) ist ein Mechanismus, mit dem Vorgänge, die in einem FUSE-Dateisystem ausgeführt werden, vom Kernel (FUSE-Treiber) an ein Userspace-Programm (FUSE-Daemon) ausgelagert werden können, das die Vorgänge implementiert. In Android 11 wurde SDCardFS eingestellt und FUSE zur Standardlösung für die Speichermulation gemacht. Im Rahmen dieser Änderung hat Android einen eigenen FUSE-Daemon implementiert, um auf Dateizugriffe zu reagieren, zusätzliche Sicherheits- und Datenschutzfunktionen zu erzwingen und Dateien zur Laufzeit zu bearbeiten.

FUSE funktioniert gut bei der Verarbeitung von cachefähigen Informationen wie Seiten oder Attributen, führt aber zu Leistungseinbußen beim Zugriff auf externen Speicher, die besonders auf Geräten der Mittel- und Einstiegsklasse sichtbar sind. Diese Regressionen werden durch eine Kette von Komponenten verursacht, die bei der Implementierung des FUSE-Dateisystems zusammenarbeiten, sowie durch mehrere Wechsel vom Kernel- zum Userspace bei der Kommunikation zwischen dem FUSE-Treiber und dem FUSE-Daemon (im Vergleich zum direkten Zugriff auf das untergeordnete Dateisystem, das schlanker ist und vollständig im Kernel implementiert ist).

Um diese Regressionen zu minimieren, können Apps Splicing verwenden, um das Kopieren von Daten zu reduzieren, und die ContentProvider-API, um direkten Zugriff auf Dateien des unteren Dateisystems zu erhalten. Auch mit diesen und anderen Optimierungen kann die Bandbreite bei Lese- und Schreibvorgängen mit FUSE im Vergleich zum direkten Zugriff auf das untergeordnete Dateisystem reduziert sein – insbesondere bei zufälligen Lesevorgängen, bei denen kein Caching oder Read-Ahead helfen kann. Bei Apps, die über den alten Pfad /sdcard/ direkt auf den Speicher zugreifen, kommt es weiterhin zu spürbaren Leistungseinbußen, insbesondere bei E/A-intensiven Vorgängen.

SDcardFS-Userspace-Anfragen

Durch die Verwendung von SDcardFS können die Speichermulation und die Berechtigungsprüfungen von FUSE beschleunigt werden, da der Aufruf des Nutzerbereichs aus dem Kernel entfernt wird. Userspace-Anfragen folgen dem Pfad: Userspace → VFS → sdcardfs → VFS → ext4 → Seitencache/Speicher.

FUSE-Passthrough-SDcardFS

Abbildung 1: SDcardFS-Userspace-Anfragen

FUSE-Nutzerbereichsanfragen

FUSE wurde ursprünglich verwendet, um die Speichermediumsemulation zu ermöglichen und Apps die transparente Nutzung des internen Speichers oder einer externen SD-Karte zu erlauben. Die Verwendung von FUSE führt zu einem gewissen Mehraufwand, da jede Userspace-Anfrage dem folgenden Pfad folgt: Userspace → VFS → FUSE-Treiber → FUSE-Daemon → VFS → ext4 → Seitencache/Speicher.

FUSE-Passthrough

Abbildung 2: FUSE-Nutzerbereichsanfragen

FUSE-Passthrough-Anfragen

Die meisten Dateizugriffsberechtigungen werden beim Öffnen der Datei geprüft. Zusätzliche Berechtigungsprüfungen erfolgen beim Lesen und Schreiben in diese Datei. In einigen Fällen kann beim Öffnen der Datei festgestellt werden, dass die anfragende App vollen Zugriff auf die angeforderte Datei hat. Das System muss Lese- und Schreibanfragen dann nicht mehr vom FUSE-Treiber an den FUSE-Daemon weiterleiten, da dadurch nur Daten von einem Ort zum anderen verschoben würden.

Mit FUSE-Passthrough kann der FUSE-Daemon, der eine offene Anfrage verarbeitet, den FUSE-Treiber darüber informieren, dass der Vorgang zulässig ist und dass alle nachfolgenden Lese- und Schreibanfragen direkt an das untergeordnete Dateisystem weitergeleitet werden können. Dadurch wird der zusätzliche Aufwand vermieden, der durch das Warten auf die Antwort des FUSE-Daemons im Nutzerbereich auf die Anfragen des FUSE-Treibers entsteht.

Im Folgenden finden Sie einen Vergleich von FUSE- und FUSE-Passthrough-Anfragen.

FUSE-Passthrough-Vergleich

Abbildung 3: FUSE-Anfrage im Vergleich zu FUSE-Passthrough-Anfrage

Wenn eine App auf das FUSE-Dateisystem zugreift, werden die folgenden Vorgänge ausgeführt:

  1. Der FUSE-Treiber verarbeitet und stellt die Anfrage in die Warteschlange und übergibt sie dann an den FUSE-Daemon, der das FUSE-Dateisystem über eine bestimmte Verbindung auf der Datei /dev/fuse verarbeitet. Der FUSE-Daemon kann die Datei nicht lesen.

  2. Wenn der FUSE-Daemon eine Anfrage zum Öffnen einer Datei erhält, entscheidet er, ob FUSE-Passthrough für diese Datei verfügbar sein soll. Wenn sie verfügbar ist, führt der Daemon folgende Schritte aus:

    1. Benachrichtigt den FUSE-Treiber über diesen Request.

    2. Aktiviert den FUSE-Passthrough für die Datei mit dem ioctl FUSE_DEV_IOC_PASSTHROUGH_OPEN, der für den Dateideskriptor der geöffneten /dev/fuse ausgeführt werden muss.

  3. Der ioctl-Befehl empfängt (als Parameter) eine Datenstruktur, die Folgendes enthält:

    • Dateideskriptor der Datei des unteren Dateisystems, die das Ziel für die Passthrough-Funktion ist.

    • Eindeutige Kennung der FUSE-Anfrage, die gerade verarbeitet wird (muss „open“ oder „create-and-open“ sein).

    • Zusätzliche Felder, die leer gelassen werden können und für zukünftige Implementierungen vorgesehen sind.

  4. Wenn der ioctl-Aufruf erfolgreich ist, schließt der FUSE-Daemon die Open-Anfrage ab, der FUSE-Treiber verarbeitet die Antwort des FUSE-Daemons und dem FUSE-Dateisystem wird im Kernel eine Referenz auf die Datei des unteren Dateisystems hinzugefügt. Wenn eine App einen Lese-/Schreibvorgang für eine FUSE-Datei anfordert, prüft der FUSE-Treiber, ob der Verweis auf eine Datei im unteren Dateisystem verfügbar ist.

    • Wenn eine Referenz verfügbar ist, erstellt der Treiber eine neue VFS-Anfrage (Virtual File System) mit denselben Parametern, die auf die Datei im unteren Dateisystem ausgerichtet ist.

    • Wenn keine Referenz verfügbar ist, leitet der Treiber die Anfrage an den FUSE-Daemon weiter.

Die oben genannten Vorgänge erfolgen für Lese-/Schreib- und Lese-Iter-/Schreib-Iter-Vorgänge für generische Dateien sowie für Lese-/Schreibvorgänge für speicherabgebildete Dateien. FUSE-Passthrough für eine bestimmte Datei ist so lange vorhanden, bis die Datei geschlossen wird.

FUSE-Passthrough implementieren

Wenn Sie FUSE-Passthrough auf Geräten mit Android 12 aktivieren möchten, fügen Sie der Datei $ANDROID_BUILD_TOP/device/…/device.mk des Zielgeräts die folgenden Zeilen hinzu.

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

Wenn Sie FUSE-Passthrough deaktivieren möchten, lassen Sie die oben genannte Konfigurationsänderung weg oder legen Sie persist.sys.fuse.passthrough.enable auf false fest. Wenn Sie FUSE-Passthrough zuvor aktiviert haben, wird es durch Deaktivieren verhindert, dass das Gerät FUSE-Passthrough verwendet. Das Gerät bleibt jedoch funktionsfähig.

Wenn Sie die FUSE-Passthrough-Funktion aktivieren oder deaktivieren möchten, ohne das Gerät zu flashen, ändern Sie die Systemeigenschaft mit ADB-Befehlen. Hier ein Beispiel:

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

Weitere Informationen finden Sie in der Referenzimplementierung.

FUSE-Passthrough validieren

Wenn Sie prüfen möchten, ob MediaProvider FUSE-Passthrough verwendet, suchen Sie in logcat nach Debugging-Meldungen. Beispiel:

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

Der Eintrag FuseDaemon: Using FUSE passthrough im Log bestätigt, dass FUSE-Passthrough verwendet wird.

Das Android 12-CTS umfasst CtsStorageTest, das Tests enthält, die FUSE-Passthrough auslösen. So führen Sie den Test manuell aus:

atest CtsStorageTest