本章节学习关于 Zygote 的内容知识
一、Zygote 的作用是是什么?
启动 SystemServer (通过Zygote 来启动,可以继承Zygote 的准备好部分资源,包括常用类,JNI函数、主题资源、共享库,从Zygote 继承过来不需要再重新加载)
孵化应用进程
创建DVM (Dalvik 虚拟机) 和 ART
加载常用类、JNI函数、主题资源、共享库
zygote 启动之前的步骤:进程启动->准备工作->SocketLoop
二、Zygote 的启动流程
2.1、进程是怎么启动的?
系统 启动之后,第一个进程为 init 进程,init 进程启动后加加载 init.rc 配置文件。init.rc 配置文件中定义了需要启动的服务,如 Zygote,SystemManager 服务。init 进程加载配置后,通过 fork + execve 系统调用。
init.rc 启动配置
1 | service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server |
配置分析:
service zygote 表示 要启动的系统服务
因为进程的启动是通过 fork+execve 系统调用,需要传一个可执行程序路径+参数
则 /system/bin/app_process
表示 可执行程序路径
-Xzygote /system/bin --zygote --start-system-server
表示 参数
进程的启动方式有两种
- fork + handle
- fork + execve
两种方式都是通过 fork()
函数调用创建子进程 ,该函数被调用一次,但返回两次。其中一次返是子进程的返回,值为0;另一次返回是父进程返回,值为新进程(子进程)的id。将子进程id返回给父进程的理由是:因为一个进程的子进程可以多于一个,没有一个函数使一个进程可以获得其所有子进程的进程id。
对应子进程来说,之所以 fork 返回0给它,是因为它随时可以调用 getpid()
来获取自己的pid;也可以调用 getppid()
来获取父进程的id。
fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是相互独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置
(两进程的程序计数器pc值相同,也就是说,子进程是从fork返回处开始执行的),但有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。
fork 出来的子进程和父进程是继承关系,继承了大部分内容,并不是全部继承。
上图中 fork + execve 启动进程的方式,在 fork 子进程后,执行系统调用 execve(path,argv,env)
加载二进制程序,则从父进程继承的资源将会被新的二进制程序给替换掉。
进程信号处理-SIGCHLD
通常父进程 fork 出子进程后,如果子进程挂了,那么父进程将会受到 SIGCHLD 信号,比如 init 进程fork 出 zygote 进程,当 zygote 挂了,那么 init 进程会受到 SIGCHLD 信号,然后会重启 zygote 进程。
2.2、Zygote进程启动之后做了什么?
Zygote 进程启动之后,主要执行分为两个部分,主要分为native部分和 java 部分
2.2.1、native 部分
Zygote 进程启动后,在 native 部分执行主要有三件事,1、启动Android虚拟机;2、注册Android的 JNI 函数;3、进入Java世界。其目的就是为后面执行Java 做准备。
1 | int main(int argc, char *argv[]){ |
2.2.2、Java 部分
主要有三件事:
- 预加载资源,一些常用的类,主题相关资源、共享库,可以在子进程孵化时继承给他们。
- fork SystemServer(单独一个子进程)
- 进入Loop 循环,等待 socket 消息,与 SystemServer 通讯。
下面关于 framework 中 ZygoteConnection.java 的部分关键代码:
1 | boolean runOnce(){ |
三、要注意的细节
3.1、Zygote fork 要单线程
因为不管父进程有多少个线程,但子进程在创建时就只有一个线程,那对子进程来说,其他多的线程就不见了。这样都会导致其他问题,死锁,或者状态不一致。
所以在创建子进程时会停掉主线程之外其他线程,等创建完子进程后在把其他线程重启。
3.2、Zygote 的 IPC 没有采用 binder,而是使用本地 socekt
所有应用程序的binder 机制不是从 zygote 继承过来的,而是应用程序的进程启动之后自己启动的binder
四、问题思考
4.1、孵化应用进程这种事为什么不交给SystemServer来做,而专门设计一个Zygote?
我们知道,应用在启动的时候需要做很多准备工作,包括虚拟机,加载各类系统资源等等,这些都是非常耗时的,如果能在zygote里就给这些必要的初始化工作做好,子进程在fork 的时候就能直接共享,那么这样的话效率就会非常高。这个就是zygote 存在的价值,这一点 SystemServer 是替代不了的,主要是因为 SystemServer 里面跑了一堆系统服务,这些是不能继承到应用进程的。而且我们应用进程在启动的时候,内存空间处理必要的资源外,最后是干干净净的,不要继承一堆乱七八糟的东西。所以,不如给SystemServer 和应用进程里都要用到的资源抽出来单独放在一个进程里,也就是这 zygote 进程,然后 zygote 进程再分别孵化出SystemServer 进程和应用进程。孵化出来之后,SystemServer 进程和应用进程就可以各干各的事了。
4.2、Zygote的 IPC 通信机制为什么不采用binder?如果采用binder 的话会有什么问题么?
第一个原因,我们可以设想一下采用binder 调用的话该怎么做,首先 zygote 要启用 binder 机制,需要打开 binder 驱动,获得一个描述符,再通过 mmap 进行内存映射,还要注册binder 线程,这还不够,还要创建一个 binder 对象注册到 ServiceManager , 另外 AMS 要向 zygote 发起创建应用进程请求的话,要先从 ServiceManager 查询 zygote 的 binder 对象,然后再发起 binder 调用, 这来来回回好几趟非常繁琐,相比之下,zygote 和SystemServer 进程本来就是父子关系,对于简单的消息通信,用管道或者 socket 非常方便省事。
第二个原因:如果 zygote 启用 binder 机制, 在fork 出 SystemServer,那么SystemServer 就会继承了 zygote 的描述符已经映射的内存,这两个进程在 binder 驱动层就会共享一套数据结构,这显然不行的,所以还得先给原来的旧的描述符关掉,再重新启用一遍binder 机制,这个就是自找麻烦了。