14、文件系统与存储:system、vendor、data 分区、SquashFS、EXT4、F2FS

做嵌入式Android系统开发,绕不开存储。说白了,你的手机开机后,系统代码放在哪?用户数据存在哪?OTA升级时写的是哪个分区?这些问题的答案,都藏在文件系统和分区布局里。

我刚开始接触AOSP时,最头疼的就是分区表。那时候我总以为Android跟Linux桌面一样,一个根分区搞定一切。结果第一次编译完刷机,发现系统根本起不来——因为我压根没给vendor分区留空间。嗯,从那以后,我养成了先看分区表的习惯。

14.1 分区布局:谁该住在哪里?

Android的分区设计,其实是为了解决一个核心矛盾:既要保证系统能安全升级,又要让厂商能独立更新驱动和固件。你想想看,如果所有东西都塞在一个分区里,Google发安全补丁时,高通还得跟着重新打包驱动,这得多麻烦?

所以,AOSP把系统拆成了几个关键分区:

分区名 挂载点 文件系统 内容
system /system EXT4 / SquashFS Android框架、系统应用、核心库
vendor /vendor EXT4 / SquashFS 硬件抽象层(HAL)、厂商驱动、固件
data /data F2FS / EXT4 用户数据、应用安装包、配置文件
boot 原始镜像 Linux内核 + ramdisk
recovery 原始镜像 恢复模式系统

我个人习惯把system和vendor看作“只读区”,data看作“读写区”。为什么?因为系统分区在OTA升级时会被整体替换,如果允许用户随意修改,那升级时的一致性就没法保证了。

核心原则:system和vendor是“工厂烧录”的,data是“用户生长”的。两者生命周期不同,文件系统选择也不同。

14.2 SquashFS:压缩到极致的只读文件系统

先聊聊SquashFS。这东西我第一次接触时觉得挺冷门,后来发现它在嵌入式领域简直是神器。

SquashFS是一个只读、压缩的文件系统。它的特点很明确:

  • 高压缩比:用gzip/lz4/lzo/zstd压缩,能把1GB的系统压到400MB左右
  • 随机读取快:虽然压缩了,但读取时是“按需解压”,只解压你访问的那一块
  • 写保护:只读特性天然防止篡改,适合system和vendor分区

我在项目中遇到过一个问题:某款低端平板,Flash只有8GB。如果用EXT4放system分区,光系统就占掉3GB,用户只剩5GB可用。换成SquashFS后,系统压缩到1.2GB,用户空间瞬间多了近2GB。用户反馈说“存储变大了”——其实只是文件系统选对了。

我的建议:如果你的设备Flash小于16GB,system和vendor优先考虑SquashFS。压缩比带来的空间收益,远大于那一点点解压性能开销。

不过要注意,SquashFS不支持写入。所以OTA升级时,系统会把新镜像下载到data分区,然后在recovery模式下把整个system分区重新刷写。这也是为什么Android的OTA需要“双分区”或“虚拟AB分区”机制——你得有个地方放新系统。

14.3 EXT4:成熟稳重的日志文件系统

EXT4是Linux世界的“老黄牛”。Android早期版本(一直到Android 8左右)的data分区几乎全是EXT4。它的优点很明显:

  • 成熟稳定:从EXT2到EXT4,二十多年的打磨,bug极少
  • 日志保护:意外断电后,fsck能快速恢复文件系统一致性
  • 大文件支持:单文件最大16TB,分区最大1EB,手机场景绰绰有余

但EXT4也有短板。它最初是为机械硬盘设计的,对Flash的磨损均衡支持不够好。你想想看,手机上的eMMC/UFS是Flash芯片,写入次数有限。EXT4的日志机制会频繁写入元数据,加速Flash老化。

我曾经在一款工控设备上做过测试:连续跑IO密集型应用,EXT4的data分区在18个月后出现了坏块。换成F2FS后,同样负载下跑了30个月还没出问题。这个差距,在消费级手机上可能不明显,但在嵌入式设备上就是生与死的区别。

注意:EXT4的“日志”是双刃剑。它保护了数据一致性,但增加了写入放大。如果你的设备Flash寿命有限(比如低端eMMC),建议考虑F2FS。

14.4 F2FS:为Flash而生的新秀

F2FS(Flash-Friendly File System)是三星专门为NAND Flash设计的文件系统。从Android 8开始,Google把它列为data分区的推荐选项。到了Android 10以后,几乎所有新设备都用F2FS了。

F2FS的核心设计思路是:把Flash的特性暴露给文件系统。它知道Flash的读写单位是“页”,擦除单位是“块”,所以它尽量:

  • 减少随机写入:把多个小写入合并成大写入
  • 避免原地更新:用“日志结构”方式写入,减少擦除次数
  • 智能垃圾回收:在空闲时整理碎片,避免写入时卡顿

我个人的体验是,F2FS在小文件随机读写场景下比EXT4快30%-50%。手机上的应用安装、数据库操作、缓存写入,基本都是小文件随机IO。所以用F2FS做data分区,日常使用会感觉更流畅。

但F2FS也不是完美的。它有个坑:空间利用率不如EXT4。F2FS默认会预留约5%的空间用于垃圾回收。如果你把data分区塞到95%以上,写入性能会急剧下降。我曾经见过一台设备,data分区用了97%,结果安装应用时卡了整整两分钟——就是因为F2FS找不到空闲块做垃圾回收了。

避坑指南:使用F2FS时,建议预留至少10%的空闲空间。如果设备存储紧张,可以考虑用EXT4替代,或者调小F2FS的预留比例(但会影响性能)。

14.5 文件系统选择决策树

说了这么多,到底该怎么选?我画了一张图,帮你快速决策:

文件系统选择决策树 分区类型? system / vendor data Flash < 16GB? → 用 SquashFS Flash ≥ 16GB? → 用 EXT4 IO密集 / 小文件多? → 用 F2FS 大文件 / 稳定性优先? → 用 EXT4 注:实际项目中还需考虑内核版本、Flash类型(eMMC/UFS)、OTA方案等因素 只读分区 读写分区

14.6 实际项目中的分区配置

在AOSP中,分区配置主要在BoardConfig.mkBoardConfigCommon.mk里。我拿一个实际项目举例:

# 文件系统类型
BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE := squashfs
BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE := squashfs
BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE := f2fs

# 分区大小(单位:MB)
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 2048
BOARD_VENDORIMAGE_PARTITION_SIZE := 512
BOARD_USERDATAIMAGE_PARTITION_SIZE := 8192

# 压缩选项(SquashFS用lz4,解压快)
BOARD_SQUASHFS_COMPRESSOR := lz4
BOARD_SQUASHFS_COMPRESSOR_OPTIONS := -Xhc

# F2FS选项
BOARD_F2FS_OPTIONS := -O extra_attr,compression

这里有几个细节要注意:

  • SquashFS的压缩器选lz4:虽然zstd压缩比更高,但lz4解压速度更快。手机启动时大量文件需要解压,lz4能让开机快1-2秒
  • F2FS开启extra_attr:支持扩展属性,有些应用(比如SELinux策略)需要这个
  • 分区大小别卡太紧:system分区留20%余量,防止后续OTA补丁包塞不下

我曾经踩过的坑:有一次我把system分区设成刚好2000MB,结果Google发了个安全补丁,多了50MB的代码,编译出来的镜像直接超了。最后只能重新调整分区表,所有设备都得重新刷机。从那以后,我每个分区至少多留15%的余量。

14.7 文件系统与OTA升级的关联

文件系统类型直接影响OTA升级策略。我简单说一下:

  • SquashFS + EXT4/F2FS:这是最常见的组合。OTA时,新系统镜像下载到data分区,recovery模式下把system/vendor分区整个重写。优点是简单可靠,缺点是升级期间不能使用手机
  • 虚拟AB分区:Android 10以后引入。系统同时保留两套分区(slot A和slot B),升级时在后台写入另一套,写完后切换启动。文件系统可以是EXT4或F2FS,但不能是SquashFS——因为需要写入

我个人更推荐虚拟AB分区 + F2FS的组合。虽然会多占一份存储空间,但用户体验好太多——升级时手机照常用,重启后直接进入新系统。如果升级失败,还能自动回滚到旧系统。

14.8 性能调优小技巧

最后分享几个我在项目中用过的调优技巧:

  1. F2FS的GC频率调低:在fstab里加gc_urgent_sleep_time=500,减少后台垃圾回收对前台IO的影响
  2. SquashFS的块大小设128KB:默认是64KB,128KB能提高压缩比,同时解压时一次读更多数据,减少IO次数
  3. data分区预留空间:F2FS预留10%,EXT4预留5%。用tune2fsfsck.f2fs可以调整
  4. 关闭atime:在fstab里加noatime,减少每次读文件时的元数据写入

嗯,文件系统这块内容不少,但核心就一句话:system/vendor用只读压缩的SquashFS,data用Flash友好的F2FS。记住这个原则,大部分场景都不会出错。


公众号:蓝海资料掘金营,微信deep3321