FUSE 直通

Android 12 支持 FUSE 直通,此功能可最大限度地减少 FUSE 开销,从而实现与直接访问较低层文件系统相当的性能。android12-5.4android12-5.10android-mainline(仅限测试)内核支持 FUSE 直通,这意味着对此功能的支持取决于设备使用的内核以及设备运行的 Android 版本

  • 从 Android 11 升级到 Android 12 的设备无法支持 FUSE 直通,因为这些设备的内核已冻结,并且无法迁移到已通过 FUSE 直通变更正式升级的内核。

  • 使用官方内核启动 Android 12 的设备可以支持 FUSE 直通。对于此类设备,实现 FUSE 直通的 Android 框架代码嵌入在 MediaProvider 主线模块中,该模块会自动升级。未将 MediaProvider 实现为主线模块的设备(例如,Android Go 设备)也可以访问公开共享的 MediaProvider 变更。

FUSE 与 SDCardFS

Userspace 文件系统 (FUSE) 是一种机制,允许内核(FUSE 驱动程序)将对 FUSE 文件系统执行的操作外包给用户空间程序(FUSE 守护进程),后者负责实现这些操作。Android 11 弃用了 SDCardFS,并将 FUSE 作为存储模拟的默认解决方案。作为此变更的一部分,Android 实现了自己的 FUSE 守护进程,用于拦截文件访问、强制实施额外的安全和隐私功能以及在运行时操控文件。

虽然 FUSE 在处理可缓存信息(例如页面或属性)时性能良好,但在访问外部存储时会引入性能衰退,这在低端和中端设备中尤为明显。这些性能衰退是由一组协同工作的组件(在 FUSE 文件系统的实现中)以及 FUSE 驱动程序与 FUSE 守护进程之间通信的多个内核空间到用户空间切换造成的(与直接访问更精简且完全在内核中实现的较低层文件系统相比)。

为了缓解这些性能衰退,应用可以使用 拼接来减少数据复制,并使用 ContentProvider API 来直接访问较低层文件系统文件。即使有了这些优化和其他优化,与直接访问较低层文件系统相比,在使用 FUSE 时,读取和写入操作仍可能会看到带宽降低,尤其是在随机读取操作中,缓存或预读都无济于事。而通过旧版 /sdcard/ 路径直接访问存储的应用会继续遇到明显的性能下降,尤其是在执行 IO 密集型操作时。

SDcardFS 用户空间请求

使用 SDcardFS 可以通过从内核中移除用户空间调用来加快 FUSE 的存储模拟和权限检查。用户空间请求遵循以下路径:用户空间 → VFS → sdcardfs → VFS → ext4 → 页面缓存/存储。

FUSE Passthrough SDcardFS

图 1. SDcardFS 用户空间请求

FUSE 用户空间请求

FUSE 最初用于启用存储模拟,并允许应用透明地使用内部存储设备或外部 SD 卡。使用 FUSE 会引入一些开销,因为每个用户空间请求都遵循以下路径:用户空间 → VFS → FUSE 驱动程序 → FUSE 守护进程 → VFS → ext4 → 页面缓存/存储。

FUSE Passthrough FUSE

图 2. FUSE 用户空间请求

FUSE 直通请求

大多数文件访问权限在文件打开时进行检查,并在从该文件读取和写入时进行额外的权限检查。在某些情况下,可以在文件打开时知道请求的应用具有对所请求文件的完全访问权限,因此系统无需继续将读取和写入请求从 FUSE 驱动程序转发到 FUSE 守护程序(因为这只会将数据从一个位置移动到另一个位置)。

通过 FUSE 直通,处理打开请求的 FUSE 守护程序可以通知 FUSE 驱动程序该操作是允许的,并且所有后续的读取和写入请求都可以直接转发到较低的文件系统。这避免了等待用户空间 FUSE 守护程序回复 FUSE 驱动程序请求的额外开销。

下面显示了 FUSE 请求和 FUSE 直通请求的比较。

FUSE Passthrough Comparison

图 3. FUSE 请求与 FUSE 直通请求的比较

当应用执行 FUSE 文件系统访问时,会发生以下操作

  1. FUSE 驱动程序处理请求并将其排队,然后通过 /dev/fuse 文件上的特定连接实例将其呈现给处理该 FUSE 文件系统的 FUSE 守护程序,FUSE 守护程序被阻止从该文件读取。

  2. 当 FUSE 守护程序收到打开文件的请求时,它会决定是否应该为该特定文件提供 FUSE 直通。如果可用,守护程序会

    1. 通知 FUSE 驱动程序有关此请求。

    2. 使用 FUSE_DEV_IOC_PASSTHROUGH_OPEN ioctl 为文件启用 FUSE 直通,该 ioctl 必须在已打开的 /dev/fuse 的文件描述符上执行。

  3. ioctl 接收(作为参数)一个包含以下内容的数据结构

    • 作为直通功能目标的较低文件系统文件的文件描述符。

    • 当前正在处理的 FUSE 请求的唯一标识符(必须是打开或创建并打开)。

    • 可以留空且用于未来实现的额外字段。

  4. 如果 ioctl 成功,则 FUSE 守护程序完成打开请求,FUSE 驱动程序处理 FUSE 守护程序回复,并且对较低文件系统文件的引用被添加到内核中的 FUSE 文件中。当应用请求对 FUSE 文件执行读取/写入操作时,FUSE 驱动程序会检查对较低文件系统文件的引用是否可用。

    • 如果引用可用,驱动程序将使用相同的参数创建一个新的虚拟文件系统 (VFS) 请求,目标是较低的文件系统文件。

    • 如果引用不可用,驱动程序会将请求转发到 FUSE 守护程序。

上述操作发生在通用文件上的读取/写入和读取迭代/写入迭代以及内存映射文件上的读取/写入操作。给定文件的 FUSE 直通一直存在,直到该文件关闭。

实现 FUSE 直通

要在运行 Android 12 的设备上启用 FUSE 直通,请将以下行添加到目标设备的 $ANDROID_BUILD_TOP/device/…/device.mk 文件中。

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

要禁用 FUSE 直通,请省略上述配置更改或将 persist.sys.fuse.passthrough.enable 设置为 false。如果您之前已启用 FUSE 直通,则禁用它会阻止设备使用 FUSE 直通,但设备仍保持功能正常。

要在不刷机的情况下启用/禁用 FUSE 直通,请使用 ADB 命令更改系统属性。示例如下所示。

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

如需更多帮助,请参阅参考实现

验证 FUSE 直通

要验证 MediaProvider 是否正在使用 FUSE 直通,请检查 logcat 中的调试消息。例如

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

日志中的 FuseDaemon: Using FUSE passthrough 条目确保 FUSE 直通正在使用中。

Android 12 CTS 包括 CtsStorageTest,其中包括触发 FUSE 直通的测试。要手动运行测试,请使用 atest,如下所示

atest CtsStorageTest