FUSE文件系统简介
Fuse(filesystem in userspace),是一个用户空间的文件系统。通过fuse内核模块的支持,开发者只需要根据fuse提供的接口实现具体的文件操作就可以实现一个文件系统。由于其主要实现代码位于用户空间中,而不需要重新编译内核,这给开发者带来了众多便利。
- Fuse包含一个内核模块和一个用户空间守护进程(下文称fuse daemon)。
- 内核模块加载时被注册成 Linux 虚拟文件系统的一个 fuse 文件系统驱动。
- 此外,还注册了一个/dev/fuse的块设备。该块设备作为fuse daemon与内核通信的桥梁,fuse daemon通过/dev/fuse读取fuse request,处理后将reply写入/dev/fuse。
上图详细展示了fuse的构架。当application挂在fuse文件系统上,并且执行一些系统调用时,VFS会将这些操作路由至fuse driver,fuse driver创建了一个fuse request结构体,并把request保存在请求队列中。此时,执行操作的进程会被阻塞,同时fuse daemon通过读取/dev/fuse将request从内核队列中取出,并且提交操作到底层文件系统中(例如 EXT4 或 F2FS)。当处理完请求后,fuse daemon会将reply写回/dev/fuse,fuse driver此时把requset标记为completed,最终唤醒用户进程。
FUSE文件在Android的历史
现在," sdcard"被视为虚拟文件系统,这意味着它可以被格式化为Google想要的任何文件系统。从Nexus S和Android 2.3开始,Google选择将" sdcard"格式化为VFAT(虚拟FAT)。当时这样做很有意义,因为安装VFAT将使几乎所有计算机都可以访问手机中存储的数据。但是,此初始实施存在两个主要问题。
第一个主要涉及最终用户。为了将设备连接到计算机,您将使用USB Mass Storage Mode传输数据。但是,这要求Android设备先卸载虚拟分区,然后计算机才能访问数据。如果用户想在插入电源的情况下使用其设备,则许多东西将显示为不可用。
MTP的引入解决了第一个问题。插入电源后,计算机会将您的设备视为"媒体存储"设备。它从您的手机请求文件列表,并且MTP返回计算机可以从设备下载的文件列表。当请求删除文件时,MTP发送命令从存储中删除请求的文件。与实际安装" sdcard"的USB大容量存储模式不同,MTP允许用户在插入电源后继续使用其设备。此外,Android手机上的文件系统对于计算机识别设备上的文件不再重要。
其次,事实是VFAT没有提供Google所需的那种强大的权限管理。早期,许多应用程序开发人员会将" sdcard"视为其应用程序数据的转储场,而对存储文件的位置没有统一的认识。许多应用程序会简单地使用应用程序名称创建一个文件夹并将其文件存储在该文件夹中。
当时几乎所有的应用程序都需要WRITE_EXTERNAL_STORAGE权限才能将其应用程序文件写入外部存储。但是,更麻烦的是,几乎每个应用程序都还需要READ_EXTERNAL_STORAGE权限-仅读取它们自己的数据文件!这意味着应用程序可以轻松访问存储在外部存储中任何位置的数据,并且这种权限通常由用户授予,因为许多应用程序都需要它才能正常运行。
Google显然认为这是有问题的。权限管理的整个思想是隔离应用程序可以访问和不能访问的内容。如果几乎每个应用程序都被授予对潜在敏感用户数据的读取访问权限,则该权限毫无意义。因此,谷歌认为他们需要一种新的方法。因此FUSE coming.在andorid4.4 中引入了 FUSE。
Google开始使用FUSE在" sdcard"虚拟分区上模拟FAT32。通过sdcard程序调用FUSE以模拟FAT-on-sdcard样式的目录权限,应用程序可以开始访问其存储在外部存储中的数据,而无需任何权限。实际上,从API级别19开始,不再需要READ_EXTERNAL_STORAGE来访问位于外部存储器上的文件-只要FUSE守护程序创建的数据文件夹与应用程序的软件包名称匹配即可。安装应用程序时, FUSE可以处理外部存储上文件的所有者,组和模式。
FUSE与内核模块不同,因为它允许非特权用户编写虚拟文件系统。Google实施FUSE的原因很简单-它做了他们想要的事情,但是,很明显,FUSE的开销正在导致性能下降等问题。
然而由于大量的性能问题,在Android O上,FUSE替换为" SDCardFS "。SDCardFS 是三星提出并开发的,其SDCardFS基于WrapFS。此内核解决方案像FUSE一样模拟FAT32,但是减少了I / O开销,双重缓存以及提到的其他问题。
实现内核内解决方案所面临的最大挑战是如何将包名称映射到应用程序ID,这对于应用程序包在不需要任何权限的情况下访问外部存储中自己的数据是必需的。
用SDCardFS 替换FUSE将减少大量的I / O开销,消除双重缓存,并解决一些与其FUSE仿真FAT32有关的晦涩问题
然而在android 11 上为了更好的权限控制,为了更好的支持 Scoped Storage Android 11 又用FUSE 替换了SDCardFS。只是有了一些新的变化。
应用权限管理在Android中的实现
Android对于外置存储的管理,需要实现以下几个目标:
1、读写外置存储需要 android.permission.READ_EXTERNAL_STORAGE和android.permission.WRITE_EXTERNAL_STORAGE, 这两个权限是运行时权限,可以动态的授予和撤销, 所以主存储目录的权限管理需要动态支持。
2、主存储目录下的${userid}/Android/obb, ${userid}/Android/data, ${userid}/Android/media 下的应用程序包名目录不需要读写存储卡权限。比如 com.androiud.ss.ugc.aweme这个应用程序读写 ${userid}/Android/data/com.androiud.ss.ugc.aweme 目录是不需要申请权限的。
但是不同应用对应的{userid}/Android/data/{package} 目录是权限隔离的,不能相互访问的。
3、除{userid}/Android/obb/{package}目录外,相同应用程序(相同包名)不同用户( {userid} )的目录也是权限隔离的。但是{userid}/Android/obb/${package}是跨用户( ${userid} ) 共享的。
要实现上述目标,使用传统文件系统是比较困难的,要使用十分复杂的组管理才能达到目标。为了实现更灵活的权限管理能力,Android引入了fuse文件系统。
1、为了实现动态的外置存储权限,Android会挂载三个目录到/dev/fuse来对应同一个外置存储,三个目录分别是/mnt/runtime/default/{label}, /mnt/runtime/read/{label}, /mnt/runtime/write/{label}, 这三个目录代表不同的权限,当一个应用进程取得了读外置存储的权限,那么它将使用 /mnt/runtime/read/{label} 目录来操作外置存储,当一个应用程序取得了写外置存储的权限,那么它将使用/mnt/runtime/write/{label}目录来操作外置存储。当一个应用程序没有获取操作外置存储的权限,将使用/mnt/runtime/default/{label}目录来操作主存储。三个fuse目录最终都会作用于外置存储的media目录,只不过对目录下的可进行的操作权限是不同的。Android 的FUSE file-system daemon会根据应用程序进程使用的fuse目录来决定是否可以读写外置存储的media目录下的数据。不过在Android 11里面这个路径方面有变化,变成了/mnt/user/0/emulated, /mnt/installer/0/emulated, /mnt/androidwritable/0/emulated, /storage/emulated。
2、${userid}/Android/obb, ${userid}/Android/data, {userid}/Android/media 下的{package} 权限管理则根据/data/system/packages.list文件中的内容来完成。 如果一个应用程序操作fuse目录,FUSE file-system daemon处理文件请求的时候可以获取操作文件的进程的uid,并根据/data/system/packages.list下的内容找到uid对应的包名,如果进程操作的报名和uid相对应,则允许操作,否则拒绝操作。
3、不同userid对应的相同应用的uid不同,根据 2中规则,即可实现 相同应用程序(相同包名)不同用户( ${userid} )的目录的权限隔离。
Sdcardfs和Fuse的共存
在Android O之前的Fuse和Android 11的Fuse并不完全一样,在Android O之前的Fuse和Sdcardfs是一个二选一的关系,即/sdcard或者是fuse或者是sdcardfs。而在Android 11里面则是sdcardfs和fuse同时存在,这主要就是因为fuse的性能相比sdcardfs还是差很多,因此在Android 11里面Google为了兼顾权限控制和IO性能,同时使用了sdcardfs和fuse文件系统,究竟在哪里使用fuse和sdcardfs,则是通过访问的路径来自动区分的。以Darwin手机为例,
Android 10的路径
bash
/data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /storage/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/runtime/full/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,unshared_obb)
Fuse文件系统路径
bash
/dev/fuse on /mnt/user/0/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
/dev/fuse on /mnt/installer/0/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
/dev/fuse on /mnt/androidwritable/0/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
/dev/fuse on /storage/emulated type fuse (rw,lazytime,nosuid,nodev,noexec,noatime,user_id=0,group_id=0,allow_other)
sdcardfs文件系统路径
bash
/data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/runtime/full/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/pass_through/0/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/user/0/emulated/0/Android/data type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /storage/emulated/0/Android/data type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/androidwritable/0/emulated/0/Android/data type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/installer/0/emulated/0/Android/data type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/user/0/emulated/0/Android/obb type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /storage/emulated/0/Android/obb type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
/data/media on /mnt/androidwritable/0/emulated/0/Android/obb type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal,unshared_obb)
索引
参考文献
Android 11 快来了IO 性能下降了 SDCardFS Vs FUSE - 程序猿
Android存储系统-使用fuse来管理外置存储_woai110120130的专栏-CSDN博客_android fuse