Android 文件管理系列 (00) - 存储系统概述

随着时间的演变,Android 支持了各种不同类型的存储设备和存储功能。所有的 Android 版本都支持 portable 和 emulated 存储。可移动存储通常由物理设备提供,比如 SD 卡或 USB 设备,主要用来进行临时的数据传输/文件存储。可移动存储的物理设备可能会在 Android 设备内长期连接,但是并不是与设备本身捆绑而是可以移除的。从 Android 1.0 起 SD 卡就可被当作可移动存储使用,到了 Android 6.0 又增加了对于 USB 的支持。从 Android 3.0 起支持 emulated 存储,通过模拟层将一部分内部存储 (Internal Storage) 暴露出来。

Android 6.0 开始,Android 开始支持 Adoptable Storage, 同样由像 SD 卡和 USB 等物理设备提供,可以加密并格式化后当作 Internal Storage 使用。

这里主要通过对系统分区的挂载、外部分区挂载、SD 卡的挂载进行分析。

系列

Android 文件管理系列 (00) – 存储系统概述 [当前]
Android 文件管理系列 (01) – 获取挂载点和权限
Android 文件管理系列 (02) – FileProvider
Android 文件管理系列 (03) – 监控文件变化

系统分区

具体有哪些系统分区,不同机器可能会不同,不过有三个分区每台机器都会有: /system, /cache, /data

如果想知道还有哪些分区,可以连接 adb 并通过 cat /proc/partitions 命令查看。不过这个命令获得的结果是根据存储设备块划分的,如果要知道每个分区的具体作用,先用 ls -l /dev/block/platform/ 列出存储设备,然后找到对应存储设备,再使用 ls -l /dev/block/platform/<block_device_name>/by-name/ 获得每个分区的作用。

下面是我的一台刷了 TWRP 并使用 CM13 三星 Note 2 的示范:

127|shell@n7100:/ $ cat /proc/partitions
major minor  #blocks  name

 179        0   15388672 mmcblk0
 179        1       4096 mmcblk0p1
 179        2       4096 mmcblk0p2
 179        3      20480 mmcblk0p3
 179        4       4096 mmcblk0p4
 179        5       4096 mmcblk0p5
 179        6       4096 mmcblk0p6
 179        7       8192 mmcblk0p7
 179        8       8192 mmcblk0p8
 179        9       8192 mmcblk0p9
 179       10      90112 mmcblk0p10
 179       11       4096 mmcblk0p11
 179       12    1400832 mmcblk0p12
 179       13    2097152 mmcblk0p13
 179       14     573440 mmcblk0p14
 179       15       8192 mmcblk0p15
 259        0   11141120 mmcblk0p16
 179       16   31166976 mmcblk1
 179       17   31165952 mmcblk1p1
shell@n7100:/ $ ls -l /dev/block/platform/
total 0
drwxr-xr-x 4 root root 420 2016-08-13 13:45 dw_mmc
drwxr-xr-x 3 root root 100 2016-08-13 13:45 s3c-sdhci.2
shell@n7100:/ $ ls -l /dev/block/platform/dw_mmc/by-name/
total 0
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 BOOT -> /dev/block/mmcblk0p8
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 BOTA0 -> /dev/block/mmcblk0p1
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 BOTA1 -> /dev/block/mmcblk0p2
lrwxrwxrwx 1 root root 21 2016-08-13 13:45 CACHE -> /dev/block/mmcblk0p12
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 EFS -> /dev/block/mmcblk0p3
lrwxrwxrwx 1 root root 21 2016-08-13 13:45 HIDDEN -> /dev/block/mmcblk0p14
lrwxrwxrwx 1 root root 21 2016-08-13 13:45 OTA -> /dev/block/mmcblk0p15
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 PARAM -> /dev/block/mmcblk0p7
lrwxrwxrwx 1 root root 21 2016-08-13 13:45 RADIO -> /dev/block/mmcblk0p10
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 RECOVERY -> /dev/block/mmcblk0p9
lrwxrwxrwx 1 root root 21 2016-08-13 13:45 SYSTEM -> /dev/block/mmcblk0p13
lrwxrwxrwx 1 root root 21 2016-08-13 13:45 TOMBSTONES -> /dev/block/mmcblk0p11
lrwxrwxrwx 1 root root 21 2016-08-13 13:45 USERDATA -> /dev/block/mmcblk0p16
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 m9kefs1 -> /dev/block/mmcblk0p4
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 m9kefs2 -> /dev/block/mmcblk0p5
lrwxrwxrwx 1 root root 20 2016-08-13 13:45 m9kefs3 -> /dev/block/mmcblk0p6

具体每个分区的作用都可以查询到,这里就不一一解释了,只说三个主要系统分区的作用:

/system: 系统分区,这里是保存 Android 系统的地方,也就是所谓的 ROM 所在的位置。另外,系统更新的改动一般也是针对这个分区的。通常来说 /system 分区会被挂载为只读模式,只能被由官方发布的经由签名验证的更新包更新。

/cache: 缓存分区,用来保存缓存文件、临时文件的地方。需要注意的是,这里的 /cache 应用程序并没有办法直接访问,应用程序所用的缓存目录位于 /data 或这其他外部存储器上。

/data: 数据分区, 用户安装的应用,应用数据,系统应用的更新等等都放在这里。对于 API 10 (Honeycomb) 之后的系统,/data/media/0 目录会被模拟成内置 SD 卡 (通常称呼是 Internal Storage, 但是部分文档也称其为 External Storage)。 这个分区同样还保存有你的各类设置数据,比如 WIFI 密码等

系统分区挂载

Android 是基于 Linux 内核的系统,使用的也是 Linux 的文件系统挂载方式,由 init 进程负责挂载常用的系统分区,init 进程所需的指令保存在 /init.rc 文件中.
在 init.rc 中 mount_all /fstab.xxxxxx 指令完成挂载(根据机型不同,也有可能在init.xxx.rc 中)。

Note 2 上的 fstab.xxxx内容如下:

# Android fstab file.
#<src>                  <mnt_point>         <type>    <mnt_flags and options>                               <fs_mgr_flags>
# The filesystem that contains the filesystem checker binary (typically /system) cannot
# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK
# data partition must be located at the bottom for supporting device encryption

/dev/block/mmcblk0p3     /efs                ext4      noatime,nosuid,nodev,journal_async_commit,errors=panic    wait
/dev/block/mmcblk0p13    /system             ext4      ro,noatime                                                wait
/dev/block/mmcblk0p12    /cache              ext4      noatime,nosuid,nodev,journal_async_commit,errors=panic    wait,check
/dev/block/mmcblk0p14    /preload            ext4      noatime,nosuid,nodev,journal_async_commit                 wait
/dev/block/mmcblk0p16    /data               ext4      noatime,nosuid,nodev,discard,noauto_da_alloc,journal_async_commit,errors=panic    wait,check,encryptable=footer

# Removable storage
/devices/platform/s3c-sdhci.2/mmc_host/mmc1*     /storage/sdcard1    auto      defaults      voldmanaged=sdcard1:auto,encryptable=userdata
/devices/platform/s5p-ehci*                      /storage/usbdisk0    auto      defaults      voldmanaged=usb:auto,noemulatedsd,encryptable=userdata

# recovery
/dev/block/mmcblk0p8            /boot                           emmc            defaults            recoveryonly
/dev/block/mmcblk0p9            /recovery                       emmc            defaults            recoveryonly

结果分为5列:

  • <src>: 挂载分区路径或者设备路径
  • <mnt_point>: 挂载点
  • <type>: 类型
  • <mnt_flags and options>: 挂在参数
  • <fs_mgr_flags>: 文件系统管理标识, voldmanaged 表示可被 vold 管理,而 encryptable 表示可加密

mount 分区时,标记有 voldmanaged 则会被跳过,在接下来交由 vold 管理,在上面的 fstab 中为 # Removable storage 下的两个设备。

外置分区

Multiple external storage devices – 多外部存储

在 API 19 (KitKat) 之前的 Android 版本会给应用程序单独分出一块外部存储空间(external storage),这块存储空间可能在 sdcard (可插拔的外置sdcard)上,也可能在仅仅是在设备内部的闪存上, 需要声明 WRITE_EXTERNAL_STORAGE 权限才能对其进行访问,读取没有做出限制。
从 API 19 (KitKat) 开始,进行读取时需要 READ_EXTERNAL_STORAGE 权限,而访问应用所属的 private 目录内容 (如:Android/data/<package-name>/) 则不需要任何权限的。

API 19 (KitKat) 中,开始支持多个外部存储,外部存储被分为了一个主要的 primary 和多个次要的 secondary。这些外部存储的位置可以在应用中由 Context.getExternalFilesDirs(),Context.getExternalCacheDirs(),Context.getObbDirs() 获取。
只有半永久的存储设备 (如电池盖下插入的 SD card) 才应该通过这几个 API 获得,存储在这些设备上的数据应该在较长时间段内保持有效。因此,临时连接的存储设备,如 USB 大容量存储设备并不能通过这些 API 进行操作。

WRITE_EXTERNAL_STORAGE 权限对 primary external storage 有效,对于 secondary external storage,除了能够写入他们自己对应包名的目录 (/Android/data/<package-name>/),其他目录不允许写入权限。
自 API 21 (Lollipop) 开始,新引入了 Storage Access Framework

Adoptable Storage

Android 6.0 版本之前支持的那种拥有不区分大小写的文件系统的、不可修改 POSIX 权限的存储系统被叫做 Traditional Storage, 由于传统的外部存储缺乏对于数据保护的支持,因此系统并不能将敏感数据保存在外部存储之上,例如配置文件和日志文件都只保存在能够被有效保护的内部存储内。当外部存储设备采用 Adoptable 方式连接到系统时,这个设备将被格式化并加密,这样这个设备在这种情况下只能在这一台 Android 设备上使用。由于这个存储设备与 Android 系统紧密的连接在了一起,它便能够安全地存储应用和应用数据了。

当用户插入一个新的存储设备时 (比如 SD 卡), Android 会询问用户如何使用这个存储设备,用户可以选择将其作为 Internal Storage 或者仍然按照传统方式使用。如果选择了按照 Internal Storage 使用,其实就是采用了 Adoptable Storage, 存储设备将被格式化并加密,并会将之前主存储上的内容迁移过去 (即之前的 Internal Storage), 然后释放原 Internal Storage 上的空间。传统存储方式分区采用 MBR, 而 Adoptable Storage 采用了 GPT.

FUSE

API 10 (Honeycomb) 之前,sdcard0 的挂载与其他分区一样,并使用了 FAT32 文件系统。这虽然使通过USB链接PC 传输文件更为方便,但是存在几个问题:

  1. 不能同时在两个设备上挂载分区,因为没有相关的文件锁机制。因此,直到 API 9 (Gingerbread), 当需要将 SD 内容连接到 PC (或者其他支持 USB 连接的设备) 时都会先在手机上取消挂载,然后在 PC 上挂载。
  2. 需要有 /data/sdcard0 两个分区,这样两个分区的大小需要平衡,因为应用安装在 /data 而用户数据 (音乐、照片等) 通常在 /sdcard
  3. FAT32 属于微软专利,可能存在许可和法律问题
  4. 实现了 Android 多用户分离存储的机制

为了解决上面几个问题,从 API 10 (Honeycomb) 开始 FUSE (Filesystem in Userspace)技术最终被采用,因为这已经是 Linux 内核的一部分了。FUSE 并不是一个具体的文件系统,而是提供了一套能够实现其他文件系统的基础。通过使用 FUSE, 有以下几个好处:

  1. 当其他设备需要使用 Android 上的文件时,并不需要首先从 Android 上取消挂载了,即使在传输文件等情况下也是可以正常使用手机
  2. 不在需要 /data/sdcard0 两个分区,只有一个 /data 分区,而将 /data/media 模拟为内置 SD 卡
  3. FUSE 可以隐藏实际的文件系统,例如通常情况下 /data 采用 ext4 文件系统,而将 /data/media 模拟为 SD 卡并连接计算机时,可以支持更为友好的 FAT32
  4. 通过 FUSE 可以实现比 owner/group/user 方式更为精细复杂的权限控制系统,FUSE 可以动态地接受/拒绝个别请求

FUSE 同时有一些缺点:

  1. 对于 MTP 连接需要在计算机端有相关的 MTP 兼容程序
  2. 性能有一定的损失

外置分区挂载

在系统分区挂载的部分可以看到, 标记有 voldmanaged 标识的设备和路径并未被挂载,这一部分通常即为 SD 卡和 USB 等可移除设备。这些可移除设备的挂载由 vold 和 MountService 完成。SD 卡插拔会被 kernel 监听到并发送 uevent 到用户空间。此时 vold daemon 进程会收到从 kernel 发出的 uevent 事件,检查和处理后并上报到 MountService 中,MountSerivce 和上层其他模块交互确认然后通过 socket 发送指令给 vold 完成具体的磁盘 Mount 和 Unmount 操作。
具体的流程分析仍然需要参考源代码,这里就不做具体分析了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注