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.rc和fstab文件来创建。我习惯把挂载流程分为三个阶段:
- 第一阶段(early-init):挂载必要的文件系统,如tmpfs、proc、sysfs
- 第二阶段(init):挂载system、vendor等只读分区
- 第三阶段(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的配置。到时候咱们再细聊。