文件系统¶
第 4 章我们讨论了编译系统如何运作以及它生成什么。具体来说,表 4-3 提供了编译系统生成镜像的详细列表。而图 6-1 则展示了这些镜像在运行时的相互关系。除少数例外情况(将在后面介绍),这个文件系统布局在 2.3/Gingerbread 和 4.2/Jelly Bean 中基本相同。
要理解从编译系统生成的镜像到图 6-1 所示运行时配置的过程,需要回到第 2 章的系统启动说明,更具体地说是回看图 2-6 所示的启动流程。本质上,内核将编译系统生成的 RAM disk 镜像挂载为根文件系统,并启动该镜像中的 init 进程。init 进程的配置文件(将在本章后面介绍)会导致多个额外的镜像和虚拟文件系统被挂载到根文件系统中现有目录项上。

你可能会问的第一个问题是:"为什么需要这么多文件系统?"答案是:每个镜像有不同的用途,对应的存储设备或技术也各有不同。例如,RAM disk 镜像应尽可能小,它的唯一目的是提供使系统启动所需的初始骨架。它通常以压缩镜像的形式存储在某种介质上,由内核加载到 RAM 中,然后以只读方式挂载为根文件系统。
而 /cache、/data 和 /system 通常从实际存储介质的独立分区挂载。通常 /cache 和 /data 以读写方式挂载,而 /system 以只读方式挂载。
使用单一文件系统¶
实际上,你完全可以使用单一文件系统来存储 Android 所有编译输出,而不必使用独立的存储分区。德州仪器的 RowBoat 发行版就是这样做的。它生成单一的根文件系统镜像,直接编程到目标存储设备上使用。以 BeagleBone 或 BeagleBoard为例,根文件系统的全部内容被编程到 microSD 卡的单个分区中,用于启动和作为设备的主存储。
然而,通过合并为单一文件系统,意味着你假设可以一次性更新整个文件系统。总之,这将很难为系统创建安全的更新程序。在 RowBoat 对 Beagle 系列的支持中,这可能不是问题,因为它们是开发板,但在你的实际产品中,这很可能会成为问题。
在 Android 2.2 及更早版本中,这三个目录通常都从 YAFFS2 格式化的 NAND flash 分区挂载。由于手机制造商已逐步转向 eMMC 而非 NAND flash, YAFFS2 在 Google 的 Android 2.3 首发设备三星 Nexus S 中被 ext4 取代。此后,所有基于 Android 的手机都应使用 ext4 而非 YAFFS2。不过,你完全可以选择使用其他文件系统类型——只需修改编译系统的 makefile 来生成这些镜像,并更新 init 配置文件中的 mount 命令参数。
eMMC 与 NOR 或 NAND Flash¶
正如《构建嵌入式 Linux 系统》第 2 版所述,Linux 的 MTD 层用于在 Linux 中管理、操控和访问 flash 设备,包括 NOR 和 NAND flash。在此之上会使用各种文件系统(如 JFFS2、UBIFS 或 YAFFS2),使 flash 设备或分区成为 Linux 虚拟文件系统(VFS)的一部分。这些 flash 文件系统通常实现磨损均衡和坏块管理,以正确处理底层 flash 设备。
eMMC 设备(如第 5 章所述)表现为传统的块设备。本质上,它包含一个微控制器和一些 RAM,能够透明地完成磨损均衡和坏块管理。因此,操作系统可以使用普通的磁盘文件系统如 ext4。据 Android 开发者 Brian Swetland 称,转向 eMMC 的决定是由 PCB 上减少引脚数量从而降低成本驱动的——但使用这种设备还有一些额外的好处。
首先,它允许你使用与传统 Linux 文件系统相同的所有传统命令和方法。MTD 子系统虽然强大,但需要一段时间才能熟练使用。此外,flash 文件系统通常为单处理器系统设计,而 Linux 中的磁盘文件系统长期以来需要应对多处理器系统。因此,它们可能更适合即将到来的多核 Android 设备潮流。
SD 卡始终表现为块设备,通常在其上创建 VFAT 文件系统。这是可以预期的,因为用户需要能够将 SD 卡从 Android 设备上取出并插入普通电脑(无论运行什么操作系统)。/proc、/sys 和 /acct 分别通过 procfs、sysfs 和 cgroupfs 挂载。虽然 /proc 和 /sys 的挂载位置与传统 Linux 系统相同,但 cgroups 在传统 Linux 中通常挂载为 /cgroup,而在 Android 中挂载为 /acct。还要注意 /dev 以 tmpfs 方式挂载,这意味着它的内容是动态创建的,不驻留在任何永久存储上。这没有问题,因为 Android 依赖 Linux 的 udev 机制来动态创建设备节点——当设备插入和/或驱动程序加载或初始化时。
Procfs、sysfs、tmpfs 和 cgroupfs 都是由当前运行内核在系统中维护的虚拟文件系统。它们没有任何对应的存储,实际上是内核内部维护的数据结构。Procfs 是内核向用户空间导出信息的传统方式。通常,procfs 中的条目被视为文本文件,或包含文本文件的目录,可以转储到命令行以从内核提取特定信息。例如,如果你想知道系统运行的 CPU 类型,可以转储 /proc/cpuinfo 文件的内容。
随着内核成熟和需求增长,人们逐渐认为 procfs 并不一定是所有内核与用户空间之间接口的正确机制。于是 sysfs 出现了,它与内核的设备和硬件管理高度绑定。例如,sysfs 中的条目可用于获取外设的详细信息,或直接从用户空间切换控制某些驱动程序行为的位。例如,Android 的许多电源管理功能就是通过 /sys/power/ 目录中的条目来控制的。
Tmpfs 允许你创建虚拟的纯 RAM 文件系统来存储临时文件。只要 RAM 有电,内核就允许你读写这些文件。但重启后,所有内容都会消失。Cgroupfs 是内核相对较新的附加功能,用于管理 Linux 2.6.24 添加的控制组功能。总之,cgroups 允许你对某些进程及其子进程进行分组,并可对这些组设置资源限制和优先级。Android 使用 cgroups 来优先处理前台任务。
根目录¶
正如第 2 章讨论的,Linux 根文件系统的经典结构在文件系统层次标准(FHS)中指定。然而,Android 并不遵循 FHS,而是严重依赖 /system 和 /data 目录来承载大部分关键功能。
Android 的根目录由 AOSP 编译系统生成的 ramdisk.img 挂载。通常 ramdisk.img 会与内核一起存储在设备的主存储设备中,由引导程序在系统启动时加载。表 6-1 详细说明了挂载后根目录的内容。
表 6-1. Android 根目录
| 条目 | 类型 | 说明 |
|---|---|---|
| /acct | 目录 | cgroup 挂载点 |
| /cache | 目录 | 下载中和其他非必要数据的临时位置 |
| /d | 符号链接 | 指向 /sys/kernel/debug,是 debugfs 的典型挂载位置 |
| /data | 目录 | data 分区的挂载点。通常 data.img 的内容挂载在此 |
| /dev | 目录 | 以 tmpfs 挂载,包含 Android 使用的设备节点 |
| /etc | 符号链接 | 指向 /system/etc |
| /mnt | 目录 | 临时挂载点 |
| /proc | 目录 | procfs 的挂载点 |
| /root | 目录 | 在传统 Linux 系统中是 root 用户的主目录。在 Android 中通常为空 |
| /sbin | 目录 | 在 Linux 中包含系统管理员必备的二进制文件。在 Android 中只包含 ueventd 和 adbd |
| /sdcard | 目录 | SD 卡的挂载点 |
| /sys | 目录 | sysfs 的挂载点 |
| /system | 目录 | system 分区的挂载点。system.img 挂载到此位置 |
| /vendor | 符号链接 | 通常是 /system/vendor 的符号链接。并非所有设备都有 /system/vendor 目录 |
在 4.2/Jelly Bean 中,你还会在根文件系统中发现表 6-2 所列的更多条目。
表 6-2. 4.2/Jelly Bean 中 Android 根目录的添加
| 条目 | 类型 | 说明 |
|---|---|---|
| /config | 目录 | configfs 的挂载点 |
| /storage | 目录 | 从 4.1/Jelly Bean 起,此目录用于挂载外部存储。/storage/sdcard0 通常是伪"外部"存储,/storage/sdcard1 是真实的 SD 卡 |
| /charger | 文件 | 原生的独立全屏应用程序,显示电池充电状态 |
| /res | 目录 | charger 应用程序的资源 |
/system¶
如前所述,/system 包含由 AOSP 编译系统生成的所有不可变组件。为了进一步说明,图 6-2 将第 2 章中的 Android 架构图与文件系统中各部分的位置对应起来。

可以看到,一旦 system.img 被挂载,大部分组件都在 /system 下的某个位置。表 6-3 进一步详细描述了每个条目。你也可以将图 6-2 与图 3-2 进行对比,看看 AOSP 源文件中各架构组件与最终文件系统中的位置有何不同。
表 6-3. /system 目录内容
| 条目 | 类型 | 说明 |
|---|---|---|
| /app | 目录 | 作为 AOSP 一部分构建的系统应用,如浏览器、邮件应用、日历等。所有使用 BUILD_PACKAGE 构建的模块都在此处 |
| /bin | 目录 | 作为 AOSP 一部分构建的所有原生二进制文件和守护进程。所有使用 BUILD_EXECUTABLE 构建的模块都在此处。唯一的例外是 adbd,它的 LOCAL_MODULE_PATH 被设置为 /sbin,因此被安装到那里 |
| /etc | 目录 | 包含各种守护进程和工具使用的配置文件,包括可能由 init 配置文件在启动时启动的 init.\<device_name>.sh 脚本 |
| /fonts | 目录 | Android 使用的字体 |
| /framework | 目录 | Framework .jar 文件 |
| /lib | 目录 | 系统的原生库。本质上这指任何使用 BUILD_SHARED_LIBRARY 构建的模块。需要再次强调的是,Android 完全不使用 /lib,只使用 /system 下的这个 lib 目录 |
| /modules | 目录 | 可选目录,用于存储运行系统所需的可加载内核模块 |
| /usr | 目录 | 类似于传统 Linux 系统中经典 /usr 目录的缩小版 |
| /xbin | 目录 | AOSP 中构建的软件包生成的"额外"二进制文件,这些包对系统运行并非必需。包括 strace、ssh 和 sqlite3 等 |
| /build.prop | 文件 | AOSP 构建过程生成的属性集。在启动时由 init 加载 |
在 4.2/Jelly Bean 中,你还会在 /system 中发现表 6-4 的条目。
表 6-4. 4.2/Jelly Bean 中 /system 的新增条目
| 条目 | 类型 | 说明 |
|---|---|---|
| /media | 目录 | 启动动画和其他媒体相关的文件 |
| /tts | 目录 | 语音合成(TTS)引擎相关文件 |
通常 /system 以只读方式挂载,因为它只在整个 Android 操作系统更新到新版本时才需要被更改。一个好处是:一些 OTA 更新脚本会进行二进制差分更新,而由于这个分区被假定为自出厂以来没有被更改过,因此差分的应用可以得到保证。
/data¶
如前所述,/data 包含所有随时间变化的数据和应用。例如,你从 Google Play 下载的应用存储的所有数据都在这里。AOSP 编译系统生成的 userdata.img 镜像大部分是空的,所以这个目录最初几乎什么都没有。但随着系统开始被使用,这个目录的内容自然会填充,并且需要在重启之间保留。这就是为什么 /data 通常以读写模式从持久存储挂载。表 6-5 显示了其内容。
表 6-5. /data 目录内容
| 条目 | 类型 | 说明 |
|---|---|---|
| /anr | 目录 | ANR 追踪文件 |
| /app | 目录 | 应用的默认安装位置 |
| /app-private | 目录 | 具有转发锁定功能的应用的安装位置 |
| /backup | 目录 | BackupManager 系统服务使用 |
| /dalvik-cache | 目录 | 所有 dex 文件的缓存 JIT 编译版本的存放位置 |
| /data | 目录 | 包含系统中每个已安装应用的子目录。实际上这里是每个应用的"主"目录所在位置 |
| /dontpanic | 目录 | 上一次 panic 的输出(控制台和线程)——供 dumpstate 使用 |
| /local | 目录 | shell 可写目录。换言之,任何可以通过 adb shell 登录到设备的用户都可以向这个目录复制任何内容(包括二进制文件),并且重启后会保留 |
| /misc | 目录 | 杂项数据,如 WiFi、蓝牙或 VPN 相关 |
| /property | 目录 | 持久化的系统属性 |
| /secure | 目录 | 如果设备使用加密文件系统,用于存储用户账户信息 |
| /system | 目录 | 系统范围的数据,如账户数据库和已安装软件包列表 |
| /tombstones | 目录 | 每当原生二进制文件崩溃时,会在此创建名为 tombstone_ 加上序号的文件,其中包含有关崩溃的信息 |
在 4.2/Jelly Bean 中,你还会在 /data 中发现表 6-6 的条目。
表 6-6. 4.2/Jelly Bean 中 /data 的新增条目
| 条目 | 类型 | 说明 |
|---|---|---|
| /app-asec | 目录 | 加密的应用 |
| /drm | 目录 | DRM 加密数据。转发锁定控制文件 |
| /radio | 目录 | 无线电固件 |
| /resource-cache | 目录 | 应用资源缓存 |
| /user | 目录 | 多用户系统的用户特定数据 |
多用户支持¶
4.2/Jelly Bean 添加的最重要功能之一是多用户支持。事实上,有人认为这一功能的加入是一个分水岭,为 Android 开辟了新的用例。虽然仅在平板模式下可用,但它允许多个用户以一致的方式共享同一设备。具体来说,这意味着每个用户都可以通过单独登录来使用设备,并且每个应用程序都可以为每个用户保留自己的一套账户凭证和数据。
为实现这一点,AOSP 的数据存储机制略有修改。例如,/data/data 现在是设备所有者应用数据的目录:
root@android:/ # ls -l /data/user/
lrwxrwxrwx root root 2012-11-30 20:46 0 -> /data/data/
drwxrwx--x system system 2012-12-04 23:38 10
SD 卡¶
如前所述,消费设备通常有 microSD 卡,用户可以将其取出并插入电脑。SD 卡的内容对系统运行并不重要。实际上,你可以相对安全地擦除它而不会有不良影响。但如果真实用户正在使用该设备,你至少需要了解其中的内容,因为某些应用会将信息存储在 SD 卡上——这对用户来说可能很重要。表 6-7 详细说明了 /sdcard 目录中你可能会发现的一些内容。
表 6-7. 示例 /sdcard 目录内容
| 条目 | 类型 | 说明 |
|---|---|---|
| /Alarm | 目录 | 可作为闹钟播放的下载音频文件 |
| /Android | 目录 | 包含应用的"外部"数据和媒体目录。前者可用于存储非关键文件和缓存,后者用于应用特定的媒体 |
| /DCIM | 目录 | 相机应用拍摄的照片和视频 |
| /Download | 目录 | 从网络下载的文件 |
| /Movies | 目录 | 电影的下载位置 |
| /Music | 目录 | 用户的音乐文件 |
| /Notifications | 目录 | 用户可选的通知播放的下载音频文件 |
| /Pictures | 目录 | 用户可用的下载图片 |
| /Podcasts | 目录 | 用户的播客 |
| /Ringtones | 目录 | 用户可选的下载铃声 |
由于 /sdcard 是全局可写的,具体内容将取决于设备上运行的应用,当然还有用户手动复制到那里的内容。再提醒一下,Android API 区分"内部"和"外部"存储,而 SD 卡是后者。另外注意,一些升级程序在升级过程中使用 SD 卡作为存储更新镜像的位置。