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升级服务,完全可以等系统启动完成后再启动。
  • 依赖关系明确:用socketfile等option来声明依赖,而不是靠“猜”。

你可能会问:怎么知道哪些服务在关键路径上?

嗯,这个问题问得好。我一般会用Bootchart来抓一次启动过程,然后看CPU和磁盘的利用率曲线。如果某个服务启动时CPU利用率突然飙高,那它很可能就在关键路径上。

3.3 并行化启动策略:让CPU忙起来

这是优化空间最大的地方。说白了,就是别让CPU闲着

init进程默认是串行启动服务的。一个服务启动完了,才启动下一个。但很多服务之间并没有依赖关系,完全可以并行启动。

我记得有一次优化,把几个没有依赖关系的服务改成并行启动,开机时间直接缩短了0.8秒。你想想看,0.8秒在开机优化里是什么概念?那是质的飞跃。

并行化策略:

  • 使用class_start:把服务按class分组,用class_start coreclass_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秒。分析下来,问题出在:

  1. init.rc里定义了30多个service,但实际用到的只有20个。
  2. 所有service都是串行启动的,CPU利用率只有30%。
  3. 有个服务在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的具体用法。到时候我会手把手教你抓一次启动过程,然后分析瓶颈在哪里。嗯,敬请期待。