综合实战:从零开始定制一个优化启动且安全的Android系统
好了,终于到了这个章节。前面我们聊了那么多启动优化的技巧,也深入分析了SELinux的策略写法,现在是时候把它们串起来了。
这一章,我会带你走一遍完整的流程——从拿到AOSP源码开始,到最终烧录出一个既启动快、又安全的Android系统。嗯,说白了就是“既要马儿跑,又要马儿不吃草”的实战版本。
1. 整体思路:先优化,再加固
我个人习惯把这件事分成两大阶段:
- 第一阶段:启动优化——减少不必要的服务、精简init脚本、优化Zygote预加载。
- 第二阶段:SELinux策略定制——收紧权限、修补漏洞、确保优化后的系统依然安全。
你想想看,如果先做安全策略,结果发现某个服务被删了导致策略冲突,回头还得改,多折腾。所以我的建议是:先瘦身,再穿盔甲。
核心原则:每一次修改都要可追溯。我在项目中吃过亏——改完代码忘了记录,结果出问题只能从头排查。所以,请务必用Git做好版本管理。
2. 启动优化实战:从init.rc开刀
拿到AOSP源码后,第一步不是急着改代码,而是先分析现有的启动流程。我通常会先看这几个文件:
system/core/rootdir/init.rcsystem/core/rootdir/init.${ro.hardware}.rcsystem/core/rootdir/init.zygote*.rc
为什么要看它们?因为Android的启动顺序全写在这里面。说白了,init进程就是按照这些脚本一条一条执行的。
2.1 禁用不必要的服务
我记得有一次给一个低端设备做优化,发现系统里居然启动了adbd、logd、dumpsys这些调试服务。对于量产设备来说,这些完全没用。
做法很简单:在init.rc里找到对应的service定义,直接注释掉或者加上disabled标志。
# 原始定义
service adbd /system/bin/adbd
class main
disabled
# 优化后:直接注释掉
# service adbd /system/bin/adbd
# class main
# disabled
小技巧:不要直接删除,先注释。万一后面需要回退,省得重新写。
2.2 精简Zygote预加载类
Zygote启动时会预加载大量Java类和资源。这部分是启动时间的大头。我曾经在一个项目里发现,预加载列表里居然有几十个第三方应用的类——这明显是之前开发阶段留下的。
修改文件:frameworks/base/config/preloaded-classes
把不需要的类删掉,只保留系统核心类。注意:不要删掉android.os.*、java.lang.*这些基础类,否则系统直接崩给你看。
# 保留的核心类
android.os.Binder
android.os.Handler
android.os.Looper
java.lang.String
java.lang.Object
# ... 其他核心类
# 可以删除的示例(如果确定不用)
# com.thirdparty.sdk.SomeClass
2.3 优化服务启动顺序
有些服务之间没有依赖关系,完全可以并行启动。我在init.rc里调整过class main和class late_start的划分,把一些非关键服务推迟到后期启动。
# 将非关键服务移到 late_start
service noncritical_service /system/bin/noncritical
class late_start
user system
group system
3. SELinux策略定制:收紧权限
启动优化做完后,系统确实变快了。但安全呢?别忘了,我们删掉了一些服务,调整了启动顺序,这些改动可能会破坏原有的SELinux策略。
嗯,这里要注意:每次修改init.rc或添加/删除服务,都必须同步更新SELinux策略。否则系统启动时会出现avc: denied错误,严重时直接起不来。
3.1 分析现有的avc日志
我习惯先跑一遍优化后的系统,然后抓取avc日志:
adb shell dmesg | grep avc
adb logcat -b events | grep avc
把这些日志保存下来,一条一条分析。比如:
avc: denied { read } for pid=1234 comm="my_service" name="config.conf" dev="mmcblk0p1" ino=5678 scontext=u:r:my_service:s0 tcontext=u:object_r:system_file:s0 tclass=file
这条日志告诉我们:my_service进程想读取system_file类型的文件,但没有权限。解决方案就是给my_service添加对应的allow规则。
3.2 编写自定义te文件
在device/你的厂商/你的设备/sepolicy/目录下,创建或修改my_service.te文件:
# 允许my_service读取system_file
allow my_service system_file:file read;
# 如果还需要写,加上write
allow my_service system_file:file write;
警告:不要滥用allow * *:* *;这种通配规则。我曾经见过有人为了省事直接开放所有权限,结果系统被植入恶意应用后完全失控。SELinux的精髓就是最小权限原则。
3.3 处理新服务的域转换
如果你在优化过程中新增了自定义服务,还需要定义它的域(domain)。比如:
# 在 new_service.te 中
type new_service, domain;
type new_service_exec, exec_type, file_type;
# 域转换规则
init_daemon_domain(new_service)
# 允许它访问必要的资源
allow new_service system_file:file read;
allow new_service self:capability { net_admin };
4. 验证与测试
改完策略后,重新编译并烧录系统。然后做两件事:
- 启动时间测试:用
bootchart或adb shell dumpsys bootstats查看启动耗时。 - SELinux模式测试:先设为
permissive模式跑一遍,收集所有avc日志,确认没有遗漏的规则。然后切回enforcing模式。
# 临时切换为permissive模式
adb shell setenforce 0
# 收集日志
adb shell dmesg | grep avc > avc_log.txt
# 切回enforcing
adb shell setenforce 1
我的经验:在permissive模式下跑至少24小时,覆盖各种使用场景。否则切回enforcing后,用户一操作就触发denied,体验极差。
5. 整体流程SVG图
下面这张图概括了整个流程,你可以把它当作检查清单:
6. 避坑指南
最后,分享几个我踩过的坑:
- 不要一次性改太多:每次只改一个服务或一条策略,编译测试通过后再改下一个。否则出了问题根本不知道是哪一步引起的。
- 保留原始策略备份:我在一个项目里把
system_app.te改坏了,导致所有系统应用都无法启动。幸好有备份,否则得重新下载整个AOSP。 - 注意策略的叠加效果:SELinux策略是累加的,多个te文件里的规则会合并。我曾经因为两个文件里定义了冲突的规则,导致编译报错,排查了半天。
我的习惯:每次修改后,在BoardConfig.mk里加上BOARD_USE_ENFORCING_SELINUX := true,这样编译出来的系统默认就是enforcing模式,避免开发阶段忘记切换。
好了,这一章的内容就是这些。从init.rc的优化到SELinux策略的定制,整个流程走下来,你手里的系统应该既轻快又安全了。记住:优化和安全不是对立面,而是可以兼得的——只要你愿意花时间去分析和调整。
公众号:蓝海资料掘金营,微信deep3321