N年前会议室:
"请你介绍一下Zygote进程吧"
"好的,Zygote进程是Android中的第一个Java进程(即虚拟机进程),是所有Java进程的父进程,由init进程启动解析,(& (&(&............"

好好好,很好,回答不错.大家工作这么多年都多少遇到过这问题或者对Zygote的困惑,尽管现在网上也不缺相关的资料,但在这里还是想分享些近期反思整理过程中意外发现的小点,大家当个拓展就行.
Zygote进程是什么,能干嘛
没错,Zygote进程作为Android系统里触发的第一个Java进程,其作用从网上跟现在的Ai工具回答,大致包括以下内容:
- Java虚拟机预热:预先加载Java虚拟机(ART/Dalvik)并初始化核心类库,避免每个应用单独加载
- 系统服务启动:负责启动SystemServer进程,SystemServer进程又会启动各种系统服务
- 应用孵化器:通过fork机制快速创建新应用进程,子进程继承已加载的类和资源,大幅提高应用启动速度
- 共享资源:所有应用进程继承Zygote的地址空间,共享系统框架和核心库,节省内存
- 安全隔离:每个fork出的应用进程运行在独立的沙箱中,实现应用间的安全隔离
其实总结起来可以是:服务于Android框架加载,SystemServer启动和孵化应用进程.
怎样被触发创建:从native到Java层
"Java层从ZygoteInit.main方法开始"
"那么native层呢"
"从init.cpp解析init.rc开始."
"那具体点呢?"
"不知道啊,网上就这么写的"
"那你告诉我init.rc文件跟init.cpp文件的全路径是什么?我去看看"
"(别问了,真顶不住了)"

上面说法没太大问题,如果把init.cpp解析init.rc作为起点去追溯的话.但这里有几个问题我们要从源码来得到答案:
- 这些文件都在哪?
- 为什么从init.cpp开始?
- 为什么得解析init.rc?
- 为什么init.rc里没有涉及到ZygoteInit的调用?
文件在哪
这里给大家先解答,上述两个文件的全路径地址,源码查看在末尾.
init.cpp为/system/core/init/init.cpp
init.rc为/system/etc/init/hw/init.rc
为什么网上说从init.cpp开始
提供基于android15的系统源码,打开init.cpp文件,搜索对init.rc的相关操作,可以找到下面代码,注意方法名,parseConfig,这里是读文件

根据LoadBootScripts
方法,接着搜索LoadBootScripts
方法,确定在SecondStafeMain
方法

而SecondStafeMain
方法在哪调用,搜索结果显示在main.cpp里

点开main.cpp
文件,能看到SecondStafeMain
方法在second_stage
阶段

这里简单介绍一下init进程便于理解: 当系统启动时,Linux内核会创建init进程(PID 1)并执行此程序。main.cpp
的main
函数会根据传入的参数决定执行不同的初始化路径:
- 如果程序名是"ueventd",则执行
ueventd_main()
:负责处理设备热插拔事件 - 如果第一个参数是"subcontext",则执行
SubcontextMain()
:处理特定上下文的初始化 - 如果第一个参数是"selinux_setup",则执行
SetupSelinux()
:设置SELinux安全策略 - 如果第一个参数是"second_stage",则执行
SecondStageMain()
:执行第二阶段初始化 - 如果没有匹配的参数,则执行
FirstStageMain()
:执行第一阶段初始化
当init进程完成第一阶段初始化后,会通过exec系统调用重新启动init进程,并以"second_stage"参数执行自己,进而触发SecondStageMain
方法.所以,init进程在合适时机让init.cpp
的SecondStageMain
方法被执行,完成init.rc的解析.
为什么得解析init.rc,解析而已,哪来触发
解答这个问题,我们得看init.rc
文件干了什么事情? 点开init.rc
文件能看到定义了启动阶段触发器,如on early-init
、on late-init
、on boot
,指定一组在特定事件发生时要执行的命令



在on late-init
可以看到trigger zygote-start
好的,那什么时候执行late-init
呢?
答案在SecondStafeMain
方法内:当系统不是处于充电模式时,会触发late-init
,原来如此


小结
目前追溯到这里,我们可以得到一个相对完整的链路,步骤如下:
- 从
init进程
(即main.cpp
的main
方法)作为入口,根据条件判断,执行init.cpp
的SecondStafeMain
方法. - 执行
SecondStafeMain
方法会触发LoadBootScripts
方法. LoadBootScripts
方法触发parser.ParseConfig("/system/etc/init/hw/init.rc");
,开始对init.rc
文件读取配置,并将这些配置转化为Action和Service对象.配置读取后init.cpp
在非充电模式下触发on late-init
init.rc
内定义了在on late-init
触发事件zygote-start
.on zygote-start
内声明start zygote
(这里可以认为开始系统调用去创建Zygote进程了)





至此,文件路径,入口和解析原因我们已经完全清楚.
app_main.cpp:从start zygote到ZygoteInit.Java
但是到这里,我们先停住.
也没看到任何关于ZygoteInit.Java的相关调用呀
这是因为ZygoteInit.main()方法的调用发生在更深层次的实现中。如果别人告诉你,或者你告诉别人"一句话概括,反正就是会调用,信不信由你",那听着也觉得很虚是吧?来,继续看
具体在而是在被导入的特定的配置文件,如init.zygote64.rc
或init.zygote64_32.rc
文件中,这些文件中会定义类似这样的服务,这里我以init.zygote64.rc
文件内容为例子(因为好找),会看到

触发start zygote
时,通过启动app_process
程序,最终实现启动zygote服务的行为.而发生在app_process
的C++实现中 ,位于frameworks/base/cmds/app_process/app_main.cpp
文件

在这里开始看到我们熟悉的ZygoteInit
,要求条件zygote==true
,那什么时候会是true呢,解析argc参数里是否存在--zygote
参数,参考上述init.zygote64.rc
截图.就能确定结论:app_main.cpp内触发ZygoteInit.Java,并且携带参数开始main方法的执行.
被触发时干了什么
到这里,是不是开始进入大家熟悉的领域了,但这次你会更熟悉.打开ZygoteInit.java,可以看到

基于前面的rc文件,你能明确startSystemServer
,zygoteSocketName
值是什么,是true,是zygote,那enableLazyPreload呢,false,因为init.zygote64.rc
里面没写,嘿嘿.什么时候会是true呢,可以去看init.zygote64_32.rc
,但这个不是我们现在要讨论的,我们继续阅读源码.

从ZygoteInit.java的main方法,可以得知除了从args取参的行为,有下面内容:
- 加载android框架
gcAndFinalize
触发GC- 创建
ZygoteServer
对象 - 选择性执行
forkSystemServer
- 最终都执行关闭
ZygoteServer
对象内部的socket


preload:加载框架
在preload
方法内完成类,资源,lib库和字体资源的加载.


gcAndFinalize:预先GC腾出空间
运行几个特殊的GC,本质上就是换个法子调用System
的gc
方法和runFinalization
方法,最终还是要Runtime
单例对象来实现,来尝试清理几代软可达和最终可达对象以及任何其他垃圾.

创建ZygoteServer对象
根据判断尝试返回Runnable线程对象去执行后续流程,由于main方法的传入参数已知startSystemServer
为true,所以会执行forkSystemServer
,这能说明为什么SystemServer
进程会在zygote
进程里被创建.
forkSystemServer:创建SystemServer进程
核心行为就是创建SystemServer进程,并且返回pid,用于父子进程分叉进行后续业务.


linux里对于fork行为的执行提供了pid便于识别:
- 在父进程中,fork()返回子进程的PID(正数)
- 在子进程中,fork()返回0
- 如果返回负值,表示fork()失败
所以这里的父子进程是相对于Zygote进程和SystemServer进程而言,由于SystemServer进程是从Zygote进程fork而来,相当于复制了一个进程,但是需要让他们被区别,用于执行后续不同的流程.
后续
对于父进程,Zygote进程:返回为null作为返回值退出forkSystemServer
流程,关闭socket,让ZygoteInit类的main方法执行runSelectLoop
:开启一个while循环在zygote进程中开启循环,等待并处理来自ActivityManagerService的命令.当接收到启动应用程序的命令时,ZygoteConnection.processCommand
方法被调用.
这也是为什么我前面总结说Zygote进程的作用包括SystemServer启动和后续孵化应用进程.

对于子进程,SystemServer进程:关闭socket,执行handleSystemServerProcess
的结果作为返回值退出forkSystemServer流程(返回Runnbale不为null).

总结:
到这里,我们已完成对ZygoteInit.java的触发源头和触发内容的介绍.当时看到这里我还是有个疑问:Zygote进程被创建的时机究竟得从什么时候开始算?
前面提到几个点:
- 从触发角度:在late-init阶段执行trigger zygote-start命令时开始
- 从服务启动角度:执行start zygote命令时开始
- 从进程创建角度:init进程fork并exec执行app_process64时开始
- 从Java角度:调用ZygoteInit.main()方法时开始
总的来回答,会认为Zygote进程的启动应该从init进程执行start zygote
命令开始,这导致创建了一个新进程来运行app_process程序,最终执行ZygoteInit.main()方法.
补充:
Zygote进程作为Android中的第一个Java进程,那pid是0还是1?
都不是.
pid 0是固定分配给init进程的;
也不会是1,因为pid由Linux内核动态分配,依赖于系统启动顺序.启动Zygote进程之前,其他运行的进程可能占用了某些PID,所以不固定.
ZygoteInit的完整调用流程
内核启动 → init进程(FirstStageMain) → init进程重新执行(SecondStageMain) → 解析init.rc → on late-init → on zygote-start → start zygote → 执行app_process → app_process调用ZygoteInit.main()
引用链接
xrefandroid.com/android-15.... xrefandroid.com/android-15.... xrefandroid.com/android-15.... xrefandroid.com/android-15.... xrefandroid.com/android-15....