Android是基于Linux内核开发的,所以它的文件系统也是跟Linux文件系统类似。
首先我们来看Android存储的分类。
一般的Android手机都有2个存储卡,一个内置到手机里的,不可更换,叫做内置存储卡;另外一个可以通过扩展卡槽添加一个SD卡,叫做外置SD卡。内置存储卡和外置SD卡,它们是从物理上来进行区分的,一个内置到设备,另一个是添加的扩展卡。
对于Android系统来说,存储只分为内部存储和外部存储两类。内部存储是在应用的安装目录下(data目录),外部存储(通常是sdcard目录)在应用的安装目录外,它们是以目录为基准划分的。我们不要和内置存储卡和外置SD卡的概念混淆了,一个是逻辑上的划分,另一个是物理上的划分。
我们在进行App开发时,通常需要对App的存储权限做一些处理:
我们已经了解了存储分为内部存储和外部存储,接下来我们来分析外部存储是如何被挂载到系统的。
sdcard是我们常说的sd卡根目录,我们以它为例,来分析外部存储是如何挂载到系统的。
我们来看sdcard的路径链接关系:
/sdcard -> /storage/self/primary
/mnt/sdcard -> /storage/self/primary
/storage/self/primary -> /mnt/user/0/primary
/mnt/runtime/default/self/primary -> /mnt/user/0/primary
/mnt/user/0/primary -> /storage/emulated/0
Android6.0 引入了一个新的应用权限模型,该模型将标记为危险的权限从安装时权限模型移动到运行时权限模型。新模型包含了READ/WRITE_EXTERNAL_STORAGE这两个存储权限,因此Android系统需要在不杀死或者重启运行中的应用的前提下,需要动态对存储访问进行授权。
动态对存储访问授权,是通过维护所有挂载的存储设备的三个不同视图来实现的:
/mnt/runtime/default:对所有的应用、root命名空间可见,而无需任何权限.
/mnt/runtime/read:对有READ_EXTERNAL_STORAGE权限的应用可见。
/mnt/runtime/write:对有WRITE_EXTERNAL_STORAGE权限的应用可见。
出现这三个不同权限目录的原因是控制不同权限app访问。然后利用挂载命名空间实现了挂载点的隔离,在不同挂载命名空间的进程,看到的目录层次不同。每个app都根据自己的授权,选择了不同权限的runtime目录进行访问,而不同用户访问的目录也根据当前用户的id区分开了。
具体的实现是:zygote fork进程时,我们为每个运行中的应用创建一个mount命名空间,并且bind mount合适的初始视图。当被授予运行时权限时,vold(vold是Andorid系统的设备管理器,Android系统通过vold层完成磁盘的热插拔功能)在运行中的应用的名命名空间上,通过bind mount来更新视图。注意,如果权限被撤销,将意味着该应用被kill。系统使用setns()函数来实现上述特性,这要求Linux3.8,不过Linux3.4加上补丁上也可以支持该功能。
/mnt/runtime/default在开机执行init.rc时,挂载到storage下:
# Mount default storage into root namespace
mount none /mnt/runtime/default /storage bind rec
并且在执行init.rc脚本时进行了软链接:
# Symlink to keep legacy apps working in multi-user world
symlink /storage/self/primary /sdcard
symlink /storage/self/primary /mnt/sdcard
symlink /mnt/user/0/primary /mnt/runtime/default/self/primary
上文中,我们知道Android文件系统用到了很多软链接,那么什么是软链接呢?链接又分为几种类型呢?
软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件,类似于windows中的快捷方式。链接文件甚至可以链接不存在的文件。
硬链接相当于一个灾备份,数据存放在两处,与复制不同的是两处之间存在同步机制,一处数据的改变会实时同步到另一处,另外一处数据如果被删除了,不会影响到另一处的数据.
硬链接,以文件副本的形式存在,但不占用实际空间。硬链接文件有两个限制:不允许给目录创建硬链接;只有在同一文件系统中的文件之间才能创建链接。
Android 8.0以上开始支持SDcardFS文件系统,它是由三星wrapfs改写而成。
早期的版本android系统使用的是fuse文件系统,用于控制不同APP对文件访问的权限。
Android将手机内置SD卡与userdata分区合并成为一个分区。userdata分区使用ext4文件系统存储数据,访问userdata分区是直接操作ext4文件系统,而访问内置SD卡,则是先访问fuse文件系统,然后再访问ext4文件系统。
fuse文件系统的基本方法是,创建fuse设备,并将fuse设备挂载到与内置SD卡目录关联的目录。那么,对内置SD卡的访问变成了先访问fuse文件系统,再访问ext4文件系统。fuse的内核部分创建了多个队列,其中包含一个pending队列和一个processing队列。每当有调用者对内置SD卡的系统调用时,fuse把文件访问路径转换为对ext4文件系统的操作路径,设置对应的操作码,并放入一个请求中。fuse在用户态有3个监控线程,循环地读取fuse设备。对fuse设备的读取操作在内核部分转换从pending队列读取请求,如果队列中没有请求,则对应的线程进入睡眠状态。监控线程读取到pending队列中的请求后,把请求转换为对ext4文件系统的系统调用操作。系统调用执行完成后,监控线程把执行结果写入到fuse设备,对fuse设备的写操作在内核部分转换为把结果放入processing队列。processing队列依次取出结果,返回给调用者。
fuse需要进行多次的用户态与内核态交互,这样就会造成切换开销。
sdcardfs的作用与fuse相同,也是用于控制文件访问的权限。sdcardfs的工作方式是把内置SD卡目录挂载到用于权限控制目录。对内置SD卡的系统调用,先经过sdcardfs,然后把访问路径改为ext4文件系统的真正路径,再到达ext4文件系统。ext4执行完以后,把结果返回给sdcardfs,再返回给调用者。