《10分钟剖析》系统启动3——Zygote的使命

Previous

  • 用户态1字号(pid=1)应用程序 init 透过 app_process 发起zygote启动动做
  • app_process 经过操做 AppRuntimeAndroidRuntime 的派生类)初始化并启动JVM
  • ART虚拟机获得启动,JNI调用环境获得初始化,众多Android API相关JNI得以注册
  • 经过 env->CallStaticVoidMethod() 发起对**ZygoteInit.java#main()**的调用,世界切换至Java
砸瓦鲁多

总的层次调用为:html

  • bootloader
    • kernel
      • init(首次切换到用户态)
        • app_process64(对64位机型来讲)在此启动Java世界,启动zygote

ZygoteInit.java的main()会作什么?java

源码位置:frameworks/base/core/java/com/android/internal/os/ZygoteInit.javalinux

new ZygoteServer、ZygoteHooks、setpgid

  • 建立ZygoteServer,它的职能是借助socket暴露API(后边会详细介绍ZygoteServer)android

  • 然后经过ZygoteHooks和虚拟机进行交互,告知虚拟机在zygote建立期间不要开启新的线程,不然会报错安全

  • Os.setpgid(0,0)并非真的能将本身的pid设置成0,详见其底层方法的官方解释架构

    无关部分隐去。Linux API手册中说的是,若是都给0,那么会跟随当前所处的进程的pid和pgid(对于zygote来讲就是init在调用app_process时产生的进程的pid和pgid了)app

使能DDMS

  • RuntimeInit.enableDdms()socket

    • android.ddm.DdmRegister.registerHandlers()函数

咱们在App中经常使用来调试查看线程、内存、UI布局等方法都是在这里注册的。oop

若是作性能收集专项,学习下系统是如何作的,能够从这里入手。

解析参数

还记得传入ZygoteInit#main()方法的参数吗?

  • com.android.internal.os.ZygoteInit
  • start-system-server
  • --abi-list=arm64-v8a

因此for循环里i从1开始计数,天然也是能够理解的了。而socket又是哪里来的呢?这个能够回头看一下init在start service时作了什么。

zygote socket插曲

回忆init.zygote64.rc

在作rc文件解析时,Service解析过程当中:

system/core/init/service.cpp

  • Service::ParseLine(每行的文字内容)
    • OptionParserMap::FindFunction(透传) //经过socket找到ParseSocket函数指针
      • 构造SocketInfo到descriptors_中

待Service:Start()被调用时:

  • DecriptorInfo::CreateAndPublish()
    • SocketInfo::Create()
      • util.cpp#CreateSocket()
    • setenv //对于SocketInfo而言设置的是环境变量 ANDROID_SOCKET_socket_name

即,在Service启动时就同时建立了名为zygote的socket,具体的socket地址是:

/dev/socket/zygote

建立完成后,将socket句柄数据以环境变量的方式,放置在了 ANDROID_SOCKET_zygote 的key下。

回到参数解析。默认给了socketName = zygote,而以前咱们记录的参数中也并没有socket配置,因此最后生效的socket名字就是zygote无疑

链接socket

zygoteServer的链接也是后边详细说明,这里直接说结论:这里是将init所建立的名为zygote的socket给注册到Java空间来。

LazyPreload 懒-预加载

上边的参数解析中,并无指定lazy-perload,因此这里enableLazyPreload是false的,那么会进行preload()。这里的逻辑是:

  1. 若是是Zygote,也就是不须要进行懒加载,那么就先行预加载一些东西
  2. 若是须要懒加载的话,会将线程的优先级调低。能够理解为,须要懒加载,那么必然就表明着不是很重要吧

preload的内容:

其中和咱们最息息相关的就是class的预加载了,这里边包含了众多API的Java类、Android内部类,最多见的好比Activity、Fragment、Service等也在预加载之列,而预加载的方式就是经过Class.forName:

以个人AOSP源码环境为例,体量巨大,高达6500+行:

Zygote.nativeSecurityInit() 访问安全

接下来,调用了Zygote.nativeSecurityInit();进行native的初始化,对应着:framworks/base/core/jni/com_android_internal_os_Zygote.cpp# com_android_internal_os_Zygote_nativeSecurityInit()

随时复习,这个是在AndroidRuntime初始化时进行注册的,就是在进入Java世界前的事情,详细参见上一篇

具体的内容是对SELinux进行了操做,由于系统设计上,只有系统能作SELinux相关的操做,App进程是不容许的。做为Java世界的头号玩家,Zygote在作fork(衍生出其余App前)天然要先作好安全这一步。

Zygote.nativeUnmountStorageOnInit() 隔离存储

在这一步调用了com_android_internal_os_Zygote.cpp中的com_android_internal_os_Zygote_nativeUnmountStorageOnInit()方法。目的是将总体的存储目录/storage卸载,取而代之的是挂载临时目录,这个动做和Android9及10所说的 隔离存储 有关,也能够叫作 沙盒存储 。这一部分能够参照官方说明

ZygoteHooks.stopZygoteNoThreadCreation()

与上边呼应,告诉ART虚拟机,如今能够在zygote进程中建立线程了,下图是虚拟机中实际建立线程的入口方法,能够看到,会经过对应的标记为去判断到底是否能够建立线程,若是触发红线的话,会抛出InternalError:

在继续跟进以前,须要理清楚一个关键的角色 ZygoteServer

ZygoteServer

其在ZygoteInit.java#main(),除了构造,其被调用到的方法和顺序是:

  • registerServerSocketFromEnv(socketName) //zygote
  • runSelectLoop()
  • closeServerSocket()

registerServerSocketFromEnv

前面说到,在init运行zygote server时候,建立了名为zygote的socket,而后将对应的socket句柄(int值)以环境变量的方式存储在了key => ANDROID_SOCKET_zygote 中。

在此方法中,经过对应的环境变量获取到了这个socket的句柄数据,而后将其转换为了Java空间的ServerSocket:

LocalServerSocket 中,又透过 LocalSocketImpl 开始了真正的监听。

这里只是注册,开始了监听,但尚未去获取其中的数据

runSelectLoop

ZygoteServer经过poll的方式轮训检查zygote socket。检查时对两类socket作区分处理:

  1. 刚刚建立的ServerSocket,当这个句柄收到新数据时,表明的是有新的socket链接进入,此时会构造新的ZygoteConnection,放入下一轮的轮训句柄集合中(ServerSocket在集合中的位置永远是0),并开启新一轮轮训
  2. 对于ZygoteConnection,也就是来自客户端的链接。会调用ZygoteConnection.processOneCommand()解析命令,得到对应的Runnable可执行命令,这个过程当中也就发生了fork。fork后在父进程中关闭链接,而子进程中就返回了这个Runnable

大概流程如上图,忽略序号的顺序,由于有fork产生了子进程,因此顺序上看连线断定吧

closeServerSocket

最后,顾名思义,关闭ServerSocket,再也不接受链接。也就是说,zygote进程的服务终止了,这对于Android系统来讲是致命错误,正常运行状况下是绝无可能发生的。那为何还有这个方法呢?

上边的时序图中,有一个点:fork后,在父进程是不返回的,只是在ZygoteConnection中关闭了来自客户端的链接(由于此时已经处理完了)。而在fork出来的子进程中是不须要在有zygote服务的,因此这里的关闭理论上是为了在子进程中关闭无用的zygote服务所说。

总结

分析完ZygoteServer三个关键的方法后,发现其定位就是zygote服务和客户端的链接器、处理器。客户端经过名为zygote的socket发来一些启动请求,由zygote进程fork出来子进程,享用zygote在启动初期所作好的一切(JVM初始化、JNI初始化、class预加载、资源预加载等),然后执行经过命令解析出的Runnable(下边会直接列出解析的流程)。这就是一个新的App启动过程当中相当重要的一个部分,后边会分析App启动,也会碰触到这一块。

processOneCommand解析Runnable过程

  • ZygoteConnection.processOneCommand
    • readArgumentList //从socket中读取参数列表
    • new Arguments //解析参数
    • fork
      • 父进程
        • return null
      • 子进程
        • ZygoteServer.setForkChild() //告知ZygoteServer当前在子进程
        • ZygoteServer.closeServerSocket() //如上所说,子进程中已经不在须要zygote服务
        • handleChildProc

fork SystemServer

通过了ZygoteServer的梳理,如今回到ZygoteHooks.stopZygoteNoThreadCreation()后的systemserver fork进程中

Arguments

下面是用来启动system_server的关键参数:

uid、gid和组别信息,niceName、runtime参数、以及最重要的 classPath=com.android.server.SystemServer

以上的参数会被ZygoteConnection的内部类Arguments解析,解析过程大致以下:

  • --会被视为是要跳过的参数字符
  • 其余的参数会有重复解析的报错

当全部已知的内容被解析过以后,会检查剩余是否还有参数:

根据上述用于启动system_server的参数,咱们在这里能够断定,会走到最后的case中,即剩余的参数会被保存在remainingArgs中,即com.android.server.SystemServer

Zygote.forkSystemServer()

参数解析完成后,ZygoteInit调用Zygote.forkSystemServer()着手进行fork。

此方运行调用后,会触发两次返回,前后顺序不必定(一样也不重要)。

  • 一次返回是带着大于0的pid回来,此时runtime处于父进程,这个pid就是fork出来的子进程的pid
  • 另外一次返回是带着等于0的pid回来,此时runtime处于子进程

ZygoteHooks.preFork()

这里是作fork前的准备,主要是经过调用Daemon.stop()来操做守护线程的中止:

  • 堆守护
  • 引用队列守护
  • 内存回收守护
  • 内存回收看门狗守护线程

resetNicePriority()

重置进程(主线程)优先级为 5 ,这一点经过咱们本身开发的App线程信息能够查看到

nativeForkSystemServer()

这里进行了真正的fork,使得整个流程在这里发起了两次的返回。一个比较关键的细节是:

在fork进行完成后,在 system_server 进程(子)中透过JNI触发了Java空间的回调(Zygote.java):

这个调用最终在ART虚拟机中生效,虚拟机为从zygote衍生出来的子进程作了一系列的配置。固然,这个过程当中,对于system_server有过不少特殊的待遇(好比,关闭JIT)。

ZygoteHooks.postForkCommon()

在这里又会启动上边所说的四个守护线程,而且从新设置线程优先级。很关键的一点是,这里会分别在父进程、子进程执行。zygote进程中,此时也是首次开启这几个守护线程。而system_server进程,以及后边可能的其余三方App的守护线程也是在这里开启的。

至此,system_server进程的fork就完成了,后边才开始在进程中作事

在sysetm_server进程中运行

return null

这里优先解释下最后的return null,把上文呼应的问题说清楚。

在ZygoteInit#main()方法中,咱们以前对最后一段作过度析,回头复习一下:

在fork完成system_server进程后,父进程中直接返回了null。这样,在zygote的进程中就会执行到runSelectLoop,也就是上边所解释过的:处理zygote socket接收到的命令。

system_server子进程中

关于second zygote socket

若是ABI(arm64等架构)有多个,那么会有第二个zygote socket,优先会去等待这第二个socket就绪。方法很传统,等待20s:

这部分不作过多讨论,咱们聚焦于主线。

zygoteServer.closeServerSocket()

关于zygoteServer的关闭,在上边也解释过,子进程并不须要保持zygote socket的链接了,因此进行断开操做。这并不会影响主进程的server socket继续工做。

handleSystemServerProcess()

很关键的一点,关于入参,上边有一笔带过,在parsedArgs中还存留着一个叫作remainingArgs的变量,其内容是com.android.server.SystemServer

  • 首先,经过调用熟悉的Process.setArgV0(parsedArgs.niceName)将system_server进程的名字真正命名为了system_server
  • 而后,经过SYSTEMSERVERCLASSPATH环境变量获取到一些jar文件(实际是最终机器上的/system/frameworks/下的jar文件,是作system_server的classpath),提早作dex opt优化(关于installd服务部分不在这里展开)
  • 然后,建立classloader(PathClassLoader),并设置给主线程
  • 最后,经过ZygoteInit调用执行(这里传入了咱们关心的remainingArgs)

在这里,最终调用了com.android.server.SystemServer#main()方法,具体内容留做下次分析。

相关文章
相关标签/搜索