Android-深入理解zygote

Posted 天津 唐秙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android-深入理解zygote相关的知识,希望对你有一定的参考价值。

文章目录

1. zygote

1.1 zygote分析

  zygote最初的名字叫做app_process,这个名字在android.mk文件中指定的,但是在运行过程中,app_process通过Linux下的pctrl系统调用将自己的名字换成了zygote,代码如下:


  zygote的这个main很简单,其重要的功能是由AppRuntime的start来完成的

1.2 AppRuntime分析

  AppRuntime类的声明和实现均在App_main.cpp中,它是从AppRuntime类派生出来的,AppRuntime重载了onStarted、onZygoteInit和onExit函数

  前面的diamagnetic调用了AppRuntime的start函数,start函数使用的是基类AppRuntime的start


  通过上面的代码,有三个关键点:
    1.创建虚拟机
    2.注册JNI函数
    3.通过JNI调用Java函数,在调用ZygoteInit的main函数之后,zygote便进入了Java世界

1.2.0.1 创建虚拟机-startVm

  调用JNI的虚拟机创建函数,但是创建虚拟机时的一些参数是在startVm中确定的


  关于dalvik虚拟机的详细参数,可以参见Dalvik/Docs/Dexopt.html中的说明

1.2.0.2 注册JNI函数-startReg

  下一步需要给这个虚拟机注册一些JNI函数,因为后续Java世界用到的一些函数是采用native方式实现的,所以才必须提前注册这些函数,startReg函数如下:

  register_jni_procs代码如下

static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)

	for(size_t i = 0; i < count; i++)
	
		if(array[i].mProc(env) < 0)
		
			return -1;
		
	
	return 0;

  全局数组的gRegJNI变量

  REG_JNI是一个宏,宏里面包括的就是那个mProc函数

  mProc就是为了Java类注册了JNI函数,到目前为止,JNI函数已经注册,下一步就要分析CallStaticVoidMethod,通过这个函数,我们将进入Android打造的Java世界,并且最好情况就是永远不会回到Native世界

1.2.1 Welcome to Java World

  根据前面的分析可知,CallStaticVoidMethod最终将调用com.android.internal.os.ZygiteInit的main函数

  在ZygoteInit的main函数中,我们列出了5大关键点,分别在上图中标记

1.2.1.1 建立IPC通信服务端-registerZygoteSocket

  zygote及系统中其他程序的通信没有使用Binder,而是采用了基于AF_UNIX类型的Socket,registerZygoteSocket函数的使命正是建立这个Socket,代码如下:

  registerZygoteSocket就是创建一个服务端的Socket,考虑两个问题:1.谁是客户端?2.服务端会怎么处理客户端的消息?

1.2.1.2 预加载类和资源

  我们现在要分析的就是preloadClasses和preloadResources函数,先看preloadClasses:

  preloadClasses看起来非常简单,但是需要预先加载很多个类,preloadClass函数的执行时间比较长,这是导致Android系统启动慢的原因之一。
  preloadResources和preloadClass类似,主要是加载framework-res.apk中的资源,在UI编程中常使用的com.android.R.XXX资源是系统默认的资源,它们就是由zygote加载的。

1.2.1.3 启动system_server

  第三个关键点:startSystemServer,这个函数会创建Java世界中系统Service所驻留的进程system_server,该进程是framework的核心,如果它死了,就会导致zygote自杀,先来看看这个核心进程是如何启动的。

  这里是一个分水岭,即zygote进行了一次无性繁殖,分裂出了一个system_server进程,即代码中的Zygote.forkSystemServer这句话。

1.2.1.4 有求必应之等待请求-runSelectLoopMode

  等到zygote从startSystemServer返回后,将进入第四个关键函数,runSelectLoopMode,前面第一个关键点registerZygoteSocket中注册了一个用于IPC的Socket,不过那时还没有地方用,它的用途在runSelectLoopMode中体现出来。

runSelectLoopMode的逻辑:
  1.处理客户链接和客户请求,其中客户在zygote中用ZygoteConnection对象来表示
  2.客户的请求由ZygoteConnection的runOnce来处理

1.2.2 关于zygote的总结

  zygote在Android系统中创建了第一个Java虚拟机,同时成功的繁殖了framework的核心system_server进程,总结一下zygote创建java世界的步骤:
  1.创建AppRuntime对象,并调用它的start,此后的活动由AppRuntime来控制
  2.调用startVm创建Java虚拟机,然后调用startReg来注册JNI函数
  3.通过JNI调用com.android.internal.os.ZygoteInit类的main函数,从此进入了Java世界,这时候的Java世界因为刚开创,啥都没有。
  4.调用registerZygoteSocket,通过这个函数,它可以响应子孙后代的请求,同时zygote调用preloadClasses和preloadReesources,为java世界添砖加瓦。
  5.zygote通过startSystemServer分裂了一个子进程system_server来为java世界服务
  6.zygote完成了Java世界的初创工作,调用runSelectLoopMode后,便睡去了
  7.以后的日子,zygote随时守护在周围,当接收到子孙后代的请求时,会随时醒来,为它们工作

1.3 SystemServer分析

1.3.1 SystemServer的诞生

SS的创建

  SS是由Zygote通过Zygote.forkSystemServer函数fork诞生出来的,forkSystemServer是一个native函数,实现在dalvik_Zygote.c中,代码如下:

  forkAndSpecializeCommon,代码如下:

  setSignalHandler函数,它由Zygote在fork子进程前调用,代码如下:

  作为Zygote的嫡长子,SS有着很高的的地位,可以到了与Zygote生死与共的地步。

1.3.2 SystemServer的重要使命

SS代码如下:

  SS调用handleSystemServerProcess来承担自己的职责

  现在SS走到RuntimeInit中,代码在RuntimeInit.java中,如下:

1.3.2.1 zygoteInitNative分析

  zygoteInitNative是一个native函数,实现在AndroidRuntime.cpp中

  由于SS是zygote fork出来的,所以它也拥有zygote进程中定义的这个gCurRuntime,也就是AppRuntime对象,onZygoteInit的代码在App_main.cpp中,如下:

  SS调用zygoteInitNative后,将与Binder通信系统建立联系,这样SS就能够使用Binder了。

1.3.2.2 invokeStaticMain分析

invokeStaticMain代码如下:

  invokeStaticMain抛出了一个异常,在ZygoteInit的main函数中被截获,代码如下:

  抛出的这个异常最后会导致com.android.server.SystemServer类的main函数被调用,为什么不在invokeStaticMain那里直接调用,而是采用抛出异常的方式,因为这个调用是在ZygoteInit.main中,相当于Native的main函数,即入口函数,位于堆栈的顶层,如果不采用抛出异常的方式,而是在invokeStaticMain那里调用,则会浪费之前函数调用所占用的一些调用堆栈。关于这个问题的深层次思考,可以利用fork和exec知识,可以认为这种抛出异常的方式是对exec的一种模拟,后续交给com.android.server.SystemServer类来处理。

1.3.2.3 SystemServer的真面目

  ZygoteInit分裂产生的SS,其实就是为了调用com.android.server.SystemServer的main函数,代码如下:

  其中main函数将加载libandroid_server.so库,这个库所包含的源文件在文件夹framework/base/services/jni下

(1)init1分析
  init1是native函数,在com_android_server_SystemServer.cpp中实现,代码如下:

  system_init的实现在system_init.cpp中,代码如下:

  init1函数创建了一些服务,然后把调用调用线程加入到Binder通信中,不过其间还通过JNI调用了com.android.server.SystemServer类的init2函数

(2)init2分析
  init2在Java层,代码在SystemServer.java中,代码如下:

  启动了一个ServerThread线程,直接看run函数

  init2函数就是单独创建了一个线程,用以启动系统的各项服务,Java世界的核心Service都在这里启动,所以非常重要。

1.3.3 关于SystemServer的总结

SS的调用流程

1.4 zygote的分裂

  zygote分裂出嫡长子system_server后,就通过runSelectLoopMode等待并处理来自客户的消息,以Activity的启动为例,具体分析下zygote是如何分裂的。

1.4.1 ActivityManagerService发送请求

  ActivityManagerService也是由SystemServer创建的,假设通过startActivity来启动一个新的Activity,而这个Activity附属于一个还未启动的进程,那么这个还未启动的进程该如何启动?先看ActivityManagerService中的startProcessLocked函数,代码如下:

  再看看Process的start函数,这个Process类是android.os.Process,代码在Process.java中,代码如下:


  ActivityManagerService终于向zygote发送请求了,请求的参数中有一个字符串的值是"android.app.ActivityThread",因为ActivityManagerService驻留于SystemServer进程中,所以正是SS向Zygote发送了消息。

1.4.2 响应请求

如何处理请求

  每当有请求数据发来时,Zygote就会调用ZygoteConnection的runOnce函数,ZygoteConnection代码在Zygote Connection.java文件中,来看看它的runOnce函数:

  看看新创建的子进程在handChildProc中做了啥


  zygote分裂子进程后,自己将在handleParentProc中做一些扫尾工作,然后继续等待请求进行下一次分裂。
  这个android.app.ActivityThread类,实际上是Android中apk程序所对应的进程,它的main函数就是apk程序的main函数,这个类的命名android.app中也可看出。
  android系统运行的那些apk程序,其父进程都是zygote,这可以通过adb shell登陆后,用ps命令查看进程和父进程号来确认。

1.4.3 关于zygote分裂的总结

zygote的分裂由SS控制,这里用时序图表达zygote响应请求的过程:

1.5 拓展思考

1.5.1 虚拟机heapsize的限制

  在分析zygote创建虚拟机的时候说过系统默认的Java虚拟机队长最大为16MB,这个值对于需要使用较大内存的程序,如图片处理程序就远远不够,但是,我们可以修改这个默认值,这个改动是全局性的,也就是说所有的Java程序都会是这个32MB。
我们可以动态配置这个值吗?
  设置一个配置文件,每个进程启动的时候根据配置文件中的参数来设置堆大小,这样做不太行,因为zygote是通过fork来创建子进程的,Zygote本身设置的信息会被子进程全部继承,例如Zygote设置的堆栈值为16MB,那么子进程也会是16MB。

关于这个问题的解决方案:
  1.为Dalivk增加一个函数,这个函数允许动态调整最大堆的大小
  2.zygote通过fork子进程后,调用exec家族的函数来加载另一个映像,该映像对应的程序会重新创建虚拟机,重新注册JNI函数,也就是模拟zygote创世界中前两天的工作,最后调用android.app.ActivityThread的main函数,这种方式应该是可行的,但是难度较大并且会影响运行速度。

1.5.2 开机速度优化

  Android的开机速度慢一直是个问题,从目前的情况看,有三个地方开机的时候耗时比较长:
  1.ZygoteInit的main函数中preloadClasses加载了一千多个类,这个耗费时不少
  2.开机启动时,会对系统内所有的apk文件进行扫描并收集信息,这个动作耗时非常长
  3.SystemServer创建的那些Service会占用不少时间
  如何减少preloadClasses的时间,其实,这个函数是可以去掉的,因为系统最终还是会在使用这个类的时候去加载,但是这样就破坏了Android采用fork机制来创建Java进程的本意,zygote预加载这些class,在fork子进程时,仅需要一个复制就行,这样就节约了子进程的启动时间。根据fork的copy-on-write机制可知,有些类如果不做改变,甚至都不用复制,他们就会直接和父进程共享数据,这样就会节省不少内存的占用。

1.5.3 Watchdog分析

  最初存在的意义时因为早期嵌入式设备上的程序经常跑飞,所以专门设置了一个硬件看门狗,每隔一段时间,看门狗就去检查一下某个参数是不是被设置了,如果发现该参数没有被设置,则判断为系统出错,然后强制重启。
  在软件层面上,Android对SystemServer的参数是否被设置也很谨慎,所以专门为它增加了一个看门狗,主要是看几个Service的门,一旦发现Service出现了问题,就会杀掉system_server,而这个会使zygote随其一起自杀,最后导致Java世界重启。
SS和Watchdog的交互流程可以总结为三个步骤:
  1.Watchdog.getInstance().init()
  2.Watchdog.getInstance().start()
  3.Watchdog.getInstance().addMonitor()

1.5.3.1 创建和初始化Watchdog

  getInstance用于创建Watchdog,代码如下:

  Watchdog有了之后,看看init函数,代码如下:

1.5.3.2 运行看门狗

  SystemServer调用Watchdog的start函数,这将导致Watchdog的run在另一个线程中被执行,代码如下:


  隔一段时间给另一个线程发送一条MONITOR消息,那个线程将检查各个Service的健康情况,看门狗等待检查结果,如果第二次还没有返回结果,那么就会杀掉SS

1.5.3.3 列队检查

  检查线程检查Service主要是有三个Service需要交给Watchdog检查:
    1.ActivityManagerService
    2.PowerManagerService
    3.WindowManagerService
  如果想要支持看门狗进行检查,就需要Service实现monitor接口,然后Watchdog就会调用monitor函数进行检查,检查的地方是在HeartbeatHandler类的handleMessage中,代码如下:

  以PowerManagerService为例,看看是怎么把自己交给看门狗进行检查的,代码如下:

  Watchdog调用各个monitor函数到底又检查了些什么,代码如下?

  monitor检查的就是Service是不是发生了锁死,因为Watchdog最害怕系统服务死锁,对于这种情况只能采取杀死系统的方式。另外,Watchdog还能检查内存的使用情况。

1.6 本章小结

  1.zygote主要工作是开创了Java世界
  2.本章介绍了创世界的七大步骤
  3.介绍了zygote的嫡长子System_server进程(SS),这个进程是Java世界中的系统Service的驻留地
  4.SS进程介绍了创建和初始化过程
  5.分析了一个Activity所属进程的创建过程,进程是由ActivityManagerService发送请求给zygote,最后由zygote通过fork的方式创建的
  6.讨论了Dalvik虚拟机对heap大小的设置,以及可能的修改方法
  7.探讨Android系统的开机速度问题
  8.分析了System_server中Watchdog的工作流程

以上是关于Android-深入理解zygote的主要内容,如果未能解决你的问题,请参考以下文章

大厂Android开发高频面试问题:说说你对Zygote的理解

小米Android岗二面:面试官让我说说对Zygote的理解!

Android 深入系统完全讲解

安卓Zygote详解

对android中Zygote的理解

对android中Zygote的理解