这一次,我们把Zygote进程的细节也说说

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.cppmain函数会根据传入的参数决定执行不同的初始化路径:

  • 如果程序名是"ueventd",则执行ueventd_main():负责处理设备热插拔事件
  • 如果第一个参数是"subcontext",则执行SubcontextMain():处理特定上下文的初始化
  • 如果第一个参数是"selinux_setup",则执行SetupSelinux():设置SELinux安全策略
  • 如果第一个参数是"second_stage",则执行SecondStageMain():执行第二阶段初始化
  • 如果没有匹配的参数,则执行FirstStageMain():执行第一阶段初始化

当init进程完成第一阶段初始化后,会通过exec系统调用重新启动init进程,并以"second_stage"参数执行自己,进而触发SecondStageMain方法.所以,init进程在合适时机让init.cppSecondStageMain方法被执行,完成init.rc的解析.

为什么得解析init.rc,解析而已,哪来触发

解答这个问题,我们得看init.rc文件干了什么事情? 点开init.rc文件能看到定义了启动阶段触发器,如on early-initon late-initon boot,指定一组在特定事件发生时要执行的命令

on late-init可以看到trigger zygote-start

好的,那什么时候执行late-init呢?

答案在SecondStafeMain方法内:当系统不是处于充电模式时,会触发late-init,原来如此

小结

目前追溯到这里,我们可以得到一个相对完整的链路,步骤如下:

  1. init进程(即main.cppmain方法)作为入口,根据条件判断,执行init.cppSecondStafeMain方法.
  2. 执行SecondStafeMain方法会触发LoadBootScripts方法.
  3. LoadBootScripts方法触发parser.ParseConfig("/system/etc/init/hw/init.rc");,开始对init.rc文件读取配置,并将这些配置转化为Action和Service对象.配置读取后init.cpp在非充电模式下触发on late-init
  4. 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.rcinit.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,本质上就是换个法子调用Systemgc方法和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....

相关推荐
蓝婷儿5 小时前
前端面试每日三题 - Day 32
前端·面试·职场和发展
EnticE15210 小时前
[高阶数据结构]二叉树经典面试题
数据结构·算法·面试
Attacking-Coder10 小时前
前端面试宝典---webpack面试题
前端·面试·webpack
前端小巷子13 小时前
CSS3 遮罩
前端·css·面试·css3
什码情况14 小时前
星际篮球争霸赛/MVP争夺战 - 华为OD机试真题(A卷、Java题解)
java·数据结构·算法·华为od·面试·机试
chenyuhao202415 小时前
链表面试题7之相交链表
数据结构·算法·链表·面试·c#
牛马baby16 小时前
Java高频面试之并发编程-16
java·开发语言·面试
Frankabcdefgh17 小时前
前端进化论·JavaScript 篇 · 数据类型
javascript·安全·面试·数据类型·操作符·初学者·原理解析
PgSheep20 小时前
一文通俗讲解MySQL数据库常见面试题-持续更新
java·数据库·mysql·面试