17、并行启动优化:init.rc中并行服务启动,on property触发并行任务,SELinux策略对并行启动的影响。

各位同学,今天我们来聊聊并行启动优化。说实话,Android 系统启动慢,很多时候不是 CPU 不够快,而是我们让 CPU 闲着等别人。你想想看,如果所有服务都排着队一个一个来,那得多浪费时间?

我在做某款旗舰机启动优化时,就遇到过这个问题。当时测出来开机时间比竞品慢了 1.2 秒,查来查去,发现是 init.rc 里一堆服务串行启动了。嗯,这其实是个很常见的坑。

17.1 init.rc 中的并行服务启动

init.rc 是 Android 启动的核心脚本。它定义了各种服务、动作和触发条件。默认情况下,服务是按顺序启动的。但我们可以通过一些技巧让它们并行跑起来。

先看一个典型的串行启动例子:

service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical

service surfaceflinger /system/bin/surfaceflinger
    class core
    user system
    group system graphics
    critical

service media /system/bin/mediaserver
    class main
    user media
    group media audio camera

这三个服务如果串行启动,servicemanager 跑完才跑 surfaceflinger,surfaceflinger 跑完才跑 media。但实际上,它们之间并没有严格的依赖关系。

怎么改成并行?很简单,用 classtrigger 配合:

on boot
    class_start core
    class_start main

on property:sys.boot_completed=1
    class_start late_start

这里 class_start core 会启动所有 class 为 core 的服务。这些服务是并行启动的。同理 class_start main 也是并行启动 main 类的服务。

关键点: 同一 class 内的服务是并行启动的。不同 class 之间可以通过 trigger 控制启动时机。

我个人习惯把服务分成三类:

  • core:系统核心服务,必须最先启动,比如 servicemanager、surfaceflinger
  • main:主要服务,可以稍晚启动,比如 media、audioserver
  • late_start:非关键服务,开机完成后启动,比如一些后台监控服务

这样分的好处是,core 类服务并行启动,main 类服务也并行启动,整体启动时间就压缩了。

17.2 on property 触发并行任务

除了 class 机制,我们还可以用 on property 来触发并行任务。这个机制非常灵活,说白了就是:当某个系统属性变成特定值时,触发一组动作。

举个例子:

on property:sys.boot_completed=1
    start logd
    start dumpsys
    start bugreport

on property:persist.sys.usb.config=mtp
    start mtp_server
    start ptp_server

sys.boot_completed 变成 1 时,logd、dumpsys、bugreport 这三个服务会同时启动。它们之间没有依赖关系,所以并行跑没问题。

我在项目中遇到过一个问题:某个服务启动时需要读取一个配置文件,但这个配置文件是由另一个服务生成的。如果两个服务并行启动,就可能出现文件还没生成就读取的情况。嗯,这时候就需要用 on property 来同步了。

小技巧: 可以用 setprop 在服务启动完成后设置一个属性,然后另一个服务用 on property 等待这个属性。这样既实现了并行,又保证了依赖关系。

看这个例子:

service config_generator /system/bin/config_generator
    class main
    onrestart setprop sys.config.ready 1

on property:sys.config.ready=1
    start config_consumer

config_generator 启动后,会在重启时设置 sys.config.ready=1。但这里有个问题:如果它第一次启动成功,就不会触发 onrestart。所以更好的做法是在服务内部主动设置属性。

实际上,更常见的做法是:

service config_generator /system/bin/config_generator
    class main

on property:init.svc.config_generator=running
    start config_consumer

config_generator 的状态变成 running 时,就启动 config_consumer。这样更可靠。

17.3 SELinux 策略对并行启动的影响

说到并行启动,就不得不提 SELinux。很多同学觉得 SELinux 只是安全模块,跟启动速度没关系。其实不然。SELinux 策略如果写得不好,会严重拖慢并行启动的效率。

为什么会这样?因为并行启动时,多个服务同时访问文件系统、设备节点、共享内存。如果 SELinux 策略没有正确配置,就会出现大量的 AVC 拒绝日志,甚至导致服务启动失败。

我记得有一次,某个服务在并行启动时总是崩溃。查了半天,发现是 SELinux 策略里少了一条 allow 规则。这个服务需要访问 /data/misc 下的一个文件,但策略只允许它访问 /data/system。串行启动时没问题,因为其他服务还没启动,文件还没被占用。但并行启动时,另一个服务先创建了文件,权限就变了。

注意: 并行启动时,SELinux 的 type_transition 规则可能会因为文件创建顺序不同而失效。一定要确保所有可能的路径都有对应的策略。

来看一个实际的 SELinux 策略例子:

# 允许 servicemanager 访问 binder 设备
allow servicemanager binder_device:chr_file rw_file_perms;

# 允许 surfaceflinger 访问 graphics 设备
allow surfaceflinger graphics_device:chr_file rw_file_perms;

# 允许 media 访问 audio 设备
allow media audio_device:chr_file rw_file_perms;

这些规则看起来没问题。但并行启动时,如果 servicemanager 和 surfaceflinger 同时访问 binder 设备,就可能出现竞争。这时候需要确保 binder 驱动本身是线程安全的,同时 SELinux 策略要允许并发访问。

我建议在调试并行启动时,打开 SELinux 的 permissive 模式:

adb shell setenforce 0

然后观察 logcat 中的 AVC 日志:

adb logcat -b events | grep avc

如果发现大量 AVC 拒绝,说明策略需要补充。等所有服务都能正常启动后,再切回 enforcing 模式,把缺失的策略补上。

17.4 并行启动的避坑指南

最后,我总结几个并行启动时容易踩的坑:

  1. 文件锁冲突:多个服务同时写同一个文件,会导致锁等待。解决办法是让每个服务写自己的文件,或者用临时文件。
  2. 共享内存竞争:如果两个服务通过共享内存通信,一定要用信号量或互斥锁保护。
  3. SELinux 策略遗漏:并行启动时,服务访问文件的顺序不确定,可能导致某些路径没有被策略覆盖。
  4. 属性设置时机:用 on property 触发任务时,要确保属性设置不会因为竞争而丢失。

我曾经遇到过一个很隐蔽的问题:某个服务在启动时设置了 sys.service.ready=1,但另一个服务用 on property:sys.service.ready=1 等待。结果因为并行启动,第一个服务还没设置属性,第二个服务就已经检查过了,导致永远等不到。解决办法是用 init.svc.<service>=running 这种更可靠的触发条件。

好了,关于并行启动优化,今天就聊到这里。核心思路就是:能并行的尽量并行,但要注意依赖关系和 SELinux 策略。下一节我们会讲更具体的启动时间测量方法,到时候可以结合今天的内容一起实践。

一句话总结: 并行启动是缩短开机时间的利器,但要用好它,必须理解 init.rc 的 class 机制、on property 触发方式,以及 SELinux 策略对并发访问的影响。

并行启动优化核心逻辑 init.rc 并行服务 on property 触发 SELinux 策略影响 class core / main / late_start class_start 并行启动 属性变化触发动作 init.svc.xxx=running AVC 拒绝日志 type_transition 失效 核心目标:缩短开机时间,保证服务依赖正确

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