Android Framework | 白话Zygote

互联网上关于Zygote发动流程的文章已经汗牛充栋,其间不乏深入分析的精品。因而本文无意于从源码层面给出解读,而是希望站在一个更加微观的视角,写一点通俗易懂的言语。

Zygote,译为“受精卵”,是Android发动过程中第一个Java进程的名称。这个进程的使命只有一个:接纳system_server的请求,fork出新的运用进程。

作为体系中的第一个进程,init会依据rc文件履行一系列发动操作。其间有一项便是发动zygote进程。init会fork出一个子进程,然后在其间经过exec加载/system/bin/app_process可履行文件。这一点和其他纯native进程的发动(譬如surfaceflinger)并无二致。

/system/bin/app_process被加载后,进程便会运转可履行文件的main办法。其间首要会做两件事:

  1. 发动运转时。所谓运转时,其实就是Dalvik字节码可以被正确理解并运转的环境,它存在于进程之中。而这种环境通常由两部分组成:一部分担任目标的创立与回收,譬如类的加载器,废物回收器等等。另一部分担任程序逻辑的运转,譬如即时编译体系,解释器等等。其实我不太喜爱“创立虚拟机”这样的说法,由于它简单让人产生误解,以为虚拟机是一个独立于进程之外的实体。
  2. Fork出system_server进程。子进程履行完SpecializeCommon函数后进入到com.android.server.SystemServer的main办法中,之后便进入system_server的运转逻辑。

做完这两件事情后,zygote便会将自身挂起,等候来自于system_server的进程发动的请求。

之所以采用fork的方式来发动进程,是由于子进程可以继承父进程的地址空间和运转环境,从而节约子进程的发动时刻。因而,问题的要害变成:一切运用进程在发动时刻有哪些操作是共性的?而这些共性的操作都可以放到zygote中去提早处理。

最大的共性操作便是发动运转时。其间会预先加载一些boot class,这些类运用频繁,因而在zygote中提早加载好,可以极大地提高后续运用的发动速度。类的加载过程大体上可以分为两步,第一步是创立类在内存中的实体,第二步是类的初始化,譬如运转static代码块。这也是为什么Android中既需求preloaded-classes,也需求boot.art的原因。即使boot.art和preloaded-classes中包含的是相同的类,但彼此做的事情其实是不同的。boot.art为image文件,其间有很多的类目标(art::mirror::Class目标),一旦加载到内存中,相当于这些类目标已经创立结束,也即加载的第一步。而preloaded-classes会调用Class.forName去初始化这些类,属于类加载的第二步。

当zygote在fork的时候,它会保持自己为单线程状况。这是由于多线程下的fork很简单在子进程中产生死锁、状况紊乱等一系列问题,而究其根源,是由于即使父进程为多线程,fork之后的子进程也只会有一个线程。这种多对一的转化便会遗漏掉很多同步的信息。因而zygote在fork之前,会封闭HeapTaskDaemon之类的线程。

另外,正是由于fork的这个动作,所以zygote才没有选用Binder作为其跨进程通讯的方式(这一点我在19年邮件咨询过Google担任Binder的两位工程师)。由于Binder压根就不支撑fork,除非fork后调用exec,敞开全新的进程环境。这首要是由于Binder中目标生命周期的管理比较杂乱,而假如为了支撑fork,那么它的设计将会更加杂乱。

当下的Android版别根本都一起支撑32位和64位的运用。鉴于此,体系中也存在两个版别的zygote进程,一个为zygote32,作为一切32位运用的父进程;另一个为zygote64,作为一切64位运用的父进程。不过跟着Android的发展,32位运用很快会被筛选出历史舞台。

为了更进一步地提高运用的发动功能,Android 10开始在zygote中引入了一种新的发动机制:USAP。详细的细节可以参阅之前的文章。

以上,便是我关于zygote的浅显认识。