2、Zygote启动流程(上):init.rc中Zygote的启动脚本解析、service关键字含义、class main与class late_start的区别

好,咱们正式开始啃Zygote。这一章我打算从init.rc入手,把Zygote的启动脚本掰开揉碎讲清楚。

说实话,我刚开始看Android源码那会儿,看到init.rc里一堆service、class、on开头的指令,头都大了。后来做项目多了才发现,这套机制其实设计得很巧妙。你想想看,整个Android系统启动,从内核到用户空间,全靠init进程和这些rc脚本串联起来。

2.1 init.rc中Zygote的启动脚本长什么样?

先上代码。这是Android 12里Zygote的启动脚本,我挑核心部分说:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

嗯,这里要注意,不同Android版本脚本略有差异。比如Android 8.0之后加入了writepid,Android 10之后又加了task_profiles。但核心结构没变过。

2.2 service关键字到底在说什么?

service是init.rc里最核心的关键字之一。它的作用很简单——告诉init进程:你给我启动这个程序,并且盯着它,挂了就重启。

我个人习惯把service理解成「守护声明」。你写一行service zygote ...,就等于跟init说:「这个叫zygote的进程,你帮我管着。」

service后面跟的选项,每个都有讲究:

选项 含义 我的经验
class main 属于main类服务 系统核心服务都用这个
priority -20 进程优先级,-20最高 Zygote必须抢占CPU
user root 以root身份运行 早期需要权限,后面会降权
socket zygote stream 660 root system 创建名为zygote的socket 这是Zygote和AMS通信的通道
onrestart 重启时执行的动作 我曾经漏配这个导致系统重启后相机打不开
writepid 将PID写入指定cgroup Android 8.0之后引入,用于CPU管控

这里我特别想提一下onrestart。你看Zygote挂了之后,要重启audioserver、cameraserver、media、netd。为什么?因为这些服务都依赖Zygote创建的socket通信。Zygote一挂,socket就断了,不重启它们,整个系统就瘫痪了。

避坑指南:我曾经在定制系统时,为了「优化」把onrestart里的audioserver去掉了。结果Zygote异常重启后,音乐App还能打开,但没声音。查了两天才发现是socket连接没重建。所以,onrestart里的服务一个都不能少

2.3 class main vs class late_start,到底差在哪?

这是很多初学者容易搞混的地方。我直接说结论:

  • class main:核心服务,系统启动时立即启动,且必须启动成功
  • class late_start:非核心服务,等系统就绪后再启动,启动失败不影响系统运行

为什么会这样设计?你想想看,Zygote是Java世界的起点,它挂了整个Android Framework就没了。所以它必须是main类,init进程会保证它优先启动。

而像logdservicemanager这些,虽然也重要,但可以稍微晚一点。late_start类的服务会在on late-init触发器里统一启动。

看个实际例子:

# 这是main类的典型服务
service zygote /system/bin/app_process ...
    class main

# 这是late_start类的典型服务
service logd /system/bin/logd ...
    class late_start

启动顺序上,init进程会先处理所有class main的服务,等它们都起来了,再处理class late_start。这个顺序是写死在init进程代码里的。

核心区别总结:
  • main类:同步启动,阻塞等待,失败则系统重启
  • late_start类:异步启动,不阻塞,失败只打log
  • main类服务数量少但关键,late_start类服务数量多但可降级

2.4 启动脚本的执行流程

光看脚本定义还不够,你得知道init进程是怎么执行它的。我简化一下流程:

  1. init进程解析init.rc,遇到service zygote,创建对应的service结构体
  2. init进程遇到on early-init触发器,做一些早期初始化
  3. 遇到on init触发器,挂载文件系统、创建目录
  4. 遇到on late-init触发器,开始启动服务
  5. on late-init里,先执行class_start main,启动所有main类服务
  6. Zygote进程被fork出来,开始执行app_process的main函数
  7. main类服务启动完成后,执行class_start late_start

这里有个细节:class_start是init进程的一个内置命令,它会遍历所有属于该class的服务,逐个启动。启动顺序就是service在rc文件里出现的顺序。

小技巧:如果你在调试启动问题,可以用adb shell dmesg | grep init查看init进程的日志。它会打印出每个服务的启动时间和状态。我曾经靠这个定位过一个第三方服务卡住导致Zygote启动延迟的问题。

2.5 一个常见的坑:class main服务不能随便加

我记得有一次,项目里有个同事想把一个自定义服务也设成class main,理由是「让它早点启动」。我赶紧拦住了。

为什么?因为main类服务是同步启动的。init进程会一个一个启动它们,每个都要等返回。如果你加了一个会阻塞的服务,整个系统启动就被拖慢了。更严重的是,如果这个服务启动失败,init进程会触发系统重启。

所以,只有真正核心的、系统必须依赖的服务才放main类。其他服务,哪怕是系统服务,也尽量用late_start或者disabled(手动触发)。

Zygote之所以是main类,是因为它承担着孵化所有Java进程的职责。没有Zygote,SystemServer起不来,App也起不来。说白了,它就是Android的「母体」。

2.6 本章小结

这一章我们干了三件事:

  • 解析了Zygote在init.rc里的完整启动脚本
  • 搞清楚了service关键字每个选项的实际作用
  • 对比了class main和class late_start的本质区别

下一章,我会带你走进Zygote的main函数,看看app_process启动后到底发生了什么。嗯,那里才是真正精彩的地方。