FUSE-Passthrough

Android 12 unterstützt FUSE-Passthrough, wodurch der FUSE-Aufwand minimiert und eine Leistung erzielt wird, die mit dem direkten Zugriff auf das untere Dateisystem vergleichbar ist. FUSE-Passthrough wird in den Kerneln android12-5.4, android12-5.10 und android-mainline (nur zum Testen) unterstützt. Das bedeutet, dass die Unterstützung für diese Funktion vom Kernel des Geräts und der Android-Version abhängt, auf der das 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 sie nicht zu einem Kernel wechseln 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 sie einen offiziellen Kernel verwenden. Bei solchen Geräten ist der Android Framework-Code, der FUSE-Passthrough implementiert, in das MediaProvider Mainline-Modul eingebettet, das automatisch aktualisiert wird. Geräte, die MediaProvider nicht als Mainline-Modul implementieren (z. B. Android Go-Geräte), können auch auf MediaProvider-Änderungen zugreifen, da diese öffentlich freigegeben sind.

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 Standard lösung für die Speicheremulation gemacht. Im Rahmen dieser Änderung hat Android einen eigenen FUSE-Daemon implementiert, um Dateizugriffe abzufangen, zusätzliche Sicherheits- und Datenschutzfunktionen zu erzwingen und Dateien zur Laufzeit zu bearbeiten.

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

Um diese Einbußen zu minimieren, können Apps Splicing verwenden, um das Kop101ieren von Daten zu reduzieren, und die ContentProvider API nutzen, um direkten Zugriff auf Dateien des unteren Dateisystems zu erhalten. Auch mit diesen und anderen Optimierungenkann die Bandbreite bei Lese und Schreibvorgängen bei Verwendung von FUSE im Vergleich zum direkten Zugriff auf das untere Datei system – insbesondere bei zufälligen Lese vorgängen, bei denen kein Caching oder Read-Ahead helfen kann. Und Apps, die über den Legacy-Pfad /sdcard/ direkt auf den Speicher zugreifen, haben weiterhin spürbare Leistungseinbußen, insbesondere bei E/A-intensiven Vorgängen.

SDcardFS-Userspace-Anfragen

Mit SDcardFS können die Speicheremulation und die Berechtigungsprüfungen von FUSE beschleunigt werden, indem der Userspace-Aufruf 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-Userspace-Anfragen

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

FUSE-Passthrough

Abbildung 2 : FUSE-Userspace-Anfragen

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 ist beim Öffnen der Datei bekannt, dass die anfragende App vollen Zugriff auf die angeforderte Datei hat. Daher muss das System die Lese- und Schreibanfragen nicht weiter vom FUSE-Treiber an den FUSE-Daemon weiterleiten (da dadurch nur Daten von einem Ort an einen anderen verschoben würden).

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

Unten sehen 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 ein FUSE-Dateisystem zugreift, werden die folgenden Vorgänge ausgeführt:

  1. Der FUSE-Treiber verarbeitet und reiht die Anfrage in die Warteschlange ein und übergibt sie dann an den FUSE-Daemon, der dieses FUSE-Dateisystem über eine bestimmte Verbindungsinstanz in der Datei /dev/fuse verarbeitet. Der FUSE-Daemon ist blockiert, um diese Datei zu lesen.

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

    1. Er benachrichtigt den FUSE-Treiber über diese Anfrage.

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

  3. Der ioctl 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 ID 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 erfolgreich ist, schließt der FUSE-Daemon die Öffnungsanfrage ab, der FUSE-Treiber verarbeitet die Antwort des FUSE-Daemons und im Kernel wird dem FUSE-Dateisystem ein Verweis 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 des unteren Dateisystems verfügbar ist.

    • Wenn ein Verweis verfügbar ist, erstellt der Treiber eine neue VFS-Anfrage (Virtual File System) mit denselben Parametern, die auf die Datei des unteren Dateisystems abzielt.

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

Die oben genannten Vorgänge erfolgen für Lese-/Schreibvorgänge und Lese-/Schreibvorgänge mit Iteration 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 diese Datei geschlossen wird.

FUSE-Passthrough implementieren

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

# 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 die Deaktivierung verhindert, dass das Gerät FUSE-Passthrough verwendet. Das Gerät bleibt aber funktionsfähig.

Wenn Sie FUSE-Passthrough 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 Referenz implementierung.

FUSE-Passthrough validieren

Wenn Sie prüfen möchten, ob MediaProvider FUSE-Passthrough verwendet, suchen Sie in logcat nach Debugging-Nachrichten. 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 zeigt an, dass FUSE-Passthrough verwendet wird.

Das Android 12 CTS enthält CtsStorageTest mit Tests, die FUSE-Passthrough auslösen. Wenn Sie den Test manuell ausführen möchten, verwenden Sie atest wie unten gezeigt:

atest CtsStorageTest