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就断了,不重启它们,整个系统就瘫痪了。
2.3 class main vs class late_start,到底差在哪?
这是很多初学者容易搞混的地方。我直接说结论:
- class main:核心服务,系统启动时立即启动,且必须启动成功
- class late_start:非核心服务,等系统就绪后再启动,启动失败不影响系统运行
为什么会这样设计?你想想看,Zygote是Java世界的起点,它挂了整个Android Framework就没了。所以它必须是main类,init进程会保证它优先启动。
而像logd、servicemanager这些,虽然也重要,但可以稍微晚一点。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进程是怎么执行它的。我简化一下流程:
- init进程解析init.rc,遇到
service zygote,创建对应的service结构体 - init进程遇到
on early-init触发器,做一些早期初始化 - 遇到
on init触发器,挂载文件系统、创建目录 - 遇到
on late-init触发器,开始启动服务 - 在
on late-init里,先执行class_start main,启动所有main类服务 - Zygote进程被fork出来,开始执行app_process的main函数
- 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启动后到底发生了什么。嗯,那里才是真正精彩的地方。