1、AOSP存储架构概览:Android存储子系统演进、分区布局与挂载点、SELinux与存储安全基础

1.1 Android存储子系统的演进之路

说实话,Android的存储架构这些年变化挺大的。我刚开始做AOSP适配那会儿,还是Android 4.4的时代,存储方案相对简单。但到了Android 10以后,整个存储模型几乎重写了一遍。

为什么会这样?说白了,就是碎片化太严重了。早期各家厂商乱改存储路径,应用开发者苦不堪言。Google后来下定决心,搞了一套强制性的存储规范。

我简单梳理一下几个关键节点:

  • Android 4.4(KitKat):引入主外存储概念,但权限管理很弱
  • Android 6.0(Marshmallow):运行时权限机制,存储权限首次需要动态申请
  • Android 10(Q):分区存储(Scoped Storage)正式登场,应用不能再随意访问公共目录
  • Android 11+:强制分区存储,媒体文件访问必须通过MediaStore

我个人习惯把Android 10当作分水岭。之前是「野蛮生长」阶段,之后是「规范治理」阶段。你在做适配时,一定要先确认目标设备的Android版本,否则存储路径写死就麻烦了。

核心要点:分区存储不是可选项,Android 11以上强制启用。如果你的应用还在用File API直接写/sdcard,赶紧改吧。

1.2 分区布局与挂载点详解

每次拿到一台新设备,我第一件事就是看分区表。你想想看,分区布局直接决定了你能在哪个目录读写数据。

典型的AOSP分区布局如下:

分区名称 挂载点 文件系统 用途说明
system /system ext4 / erofs 系统镜像,只读
vendor /vendor ext4 / erofs 厂商定制内容,只读
product /product ext4 / erofs 产品级定制,只读
data /data f2fs / ext4 用户数据,可读写
cache /cache ext4 系统缓存,可读写
metadata /metadata ext4 元数据分区,加密相关

嗯,这里要注意一个细节:/sdcard其实是个符号链接,指向/data/media/0。我在项目中遇到过有人直接操作/data/media目录,结果导致媒体数据库错乱。千万别这么干。

避坑指南:我曾经在适配某款平板时,发现/vendor分区空间不够。后来查了才知道,Android 10以后要求vendor分区至少2GB。如果你在编译时遇到分区空间不足,先检查一下BoardConfig.mk里的BOARD_VENDORIMAGE_PARTITION_SIZE。

1.3 挂载点与init.rc的关系

挂载点不是凭空出现的,它们由init进程在启动时解析init.rcfstab文件来创建。我习惯把挂载流程分为三个阶段:

  1. 第一阶段(early-init):挂载必要的文件系统,如tmpfs、proc、sysfs
  2. 第二阶段(init):挂载system、vendor等只读分区
  3. 第三阶段(late-init):挂载data分区,启动加密服务

看一段典型的fstab配置:

# 设备节点          挂载点        类型    挂载选项
/dev/block/by-name/system      /system         ext4    ro,barrier=1
/dev/block/by-name/vendor      /vendor         ext4    ro,barrier=1
/dev/block/by-name/userdata    /data           f2fs    noatime,nosuid,nodev,reserve_root=32768
/dev/block/by-name/cache       /cache          ext4    noatime,nosuid,nodev

你想想看,如果fstab里写错了设备节点,设备就起不来了。我调试过一台设备,data分区挂载失败,卡在bootanimation循环。最后发现是f2fs的reserve_root参数设得太小,导致保留块不足。

1.4 SELinux与存储安全基础

SELinux,说白了就是给Android加了一层「门禁系统」。没有它,任何进程都能访问你的照片,那还得了?

在存储领域,SELinux主要管三件事:

  • 文件上下文(File Context):每个文件都有个标签,比如u:object_r:sdcard_external_file:s0
  • 进程域(Domain):每个进程运行在特定域中,比如u:r:untrusted_app:s0
  • 访问规则(Policy):定义哪个域能访问哪个标签的文件

举个例子,普通应用(untrusted_app域)想读SD卡上的文件(sdcard_external_file标签),必须有一条规则允许:

allow untrusted_app sdcard_external_file:file read;

如果没有这条规则,就算你有文件权限,SELinux也会直接拒绝。我在项目中遇到过最典型的案例:某款App在Android 9上能正常读写外置SD卡,升级到Android 10后突然不行了。查了半天,原来是SELinux策略里没给新版本的应用域添加sdcard_external_file的写权限。

警告:千万不要为了省事直接setenforce 0。我在早期项目里干过这事,结果安全审核直接被拒。正确的做法是编写自定义的SELinux策略文件,放在device/<vendor>/<product>/sepolicy/目录下。

1.5 存储安全的基础模型

Android的存储安全模型,我总结为三层防护:

层级 机制 作用范围
第一层 Linux UID/GID权限 文件所有者、组、其他
第二层 SELinux强制访问控制 所有进程与文件
第三层 Android权限系统 应用层权限声明

这三层是叠加的,缺一不可。举个例子,一个应用即使声明了READ_EXTERNAL_STORAGE权限(第三层),如果SELinux策略不允许(第二层),照样读不了文件。

我个人习惯在调试存储问题时,先看logcat里有没有avc: denied的日志。如果有,基本就是SELinux的问题。用audit2allow工具可以快速生成需要的策略规则:

# 从logcat提取avc日志
logcat -b events | grep "avc: denied" > avc_log.txt

# 生成允许规则
audit2allow -i avc_log.txt

嗯,这里要提醒一下:生成的规则只是临时方案,正式发布前一定要review,避免权限放得太大。

调试技巧:我曾经在调试某款车机系统时,发现媒体扫描服务总是无法访问USB存储。用ls -Z /mnt/media_rw/一看,文件上下文标签是u:object_r:fuse:s0,但策略里只允许访问vfat标签。加了一条allow mediascanner fuse:dir search;就解决了。

1.6 小结

这一章我们聊了Android存储架构的演进、分区布局、挂载点以及SELinux安全基础。说白了,存储适配的核心就是搞清楚三件事:

  • 文件该放哪——分区布局决定了路径
  • 怎么挂上去——fstab和init.rc控制挂载
  • 谁能访问——SELinux策略说了算

下一章,我会深入讲分区存储的具体适配方案,包括MediaStore的正确用法和FileProvider的配置。到时候咱们再细聊。