Android 12 支持 FUSE 直通,将 FUSE 开销降至最低,从而实现与直接访问较低文件系统相媲美的性能。 FUSE passthrough 在android12-5.4
、 android12-5.10
和android-mainline
(testing only) 内核中受支持,这意味着对该功能的支持取决于设备使用的内核和设备运行的 Android 版本:
从 Android 11 升级到 Android 12 的设备无法支持 FUSE 直通,因为这些设备的内核已冻结,它们无法迁移到已通过 FUSE 直通更改正式升级的内核。
搭载 Android 12 的设备在使用官方内核时可以支持 FUSE 直通。对于此类设备,实现 FUSE 透传的 Android 框架代码嵌入在MediaProvider主线模块中,该模块会自动升级。未将 MediaProvider 实现为主线模块的设备(例如,Android Go 设备)也可以访问 MediaProvider 更改,因为它们是公开共享的。
FUSE 与 SDCardFS
用户空间中的文件系统 (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 → 页面缓存/存储。
图 1. SDcardFS 用户空间请求
FUSE 用户空间请求
FUSE 最初用于启用存储仿真并允许应用程序透明地使用内部存储或外部 sdcard。使用 FUSE 会带来一些开销,因为每个用户空间请求都遵循以下路径:用户空间 → VFS → FUSE 驱动程序 → FUSE 守护进程 → VFS → ext4 → 页面缓存/存储。
图 2. FUSE 用户空间请求
FUSE 直通请求 {#fuse-passthrough-requests}
大多数文件访问权限在文件打开时检查,在读取和写入该文件时会进行额外的权限检查。在某些情况下,可以在文件打开时知道请求应用程序对请求的文件具有完全访问权限,因此系统不需要继续将 FUSE 驱动程序的读取和写入请求转发到 FUSE 守护程序(因为只会将数据从一个地方移动到另一个地方)。
使用 FUSE passthrough,处理打开请求的 FUSE 守护进程可以通知 FUSE 驱动程序允许该操作,并且所有后续的读写请求都可以直接转发到较低的文件系统。这避免了等待用户空间 FUSE 守护程序回复 FUSE 驱动程序请求的额外开销。
FUSE 和 FUSE 直通请求的比较如下所示。
图 3. FUSE 请求与 FUSE 直通请求
当应用程序执行 FUSE 文件系统访问时,会发生以下操作:
FUSE 驱动程序处理请求并将其排入队列,然后通过
/dev/fuse
文件上的特定连接实例将其呈现给处理该 FUSE 文件系统的 FUSE 守护程序,FUSE 守护程序被阻止读取该文件。当 FUSE 守护进程收到打开文件的请求时,它决定 FUSE passthrough 是否应该可用于该特定文件。如果可用,则守护程序:
通知 FUSE 驱动程序有关此请求。
使用
FUSE_DEV_IOC_PASSTHROUGH_OPEN
ioctl 为文件启用 FUSE 直通,这必须在打开的/dev/fuse
的文件描述符上执行。
ioctl 接收(作为参数)包含以下内容的数据结构:
作为直通功能目标的较低文件系统文件的文件描述符。
当前正在处理的 FUSE 请求的唯一标识符(必须是 open 或 create-and-open)。
可以留空并用于未来实现的额外字段。
如果 ioctl 成功,FUSE 守护进程完成打开请求,FUSE 驱动程序处理 FUSE 守护进程回复,并在内核中的 FUSE 文件中添加对较低文件系统文件的引用。当应用程序请求对 FUSE 文件进行读/写操作时,FUSE 驱动程序会检查对较低文件系统文件的引用是否可用。
如果引用可用,驱动程序将创建一个新的虚拟文件系统 (VFS) 请求,该请求具有针对较低文件系统文件的相同参数。
如果引用不可用,驱动程序会将请求转发给 FUSE 守护程序。
上述操作发生在通用文件上的读/写和 read-iter/write-iter 以及内存映射文件上的读/写操作。给定文件的 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 passthrough 正在使用中。
Android 12 CTS 包括CtsStorageTest
,其中包括触发 FUSE 直通的测试。要手动运行测试,请使用 atest,如下所示:
atest CtsStorageTest