init.zygote64.rc 里写得很清楚:init 启动的可执行文件是 /system/bin/app_process64。但开机后执行 ps,我们更熟悉的名字却是 zygote64。Java 调用栈的入口又变成了 ZygoteInit.main()。
于是很容易画出一条错误链:
init → app_process 进程 → ART 进程 → Zygote 进程
先钉住最容易错的身份变化:
app_process64是 init 执行的 Native 可执行文件;它在同一个 Linux 进程里创建 ART、注册 Framework JNI,再调用ZygoteInit.main()。这个进程根据--zygote参数把可见进程名改成zygote64,随后长期扮演 Zygote。Native 进入 Java 不会凭空创建第二个进程。
本文只追一条身份转换链:
init Service
→ fork
→ exec app_process64
→ app_main.cpp:main()
→ AndroidRuntime::start()
→ 创建 ART / 注册 JNI
→ ZygoteInit.main()
→ Zygote
Zygote 的预加载、fork、普通 App 创建协议分别留给后续文章。
源码基线: Android 16 QPR2
android-16.0.0_r4,frameworks/basecommit45034f0663f960d9ee5fb0a101a4732b71f6e2f4,system/corecommite9d1fa3705d7fbd0ac1c942bc4c276161bfbfed1。
一、rc 究竟执行了什么?
64 位主 Zygote 的配置入口在 rootdir/init.zygote64.rc:
service zygote /system/bin/app_process64 -Xzygote /system/bin \
--zygote --start-system-server --socket-name=zygote
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
这段配置同时出现了三个不同层面的名字:
| 名字 | 所属层面 | 它回答什么 |
|---|---|---|
zygote | init Service 名 | init 用什么名字管理、查询和重启这个服务 |
/system/bin/app_process64 | 可执行文件路径 | fork 后的子进程要 exec 哪个 ELF |
--zygote | 程序参数 | app_process64 进入哪种运行模式 |
所以,getprop init.svc.zygote 查询的是 init Service 状态;/proc/<pid>/exe 指向的是可执行文件;ps 展示的进程名还可能被程序主动修改。三者不要求相同。
二、从 init 到 Zygote,哪里真正创建了新 PID?
上一篇已经看到,init 在 Service::Start() 中准备 socket、凭据和环境,然后 fork/clone。子分支进入 RunService(),最终执行 execv():
init,PID 1
│
├─ fork
│ └─ 新子进程,先继承 init 的地址空间快照
│
└─ 子进程 exec /system/bin/app_process64
└─ 旧地址空间被 app_process64 ELF 替换
这里要同时记住两条 Unix 语义:
fork:创建新进程,返回两次;
exec:不创建新 PID,只用新程序替换当前进程映像。
因此,在本篇追踪的“init 创建 app_process 子进程,随后同一 PID 进入 Zygote 角色”这段路径中,init 的 fork/clone 是唯一创建新 PID 的动作。exec app_process64、创建 ART 与调用 ZygoteInit.main() 都发生在这个子进程中;除非源码再次调用 fork/clone,否则不能仅凭“函数从 C++ 进入 Java”推导出新进程。后续 Zygote fork system_server 和 App 属于下一段创建路径,不在这句话的限定范围内。
三、app_process 变成 Zygote 时,为什么 PID 没变?
可以把整段变化拆成六层:
| 层 | Android 16 中的实体 | 是否创建新进程 |
|---|---|---|
| 文件 | /system/bin/app_process64 | 否,它是被 exec 的 ELF |
| Native 入口 | app_main.cpp:main() | 否 |
| Native 运行时封装 | AppRuntime : AndroidRuntime | 否,它是当前进程里的 C++ 对象 |
| Managed Runtime | ART VM | 否,它在当前进程内创建 |
| Java 入口 | ZygoteInit.main() | 否,它是 VM 中执行的方法 |
| 长期角色 | Zygote 进程 | 否,还是原来的 PID |
这张表也解释了一个常见语言陷阱:
“app_process 启动了 Zygote”
如果它表示“app_process 的 main 完成运行时初始化并进入 ZygoteInit”,可以接受;如果它暗示“app_process 又 fork 出一个 Zygote 进程”,就是错误的。
四、app_main.cpp 怎样决定进入 Zygote 模式?
app_process64 并非 Zygote 专用。它还可以按参数运行普通 Java 类。决定分支的是 app_main.cpp 对参数的解析。
1. --zygote 决定入口类
核心参数来自 rc:
-Xzygote
/system/bin
--zygote
--start-system-server
--socket-name=zygote
app_main.cpp 识别 --zygote 与 --start-system-server,并把后者传给 Java 层。Zygote 分支最终调用:
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
非 Zygote 的工具模式则会进入指定 Java 类。也就是说,Zygote 不是另一个 ELF;它是 app_process 可执行文件的一种长期运行模式。
2. --start-system-server 不是“现在另起一个程序”
这个标记只告诉后续 ZygoteInit.main():完成预加载后,还要走一次 forkSystemServer()。真正 fork system_server 的代码在 Java/Native Zygote 路径中,并不发生在 app_main.cpp 解析参数的这一刻。
3. --socket-name=zygote 选定主 socket 名称
主、次 Zygote 以及不同 ABI 组合可以使用不同 socket。参数在 Java Zygote 参数解析阶段决定要注册/接管哪个服务端 socket;它同样不会创建新进程。
五、为什么 ps 里显示的是 zygote64?
app_main.cpp 在 Zygote 分支调用 setArgv0(),64 位构建通常把可见名称改成 zygote64,32 位构建则使用 zygote。
这是一种“修改进程可见标题”的动作,不是 exec,更不是 fork:
同一个 PID
executable:/system/bin/app_process64
启动时 argv[0]:/system/bin/app_process64
后续 nice/process name:zygote64
不同命令读取的字段也可能不同:
ps 的 NAME / CMD / ARGS;
/proc/<pid>/comm;
/proc/<pid>/cmdline;
/proc/<pid>/exe。
所以诊断时不能只截一列就断言“执行文件已经变成 zygote64”。在 user build 上,/proc 访问和 ps 列支持还可能受权限、toolbox/toybox 版本影响。
六、AndroidRuntime::start() 怎样把 Native 进程带进 Java?
AppRuntime 是 AndroidRuntime 的子类,负责把通用 Native 运行时启动逻辑接到 app_process/Zygote 特有回调。真正的桥梁位于 AndroidRuntime::start()。
可以压缩成四步:
1. startVm()
创建并配置 ART VM
2. onVmCreated()
让 AppRuntime 在 VM 已存在后完成自身准备
3. startReg()
注册 Android Framework 的 Native 方法
4. FindClass + CallStaticVoidMethod
查找 ZygoteInit,并调用它的 static main(String[])
1. startVm:ART 是进程内运行时
startVm() 组装运行时参数并创建 Java VM。ART 不是一个叫“ART”的守护进程;它是当前 Zygote 进程里的 Managed Runtime。
2. startReg:让 Framework Java 能调用 Native 实现
startReg() 批量注册 Framework JNI。没有这一步,许多 android.* / com.android.* 类即使能被加载,也无法落到对应 Native 实现。
JNI 注册的对象映射、nativePollOnce() 等机制不是本文主线,可接着阅读 JNI 机制(一)nativePollOnce() 到底去了哪里?。
3. CallStaticVoidMethod:从 Native 主动进入 Java main
源码通过 JNI 找到 com.android.internal.os.ZygoteInit,调用其 main()。这只是当前线程开始执行 Managed Code:
Linux 进程边界:没有变化
Linux PID:没有变化
当前线程:仍是同一条启动线程
执行语言:C++ → Java
把“跨语言边界”误读成“跨进程边界”,是理解 Zygote 时最常见的误解之一。
七、进入 ZygoteInit.main(),进程就已经完成 Zygote 初始化了吗?
还没有。ZygoteInit.main() 是初始化入口,不是完成标记。ZygoteInit.main() 会继续完成:
解析 Zygote 参数;
注册 Zygote server socket;
预加载 classes、resources、shared libraries 等;
按 --start-system-server 决定是否 fork system_server;
父进程进入 ZygoteServer.runSelectLoop();
等待普通 App 的进程创建请求。
因此,“成为 Zygote”不是一次内核级身份转换,而是这一个 app_process 进程完成初始化、开始承担进程孵化器职责后的语义名称。
八、Binder 线程池不属于父 Zygote 的无条件初始化
Zygote 父进程不会在进入 ZygoteInit.main() 时无条件启动普通 Binder 线程池;线程池是在 fork 后 child 的通用初始化路径中建立。完整调用链及其与 SystemServer.main() 的先后关系,以 第五篇:system_server 的出生路径为唯一事实源,本篇先不展开。
九、不要从 Java main 画出一个不存在的 PID
| 结论 | 本文证据能否证明 | 说明 |
|---|---|---|
init exec 的是 app_process64 | 能 | rc 的 service pathname 直接给出 |
| app_process 与 Zygote 必然是两个 PID | 不能,而且源码反证 | app_main 直接进入 ZygoteInit.main(),中间没有 fork |
| ART 是当前进程内创建的运行时 | 能 | AndroidRuntime::start() 调用 startVm() |
zygote64 一定是设备上的 ELF 文件名 | 不能 | 它通常是被设置的进程名;可执行文件仍是 app_process64 |
| 所有设备都只有一个 Zygote | 不能 | ABI 与产品配置可启用主/次 Zygote |
| 看到 Java main 就意味着创建 Java 进程 | 不能 | Native 可在当前线程通过 JNI 调用 Java 方法 |
不要说:
init 启动 app_process,然后 app_process 再启动一个 Zygote 进程。
更准确的说法是:
init fork 子进程并 exec
/system/bin/app_process64;app_process 根据--zygote参数在当前进程内创建 ART、注册 Framework JNI并调用ZygoteInit.main(),随后这个同一 PID 的进程以zygote64名称长期承担 Zygote 职责。
当前 PID 的身份变化已经说清楚。接下来的问题转向进程模型:既然 Zygote 终究要 fork,为什么它不立刻 fork,而要先执行一段看起来很重的 preload?
源码定位
rootdir/init.zygote64.rc:init Service 与 app_process 参数。app_main.cpp:参数解析、进程名与runtime.start()。AndroidRuntime.cpp:创建 VM、注册 JNI、调用 Java main。ZygoteInit.java:Zygote Java 入口。