1.Zygote
Zygote译为“受精卵”,在Android中,zygote进程主要负责孵化新进程,其他的应用进程都是由它孵化的。
安卓是Linux内核,安卓系统上运行的一切程序都是放在Dalvik虚拟机上的,Zygote也不例外,事实上,它是安卓运行的第一个Dalvik虚拟机进程。Zygote是由Linux内核启动的用户级进程Init创建的。
zygote的作用:
①创建并启动SystemServer进程
②fork新的应用进程
Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程时,都会将虚拟机实例复制到新的应用程序进程里,从而使每一个应用程序进程都有一个独立的Dalvik虚拟机实例。这样省去了每个应用程序在启动时单独运行和初始化一个虚拟机,大大降低系统性能。而且通过zygote孵化出其他的虚拟机进程,还可以共享虚拟机内存和框架层资源,大幅度提高应用程序的启动和运行速度。
Zygote为孵化的应用程序提供了几个基础资源:
①常用类:Android的java类库
②JNI函数
③主题资源:比如theme图片,占用10M+的内存空间,这些内存是系统共享的
④共享库
2.zygote的启动
zygote是由Init进程(pid=1)解析init.rc脚本启动的。Zygote进程在Init进程启动过程中被以service服务的形式启动。
从rc文件中可以看出:
①zygote的进程优先级最高,priority被设成-20。这是因为Android的启动非常依赖于SystemServer的启动,而SystemServer是zygote fork出来的,因此需要将zygote优先级设成最高的。
②zygote在启动时创建了两个socket(zygote和usap_pool_primary),用于进程间的通信。
③zygote是Android非常重要的核心服务之一,当其因异常退出后,Init会将其重新启动,并将一些相关的节点和服务重新设置。
zygote的启动实际上分成了两部分:SystemServer的启动、ZygoteServer的启动。两者前面是在同一个进程中执行的,当虚拟机创建完成后才正式分离。
zygote是通过app_process启动,入口就是app_process的main函数:
#frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[]) {
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
…
runtime.start( "com.android.internal.os.ZygoteInit", args, zygote);
…
}
创建AndroidRuntime对象(AndroidRuntime继承自AppRuntime),这里面主要的动作就是启动虚拟机。然后在虚拟机中运行ZygoteInit,ZygoteInit是java写的,即这一步Zygote就从native世界进入到了Java世界。接下来的流程如下图:
Zygote的java世界入口就是ZygoteInit类的main方法:
#frameworks/base/core/java/com/android/internal/os/ZygoteInit.java:
public static void main(String argv[]) {
……
zygoteServer = new ZygoteServer( isPrimaryZygote); //1.创建ZygoteServer
zygoteServer.registerServerSocket( socketName); //2.创建一个Server端的Socket
preload(bootTimingsTraceLog); //3.加载进程的资源和类
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); //4.启动SystemServer
if (r != null) { //forkSystemServer返回不为空说明是子进程,即SystemServer进程
r.run();
return;
}
}
//5.启动一个死循环监听来自Client端的消息
caller = zygoteServer.runSelectLoop(abiList);//这里是父进程,即zygote进程
if (caller != null) {
caller.run();
}
}
ZygoteInit的main()方法是Android启动中的第一个Java进程的主方法。它主要完成几件事情:
①注册一个socket
在main方法里,通过registerServerSocket方法创建一个Server端的socket,这个name为zygote的socket用于等待AMS请求Zygote创建新的应用程序进程。
Zygote作为孵化器,跟其他进程间的通讯不是通过binder,而是通过socket。一旦有新进程需要运行,系统会通过这个socket跟Zygote通讯,由zygote完成进程孵化过程。
②预加载各类资源
preload函数用于加载虚拟机运行时所需的各类资源。
在ZygoteInit中,一般都会调用preload()方法将Android Java层应用常用的资源预加装。原因是:
1)Android是基于Linux系统开发的,底层就是Linux操作系统,而Linux进程的fork机制有一个特点就是写时拷贝机制,即fork出来的进程最初是共用一块内存,只有当发生写操作时,才会将对应的内存块进行拷贝修改,而一些只读的内存块则会所有fork出来的进程共享。
2)preload()方法所加装的东西主要有以下几类:常用类文件、Resources资源、HALs资源、opengl、webview等。这些资源一般都是只读的,不会进行修改,而且是很多应用都可能会用到的。因此预先加载后所有由zygote fork出来的应用进程都能共用一块内存。
通过上图可以很容易理解在Zygote进程预加载系统资源后,然后通过它孵化出其他的虚拟机进程,进而共享虚拟机内存和框架层资源,这样大幅度提高应用程序的启动和运行速度。
③启动SystemServer
SystemServer是通过forkSystemServer()方法fork出来并开始执行。
private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) {
int pid;
pid = Zygote.forkSystemServer(
parsedArgs.mUid, parsedArgs.mGid,
parsedArgs.mGids,
parsedArgs.mRuntimeFlags,
null,
parsedArgs.mPermittedCapabilities,
parsedArgs.mEffectiveCapabilities);
if (pid == 0) { //子进程,即SystemServer进程
zygoteServer.closeServerSocket();
return handleSystemServerProcess( parsedArgs); //forkSystemServer返回不为空说明是子进程
}
return null;//forkSystemServer返回为空说明是zygote进程
}
forkSystemServer()主要做了两件事:①调用Zygote.forkSystemServer()方法fork一个新的进程出来。②fork()后的子进程是SystemServer进程,等待zygote的启动完成,然后执行真正的SystemServer代码。
Zygote.java:
public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities,long effectiveCapabilities){
ZygoteHooks.preFork(); //将Daemons线程关掉,虚拟机进行一些copy前的处理
resetNicePriority(); //将进程的优先级设回普通的级别
int pid = nativeForkSystemServer(uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities); //调用native层方法fork一个新的进程。该方法主要调用Linux的fork系统调用,fork一个新的进程,子进程的pid为0,父进程返回子进程的pid值
if (pid == 0) { //子进程
Trace.setTracingEnabled(true, runtimeFlags);
}
ZygoteHooks.postForkCommon(); //重新启动Daemons线程,虚拟机进行copy后的处理
return pid;
}
注:fork操作是不会将进程中所有线程拷贝的,只会拷贝当前线程。
再回到ZygoteInit.forkSystemServer()方法,此时两个进程都会返回到此处,然后两个进程到这就分道扬镳了。SystemServer进程将ZygoteServer的socket关闭,然后调用handleSystemServerProcess()方法去执行SystemServer的源码。
ZygoteInit.forkSystemServer()返回一个runnable,返回到ZygoteInit.main()方法后,SystemServer进程就会继续执行这个runnable。
zygote在fork进程后,使用runnable的方式返回到main()方法后才执行的原因?减少线程堆栈的长度,因为fork后进程执行的内容与之前zygote的堆栈内容并无关系,因此不需要为每个进程都保留前面的堆栈信息,而且还能减少进程的堆栈容量限制。
④进入Loop循环 ZygoteServer.runSelectLoop
当zygote进程返回到main()方法后,就会继续执行ZygoteServer.runSelectLoop()方法,这个方法是一个死循环,是不断循环执行命令的方法。执行runSelectLoop()方法是为了等待消息去创建应用进程。
runSelectLoop()方法主要做两件事:
①每次循环都重新构建监听文件列表,主要是ZygoteServer的socket文件(ZygoteServer的socket和其他应用进程连接过来的socket)和usap文件节点。
②监听文件列表,并从中获取命令执行。
一张图看看Zygote的启动流程:
3.总结
Zygote创建Java世界的步骤:
①第一天:创建AppRuntime对象,并调用它的start。此后的活动则由AppRuntime来控制。
②第二天:调用startVm创建Java虚拟机,然后调用startReg来注册JNI函数。
③第三天:通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了Java世界。然而在这个世界刚开创的时候,什么东西都没有。
④第四天:调用registerZygoteSocket,通过这个函数,它可以响应子孙后代的请求。同时Zygote调用preloadClasses和preloadResources,为Java世界添砖加瓦。
⑤第五天:Zygote觉得自己工作压力太大,便通过调用startSystemServer分裂一个子进程system_server来为Java世界服务。
⑥第六天:Zygote完成了Java世界的初创工作,它已经很满足了。下一步该做的就是调用runSelectLoopMode后,便沉沉地睡去了。
⑦以后的日子:Zygote随时守护在我们的周围,当接收到子孙后代的请求时,它会随时醒来,为它们工作。
⑧有一天,他的孩子SystemServer发送了个请求,Zygote苏醒,Zygote调用ZygoteConnection的runOnce函数。在runOnce方法中Zygote分裂一个子进程(App进程),然后在调用handleChildProc方法在子进程中进行处理。
⑨然后继续沉睡,等待请求进行下一次分裂。
4.面试问题
①zygote通信为何用socket而不是binder
Zygote是通过fork来创建新进程的,而binder是多线程的,有可能造成死锁。
在POSIX标准中,fork的行为是这样的:复制整个用户空间的数据(通常使用copy-on-write的策略,所以可以实现的速度很快)以及所有系统对象, 然后仅复制当前线程到子进程。这里:所有父进程中别的线程,到了子进程中都是突然蒸发掉的。
假如父进程在获取到锁的情况下,fork了一个子进程。子进程的内存中,这个锁的状态是上锁状态。子进程仅运行了fork所在的这个线程,其它线程没有运行,当它尝试获取锁时,就发生了死锁
②为何要通过Zygote来孵化程序,而不是由其他进程直接创建?
主要有两个好处:
1)缩短应用的启动时间
无论哪个app,所有的虚拟机都必须按照某种一样的而且是确定的方式初始化。Zygote预加载了各种资源,创建了虚拟机。由Zygote创建的进程能够继承这些资源,不用再重新创建。加载app的类只是最后一步而已。
2)优化共享内存
所有虚拟机都是从Zygote fork出来的,所以它们能够享受到由内核实现的内存共享的优势。比如Zygote预加载的各类资源,比如theme主题图片,所有的进程都是共享的,在物理内存中只需要保存一份。
③Zygote这一设计有什么缺点?
所有应用进程都是从同一个进程fork出来的,这会有效地破坏地址空间布局随机化,这一技术是对抗代码注入攻击的重要安全手段。