19、文件系统优化:只读文件系统(EROFS)使用,文件系统挂载参数优化,SELinux策略对文件系统访问的影响
说到Android系统启动优化,文件系统这块儿,我估计很多同学第一反应就是「不就是挂载分区嘛」。嗯,话是这么说,但这里面的门道可不少。尤其是EROFS这个只读文件系统,我当年第一次接触的时候,还觉得「只读?那能干嘛」。后来在项目里踩过坑,才明白它的价值。
EROFS:为只读而生的文件系统
EROFS,全称是Enhanced Read-Only File System。说白了,它就是专门为只读场景设计的。Android系统里,/system、/vendor、/product这些分区,一旦出厂,基本就不会再写了。那为什么不用传统的ext4呢?
我个人的习惯是,只要分区是只读的,优先考虑EROFS。原因很简单:
- 体积更小:EROFS采用内联压缩,同样的数据,比ext4能省出10%-20%的空间。我记得有一次项目里/system分区吃紧,换成EROFS后,直接多出了200MB的余量。
- 随机读取更快:EROFS的元数据设计更紧凑,随机读性能比ext4好不少。这对系统启动来说,太关键了。
- 天然防篡改:只读就是只读,你没法通过挂载参数改成读写。安全性上,比ext4的ro模式更可靠。
核心结论:EROFS不是用来替代所有文件系统的,它只适合那些「写一次,读一辈子」的分区。
EROFS的挂载参数优化
光选对文件系统还不够,挂载参数也得调。我见过不少同学,直接默认参数挂载,结果性能没发挥出来。
这里我列几个常用的优化参数:
| 参数 | 说明 | 建议值 |
|---|---|---|
context= |
设置SELinux上下文 | 根据分区类型设置,如u:object_r:system_file:s0 |
fscontext= |
文件系统本身的SELinux上下文 | 与分区类型一致 |
noatime |
不更新访问时间 | 建议开启,减少不必要的写操作 |
nodiratime |
不更新目录访问时间 | 建议开启 |
举个例子,我在一个项目里是这样挂载/system的:
mount -t erofs -o ro,context=u:object_r:system_file:s0,noatime /dev/block/by-name/system /system
你可能会问,为什么还要加noatime?EROFS不是只读吗?嗯,这里要注意,noatime虽然对只读文件系统没有实际效果,但加上去可以保证万一哪天你误操作挂载成读写,也不会产生额外的写开销。算是个防御性编程吧。
小技巧:如果你不确定某个参数是否生效,可以查看/proc/mounts,里面会列出当前所有挂载点的实际参数。
SELinux策略对文件系统访问的影响
文件系统挂好了,SELinux策略没配对,照样启动不起来。我曾经在一个项目里,明明EROFS挂载成功了,但系统就是卡在init阶段。查了半天,发现是SELinux上下文没设对。
SELinux对文件系统的访问控制,主要体现在两个方面:
- 文件上下文(file_contexts):每个文件或目录,都需要有正确的SELinux标签。比如
/system/bin/init的标签必须是u:object_r:init_exec:s0,否则init进程无法执行。 - 挂载点上下文:挂载时,可以通过
context=参数指定整个分区的默认上下文。这个上下文会作为该分区下所有文件的默认标签。
举个例子,假设你的/vendor分区是EROFS,那么你需要在file_contexts里定义:
/vendor(/.*)? u:object_r:vendor_file:s0
然后在fstab里挂载时指定:
/dev/block/by-name/vendor /vendor erofs ro,context=u:object_r:vendor_file:s0 0 0
这样,整个/vendor分区下的文件,都会继承vendor_file这个标签。如果你有特殊的文件需要不同标签,比如某个可执行文件,可以在file_contexts里单独覆盖:
/vendor/bin/foo u:object_r:foo_exec:s0
注意:SELinux的上下文匹配是精确匹配优先。也就是说,如果某个文件有单独的规则,会优先使用那条规则,而不是继承分区的默认上下文。
启动流程中的文件系统与SELinux交互
为了让你更直观地理解,我画了一张图,展示从内核启动到文件系统挂载,再到SELinux策略加载的整个流程:
从这张图你可以看到,SELinux策略的加载,是在根文件系统挂载之后、其他分区挂载之前。这意味着,/system和/vendor这些分区的SELinux上下文,是在挂载时通过context=参数指定的,而不是从文件系统本身读取的。
嗯,这里有个坑。如果你在file_contexts里定义了某个文件的上下文,但挂载时又用了context=参数,那到底听谁的?答案是:挂载参数优先。因为context=是在VFS层设置的,它会覆盖文件系统自身的标签。所以,如果你希望某些文件有独立的上下文,要么不用context=,要么在挂载后通过restorecon重新设置。
避坑指南:我曾经在一个项目里,为了省事,给整个/vendor分区统一设置了vendor_file上下文。结果有个可执行文件需要vendor_exec标签,导致SELinux一直报avc denial。后来我改成在file_contexts里单独定义,挂载时不加context=,问题才解决。
总结一下
文件系统优化这块,说白了就是三件事:
- 选对文件系统:只读分区用EROFS,读写分区用ext4或f2fs。
- 调好挂载参数:
noatime、context=这些,别偷懒。 - 配好SELinux策略:文件上下文和挂载上下文要一致,否则启动时等着avc denial吧。
我个人习惯是,每次修改文件系统或SELinux策略后,都会在启动时抓一下dmesg和logcat,看看有没有SELinux相关的警告。早发现早解决,别等到集成测试时才手忙脚乱。
好了,这一章的内容就到这里。记住,文件系统是Android系统的基石,优化好了,启动速度能快不少。