3、init进程优化:init.rc脚本解析、服务启动顺序调整、并行化启动策略
好,咱们接着聊。上一节我们把init进程的启动流程捋了一遍,这一节要动真格的了——怎么优化它。
说实话,init进程的优化,核心就三个字:快、准、狠。快,就是让服务尽早启动;准,就是别启动那些暂时用不上的东西;狠,就是该并行就并行,别傻等着。
我个人习惯把init优化分成三个层面:脚本解析优化、启动顺序调整、并行化策略。咱们一个一个来。
3.1 init.rc脚本解析:别让解析成为瓶颈
init.rc是init进程的“剧本”。它告诉init:你要启动哪些服务、设置哪些属性、创建哪些目录。
但问题来了——这个脚本是逐行解析的。你想想看,如果脚本里写了100个service,每个service后面跟了一堆option,那解析起来得多慢?
我在项目中遇到过一个问题:某款低端机,开机时间比竞品慢了2秒。一查,发现init.rc里塞了太多冗余的service定义,有些服务甚至根本用不上。解析这些没用的东西,白白浪费了CPU时间。
优化建议:
- 精简service定义:只保留真正需要的服务。那些“可能以后会用”的服务,先注释掉。
- 合并同类项:多个service如果共享相同的启动条件,可以合并成一个group。
- 减少on触发器的数量:每个on触发器都会触发一次解析,能少则少。
举个例子,你可能会看到这样的代码:
# 不推荐:每个服务单独定义,解析开销大
service serv1 /system/bin/serv1
class main
user root
group root
service serv2 /system/bin/serv2
class main
user root
group root
# 推荐:合并成同一个class,减少解析次数
service serv1 /system/bin/serv1
class core
service serv2 /system/bin/serv2
class core
嗯,这里要注意:class的优先级。core类服务会先于main类启动。如果你把不该放core的服务放进去,反而会拖慢关键路径。
3.2 服务启动顺序调整:谁先谁后,心里要有数
启动顺序,说白了就是依赖关系。服务A依赖服务B,那B就得先启动。但很多时候,依赖关系并没有那么严格。
我曾经踩过一个坑:某个服务需要访问/data分区,但/data分区是在on fs阶段才挂载的。结果这个服务被放在了on init阶段启动,一启动就报错,然后反复重启,白白浪费了时间。
避坑指南:
我曾经在优化某款平板时,发现一个服务在启动时总是卡住。查了半天,发现它依赖一个硬件驱动,但那个驱动要等内核加载完才能用。解决方案很简单:把这个服务的启动时机从on init改成on boot,问题就解决了。
所以,调整启动顺序的核心原则是:
- 关键路径上的服务优先:比如surfaceflinger、servicemanager这些,越早启动越好。
- 非关键服务延后:比如一些后台监控服务、OTA升级服务,完全可以等系统启动完成后再启动。
- 依赖关系明确:用
socket、file等option来声明依赖,而不是靠“猜”。
你可能会问:怎么知道哪些服务在关键路径上?
嗯,这个问题问得好。我一般会用Bootchart来抓一次启动过程,然后看CPU和磁盘的利用率曲线。如果某个服务启动时CPU利用率突然飙高,那它很可能就在关键路径上。
3.3 并行化启动策略:让CPU忙起来
这是优化空间最大的地方。说白了,就是别让CPU闲着。
init进程默认是串行启动服务的。一个服务启动完了,才启动下一个。但很多服务之间并没有依赖关系,完全可以并行启动。
我记得有一次优化,把几个没有依赖关系的服务改成并行启动,开机时间直接缩短了0.8秒。你想想看,0.8秒在开机优化里是什么概念?那是质的飞跃。
并行化策略:
- 使用class_start:把服务按class分组,用
class_start core和class_start main来并行启动不同class的服务。 - 使用trigger:用
trigger来触发一组服务的启动,而不是一个一个来。 - 调整service的critical属性:critical服务如果启动失败,会导致系统重启。所以,非关键服务不要设成critical。
举个例子:
# 串行启动
service serv1 /system/bin/serv1
class main
service serv2 /system/bin/serv2
class main
# 这两个服务会串行启动
# 并行启动
service serv1 /system/bin/serv1
class core
service serv2 /system/bin/serv2
class main
# core和main是并行启动的
但这里有个坑:并行启动不等于乱序启动。如果两个服务共享同一个资源(比如同一个socket),那它们还是得串行。
我曾经在优化时犯过一个错误:把两个都依赖同一个硬件驱动的服务并行启动了。结果两个服务同时去访问驱动,导致驱动崩溃,系统直接重启。嗯,从那以后,我每次改并行化策略,都会先确认一下依赖关系。
3.4 实战:一个完整的优化案例
说了这么多,咱们来看一个实际案例。
某款手机,开机时间12秒,目标是10秒以内。我用Bootchart抓了一次启动过程,发现init阶段花了3.2秒。分析下来,问题出在:
- init.rc里定义了30多个service,但实际用到的只有20个。
- 所有service都是串行启动的,CPU利用率只有30%。
- 有个服务在
on init阶段启动,但它依赖的驱动在on boot阶段才加载。
优化方案:
| 问题 | 优化措施 | 效果 |
|---|---|---|
| 冗余service | 删除10个不用的service定义 | 解析时间减少0.3秒 |
| 串行启动 | 将无依赖关系的服务改为并行启动 | 启动时间减少0.8秒 |
| 启动时机错误 | 将服务从on init移到on boot |
避免了一次重启,节省0.5秒 |
最终,init阶段从3.2秒降到了1.6秒,整机开机时间从12秒降到了9.8秒。效果还是很明显的。
总结一下:
init进程优化,说白了就是三件事:精简脚本、调整顺序、并行启动。但每一步都要小心,别为了追求速度而牺牲稳定性。毕竟,系统启动再快,如果动不动就崩溃,那也没用。
下一节,我们会聊Bootchart的具体用法。到时候我会手把手教你抓一次启动过程,然后分析瓶颈在哪里。嗯,敬请期待。