Android Zygote

1.Zygote

Zygote译为“受精卵”,在Android中,zygote进程主要负责孵化新进程,其他的应用进程都是由它孵化的。

安卓是Linux内核,安卓系统上运行的一切程序都是放在Dalvik虚拟机上的,Zygote也不例外,事实上,它是安卓运行的第一个Dalvik虚拟机进程。Zygote是由Linux内核启动的用户级进程Init创建的。

60217847d1154b6aa634f5889c07a818.png

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世界。接下来的流程如下图:

835bb781a5794b25bb27a8abf0cd409b.jpg

 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出来的应用进程都能共用一块内存。

0fe012d22aa34373ac026fe4da44cd21.jpg

 通过上图可以很容易理解在Zygote进程预加载系统资源后,然后通过它孵化出其他的虚拟机进程,进而共享虚拟机内存和框架层资源,这样大幅度提高应用程序的启动和运行速度。

③启动SystemServer

b9ae6f20438346789ed239f35022cbfc.jpg 4718c8314bc94f2c9415d97e9834f27f.jpg

 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的启动流程:

789a756232844da693dec72a474be40e.jpg

 

dbeb367eb65049e0889173200bd8aaf0.png

84ca3d8d2d704fc994fe1091a791448e.jpg

 

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方法在子进程中进行处理。

⑨然后继续沉睡,等待请求进行下一次分裂。

6298a2cc63874bd9b3a42133eeaeaac6.png

 

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出来的,这会有效地破坏地址空间布局随机化,这一技术是对抗代码注入攻击的重要安全手段。

 

 

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值