Android 12 supports FUSE passthrough, which minimizes
FUSE overhead to achieve performance comparable to direct access to the lower
file system. FUSE passthrough is supported in the android12-5.4
,
android12-5.10
, and android-mainline
(testing only) kernels, which means
that support for this feature depends on the kernel used by the device and the
version of Android the device is running:
Devices upgrading from Android 11 to Android 12 can't support FUSE passthrough as the kernels for these devices are frozen and they can't move to a kernel that's been officially upgraded with the FUSE passthrough changes.
Devices launching with Android 12 can support FUSE passthrough when using an official kernel. For such devices, the Android framework code that implements FUSE passthrough is embedded in the MediaProvider mainline module, which is automatically upgraded. Devices that don't implement MediaProvider as a mainline module (for example, Android Go devices), can also access MediaProvider changes as they are publicly shared.
FUSE versus SDCardFS
File system in Userspace (FUSE) is a mechanism that allows operations performed on a FUSE file system to be outsourced by the kernel (FUSE driver) to a userspace program (FUSE daemon), which implements the operations. Android 11 deprecated SDCardFS and made FUSE the default solution for storage emulation. As part of this change, Android implemented its own FUSE daemon to intercept file accesses, enforce extra security and privacy features, and manipulate files at runtime.
While FUSE performs well when dealing with cacheable information such as pages or attributes, it introduces performance regressions when accessing external storage that are especially visible in mid and low-end devices. These regressions are caused by a chain of components cooperating in the implementation of the FUSE file system, as well as multiple switches from kernel space to user space in communications between the FUSE driver and the FUSE daemon (as compared to direct access to the lower file system that is leaner and completely implemented in the kernel).
To mitigate these regressions, apps can use
splicing to
reduce data copying and use the ContentProvider
API
to get direct access to lower file system files. Even with these and other
optimizations, read
and write operations might see reduced bandwidth when using FUSE when compared
to direct access to the lower file
system — especially with random read
operations, where no caching or read-ahead can help. And apps that directly
access storage through the legacy /sdcard/
path continue to experience
noticeable performance drops, especially when performing IO-intensive
operations.
SDcardFS userspace requests
Using SDcardFS can speed up the storage emulation and permission checks of FUSE by removing the user space call from the kernel. Userspace requests follow the path: Userspace → VFS → sdcardfs → VFS → ext4 → Page cache/Storage.
Figure 1. SDcardFS userspace requests
FUSE userspace requests
FUSE was initially used to enable storage emulation and to allow apps to transparently use either the internal storage or an external sdcard. Using FUSE introduces some overhead because each userspace request follows the path: Userspace → VFS → FUSE driver → FUSE daemon → VFS → ext4 → Page cache/Storage.
Figure 2. FUSE userspace requests
FUSE passthrough requests
Most file access permissions are checked at file open time, with additional permissions checks occurring when reading from and writing to that file. In some cases, it's possible to know at file open time that the requesting app has full access to the requested file, so the system doesn't need to continue forwarding read and write the requests from the FUSE driver to the FUSE daemon (as that would only move data from one place to another).
With FUSE passthrough, the FUSE daemon handling an open request can notify the FUSE driver that the operation is allowed and that all subsequent read and write requests can be directly forwarded to the lower file system. This avoids the extra overhead of waiting for the user space FUSE daemon to reply to the FUSE driver requests.
A comparison of FUSE and FUSE passthrough requests is shown below.
Figure 3. FUSE request versus FUSE passthrough request
When an app performs a FUSE file system access, the following operations occur:
The FUSE driver handles and enqueues the request, then presents it to the FUSE daemon that handles that FUSE file system through a specific connection instance on the
/dev/fuse
file, which the FUSE daemon is blocked from reading.When the FUSE daemon receives a request to open a file, it decides whether FUSE passthrough should be available for that particular file. If it's available, the daemon:
Notifies the FUSE driver about this request.
Enables FUSE passthrough for the file using the
FUSE_DEV_IOC_PASSTHROUGH_OPEN
ioctl, which must be performed on the file descriptor of the opened/dev/fuse
.
The ioctl receives (as a parameter) a data structure that contains the following:
File descriptor of the lower file system file that's the target for the passthrough feature.
Unique identifier of the FUSE request that is currently being handled (must be open or create-and-open).
Extra fields that can be left empty and are meant for future implementations.
If the ioctl succeeds, the FUSE daemon completes the open request, the FUSE driver handles the FUSE daemon reply, and a reference to the lower file system file is added to the FUSE file within the kernel. When an app requests a read/write operation on a FUSE file, the FUSE driver checks if the reference to a lower file system file is available.
If a reference is available, the driver creates a new Virtual File System (VFS) request with the same parameters targeting the lower file system file.
If a reference isn't available, the driver forwards the request to the FUSE daemon.
The above operations occur for read/write and read-iter/write-iter on generic files and read/write operations on memory-mapped files. FUSE passthrough for a given file exists until that file is closed.
Implement FUSE passthrough
To enable FUSE passthrough on devices running Android
12, add the following lines to the
$ANDROID_BUILD_TOP/device/…/device.mk
file of the target device.
# Use FUSE passthrough
PRODUCT_PRODUCT_PROPERTIES += \
persist.sys.fuse.passthrough.enable=true
To disable FUSE passthrough, omit the above configuration change or set
persist.sys.fuse.passthrough.enable
to false
. If you've previously enabled
FUSE passthrough, disabling it prevents the device from using FUSE passthrough
but the device remains functional.
To enable/disable FUSE passthrough without flashing the device, change the system property using ADB commands. An example is shown below.
adb root
adb shell setprop persist.sys.fuse.passthrough.enable {true,false}
adb reboot
For additional help, refer to the reference implementation.
Validate FUSE passthrough
To validate that MediaProvider is using FUSE passthrough, check logcat
for
debugging messages. For example:
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...
The FuseDaemon: Using FUSE passthrough
entry in the log ensures that FUSE
passthrough is in use.
The Android 12 CTS includes CtsStorageTest
, which
includes tests that trigger FUSE passthrough. To run the test manually, use
atest as shown below:
atest CtsStorageTest