在Android中,zygote是整个系统创建新进程的核心进程。zygote进程在内部会先启动Dalvik虚拟机,继而加载一些必要的系统资源和系统类,最后进入一种监听状态。在之后的运作中,当其他系统模块(比如 AMS)希望创建新进程时,只需向zygote进程发出请求,zygote进程监听到该请求后,会相应地fork出新的进程,于是这个新进程在初生之时,就先天具有了自己的Dalvik虚拟机以及系统资源。
| 关键类 | 路径 |
|---|---|
| init.rc | system/core/rootdir/init.rc |
| init.cpp | system/core/init/init.cpp |
| init.zygote64.rc | system/core/rootdir/init.zygote64.rc |
| builtins.cpp | system/core/init/builtins.cpp |
| service.cpp | system/core/init/service.cpp |
| app_main.cpp | frameworks/base/cmds/app_process/app_main.cpp |
| AndroidRuntime.cpp | frameworks/base/core/jni/AndroidRuntime.cpp |
| JniInvocation.cpp | libnativehelper/JniInvocation.cpp |
| ZygoteInit.java | frameworks/base/core/java/com/android/internal/os/ZygoteInit.java |
| ZygoteServer.java | frameworks/base/core/java/com/android/internal/os/ZygoteServer.java |
在Android系统中,JavaVM(Java虚拟机)、应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器。它通过fock(复制进程)的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动时会创建JavaVM,因此通过fock而创建的应用程序进程和SystemServer进程可以在内部获取一个JavaVM的实例拷贝。
Zygote触发
在分析init进程时,我们知道init进程启动后,会解析init.rc文件,然后创建和加载service字段指定的进程。zygote进程就是以这种方式,被init进程加载的。
在system/core/rootdir/init.rc的开始部分,可以看到:
1 | import /init.environ.rc |
init.zygote64_32.rc的代码如下所示:
1 | // 进程名称是zygote,运行的二进制文件在/system/bin/app_process64 |
start zygote
定义了service,肯定有地方调用 start zygote。在之前init解析的博客中,我们分析过init进程的启动。init进程启动的最后,会产生”late-init”事件。
1 | // Don't mount filesystems or start core system services in charger mode. |
对应于init.rc配置文件中,我们找到如下代码:
1 | # Mount filesystems and start core system services. |
app_process
从上面我们分析的init.zygote64.rc可以看出,zygote64启动文件的地址为app_process64。app_process64对应的代码定义在frameworks/base/cmds/app_process中
在app_main.cpp的main函数中,主要做的事情就是参数解析. 这个函数有两种启动模式:
- 一种是zygote模式,也就是初始化zygote进程,传递的参数有–start-system-server –socket-name=zygote,前者表示启动SystemServer,后者指定socket的名称(Zygote64_32)。
- 一种是application模式,也就是启动普通应用程序,传递的参数有class名字以及class带的参数。
两者最终都是调用AppRuntime对象的start函数,加载ZygoteInit或RuntimeInit两个Java类,并将之前整理的参数传入进去。
1 | \frameworks\base\cmds\app_process\app_main.cpp |
AndroidRuntime
由于AppRuntime继承自AndroidRuntime,且没有重写start方法,因此zygote的流程进入到了AndroidRuntime.cpp。
接下来,我们来看看AndroidRuntime的start函数的流程。
创建Java虚拟机
1 | /* |
跟一下jni_invocation.Init() /libnativehelper/JniInvocation.cpp
Init函数主要作用是初始化JNI,具体工作是首先通过dlopen加载libart.so获得其句柄,然后调用dlsym从libart.so中找到JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM、JNI_GetCreatedJavaVMs三个函数地址,赋值给对应成员属性,这三个函数会在后续虚拟机创建中调用。
1 | bool JniInvocation::Init(const char* library) { |
注册JNI函数
1 | void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) |
startReg首先是设置了Android创建线程的处理函数,然后创建了一个200容量的局部引用作用域,用于确保不会出现OutOfMemoryException,最后就是调用register_jni_procs进行JNI注册。
我们跟进startReg():
1 | /* |
反射启动ZygoteInit
继续分析AndroidRuntime.cpp的start函数:
1 | void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) |
可以看到,在AndroidRuntime的最后,将通过反射调用ZygoteInit的main函数。至此,zygote进程进入了java世界。
- 在Android中,每个进程都运行在对应的虚拟机上,因此zygote首先就负责创建出虚拟机。
- 然后,为了反射调用java代码,必须有对应的JNI函数,于是zygote进行了JNI函数的注册。
- 当一切准备妥当后,zygote进程才进入到了java世界。
ZygoteInit
1 | public static void main(String argv[]) { |
上面是ZygoteInit的main函数的主干部分,除了安全相关的内容外,最主要的工作就是注册server socket、预加载、启动system server及进入无限循环处理请求消息。
创建server socket
1 | /** |
我们跟踪LocalServerSocket():
1 | public LocalServerSocket(String name) throws IOException |
预加载
1 | static void preload(TimingsTraceLog bootTimingsTraceLog) { |
为了让系统实际运行时更加流畅,在zygote启动时候,调用preload函数进行了一些预加载操作。Android 通过zygote fork的方式创建子进程。zygote进程预加载这些类和资源,在fork子进程时,仅需要做一个复制即可。
这样可以节约子进程的启动时间。同时,根据fork的copy-on-write机制可知,有些类如果不做改变,甚至都不用复制,子进程可以和父进程共享这部分数据,从而省去不少内存的占用。
启动SystemServer进程
再来看看启动System Server的流程:
1 | /** |
1 | public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags, |
创建出SystemServer进程后,zygote进程调用ZygoteServer中的函数runSelectLoop,处理server socket收到的命令。
1 | /** |
从上面代码可知,初始时fds中仅有server socket,因此当有数据到来时,将执行i等于0的分支。此时,显然是需要创建新的通信连接,因此acceptCommandPeer将被调用。
我们看看acceptCommandPeer函数:
1 | /** |
从上面的代码,可以看出acceptCommandPeer调用了server socket的accpet函数。于是当新的连接建立时,zygote将会创建出一个新的socket与其通信,并将该socket加入到fds中。因此,一旦通信连接建立后,fds中将会包含有多个socket。
当poll监听到这一组sockets上有数据到来时,就会从阻塞中恢复。于是,我们需要判断到底是哪个socket收到了数据。
在runSelectLoop中采用倒序的方式轮询。由于server socket第一个被加入到fds,因此最后轮询到的socket才需要处理新建连接的操作;其它socket收到数据时,仅需要调用zygoteConnection的runonce函数执行数据对应的操作。若一个连接处理完所有对应消息后,该连接对应的socket和连接等将被移除。
总结
Zygote启动流程到此结束,Zygote进程共做了如下几件事:
- 创建AppRuntime并调用其start方法,启动Zygote进程。
- 创建JavaVM并为JavaVM注册JNI。
- 通过JNI调用ZygoteInit的main函数进入Zygote的Java框架层。
- 通过registerZygoteSocket函数创建服务端Socket,预加载类和资源,并通过runSelectLoop函数等待如ActivityManagerService等的请求。
- 启动SystemServer进程。