Zygote进程的启动 --学习笔记

Android系统分层图:

 

带着面试问题来看Zygote进程的启动

 

What:Zygote的作用是什么?

How:Zygote的启动流程是什么?

Why:Zygote的工作原理是什么?


1、Zygote进程的启动的作用:

主要是两大点:1.启动SystemServer(Android系统进程);2.孵化应用进程

加载 常用类 JNI函数 主题资源 共享库 等

Zygote进程启动三段式

进程启动 ---> 准备工作 ---> Loop(接收消息,处理消息)

2、Zygote进程启动流程

主要是围绕以下两个问题展开:

a.Zygote进程是怎么启动的?

init进程是Linux系统启动后用户空间的第一个进程,首先会去加载init.rc配置文件,启动其中定义的系统服务(Zygote,ServiceManager等),

init进程(加载init.rc)---> fork - execve ---> Zygote进程

 

以下是init.rc文件中启动Zygote进程部分

 

zygote(红色部分):表示要启动进程的名称

/system/bin/app_process/(蓝色部分):表示可执行程序的路径

-Xzygote /system/bin --zygote --start-system-server(黄色部分):表示其参数

 

两种方式:fork + handle、fork + execve 。都是先通过调用fork()函数来创建子进程,会有一个返回值pid,分别在子进程和父进程各返回一次,子进程返回 0 ,父进程返回1,通过判断pid为0还是1来判断当前是是父进程还是子进程;默认子进程继承父进程是继承了父进程的一切资源,如果调用了execve()函数,会清除父进程的资源 ,再调用新的二进制程序来生成;execve()函数的三个参数:path表示路径,argv表示其携带的参数,env表示环境变量

  • 信号处理 - SIGCHLD

     

如上图,父进程fork()出了子进程,如果子进程挂掉了,父进程就会收到SIGCHID信号,进行一些相关的处理。eg:如果说Zygote进程挂了,那么init进程就会收到SIHCHLD信号,就会去重启Zygote进程。

b.Zygote进程启动之后又做了些什么呢?

其执行了一个execve()程序调用,其实质启动了一个二进制程序(C++),里面main函数作为入口,来进行相关的Native层的准备工作,然后就切换到Java层

  • Zygote的Native世界

Zygote的Native世界主要做了以下三件事

 

进入Java世界的启动代码如下:

 

如图,通过标红函数 JNI_CreateJavaVM 创建Java虚拟机,然后找到一个 ZygoteInit 的Java类,然后在其中找到一个Main的静态函数,再通过 CallStaticMethodID 函数去进行调用,最后销毁了这个Java虚拟机。最重要部分就是 JNI_CreateJavaVM 部分,但是一般在应用进程都是直接通过JNI去调用了虚拟机,是不会去创建这个Java虚拟机的,为什么呢?因为它一般在Zygote进程就已经创建完成,后面应用进程都是通过Zygote进程所孵化出来,都是继承了Zygote进程所创建的虚拟机,应用进程的在使用的时候就不需要去创建,只需要重置一下已存在的虚拟机的状态重启虚拟机的守护线程即可

  • Zygote的Java世界

首先预加载资源(一些常用类,主题资源及共享库),再启动SystemServer,其是单独跑在一个进程,所以在Zygote进程在启动它后,后面Zygote基本不用管了,Zygote进程天生的native层,最后就是启动Loop循环,其等待socket消息(等待孵化其他应用进程等或者重启一些子进程),其通过名称去与socket通信。

 

Zygote处理请求的主要代码如下:

 

Zygote在启动之后,最后进入一个socket Loop循环,在这个循环中,它是不断去轮询socket,当有消息传来是就去执行runOnce函数;其主要做三件事:通过readArgumentList函数获取参数列表、再根据参数启动子进程(forkAndSpecialize)、最后在子进程中调用handleChildProc()函数去执行ActivityThread()函数的main函数(注:这个类名就是通过参数列表去获得)。

4.两点注意:

a.Zygote进程的启动要保证单线程:不管父进程中有多少个线程,子进程在创建的时候都只有一个线程,对于子进程来说,多出现的线程在子进程中都不复存在,这时在子进程中就会存在一些问题,有时程序在执行的过程中可能会形成死锁,状态不一致等,所以比较安全的做法是在创建子进程的时候,只保留父进程的主线程,其他都在暂停(此时线程资源是释放的所以不会继承到子进程),子进程启动完后再重启这些线程(Zygote进程就是如此处理);

b.Zygote的IPC(进程间通信)没有采用binder:使用的是本地的socket通信,所以应用程序的binder机制并不是从Zygote进程继承过来的,而是应用程序开了进程创建之后自身启动的binder机制。


问题总结:

a.孵化应用进程这种事为什么不交给SystemServer来做,而专门设计一个Zygote?

我们知道,应用在启动的时候需要做很多准备工作,包括启动虚拟机,加载各类系统资源等等,这些都是非常耗时的,如果能在zygote里就给这些必要的初始化工作做好,子进程在fork的时候就能直接共享,那么这样的话效率就会非常高。这个就是zygote存在的价值,这一点呢systemServer是替代不了的,主要是因为systemServer里跑了一堆系统服务,这些是不能继承到应用进程的。而且我们应用进程在启动的时候,内存空间除了必要的资源外,最好是干干净净的,不要继承一堆乱七八糟的东西。所以呢,不如给systemServer和应用进程里都要用到的资源抽出来单独放在一个进程里,也就是这的zygote进程,然后zygote进程再分别孵化出systemServer进程和应用进程。孵化出来之后,systemServer进程和应用进程就可以各干各的事了。

b.Zygote的IPC通信机制为什么不采用binder?如果采用binder的话会有什么问题?

关于为什么不采用binder,有两个原因:

第一个原因,我们可以设想一下采用binder调用的话该怎么做,首先zygote要启用binder机制,需要打开binder驱动,获得一个描述符,再通过mmap进行内存映射,还要注册binder线程,这还不够,还要创建一个binder对象注册到serviceManager,另外AMS要向zygote发起创建应用进程请求的话,要先从serviceManager查询zygote的binder对象,然后再发起binder调用,这来来回回好几趟非常繁琐,相比之下,zygote和systemServer进程本来就是父子关系,对于简单的消息通信,用管道或者socket非常方便省事,如果对管道和socket不了解的话,可以参考APUE和UNP这两本书。

第二个原因,如果zygote启用binder机制,再fork出systemServer,那么systemServer就会继承了zygote的描述符以及映射的内存,这两个进程在binder驱动层就会共用一套数据结构,这显然是不行的,所以还得先给原来的旧的描述符关掉,再重新启用一遍binder机制,这个就是自找麻烦了。

总的来说呢,对于轻量级的跨进程消息通信,没必要杀鸡用牛刀,普通的管道或者socket足矣。

c.Zygote 与 Ams不用binder通讯的原因有没有多线程死锁的问题呢?感觉fork的时候如果有锁 复制的新进程走到有锁的地方就卡死了。

多线程fork确实可能会死锁,所以zygote为了避免这个问题,在fork的时候给其它的线程都停掉了,另外呢,如果zygote采用binder机制的话,比较好的方式就是直接给主线程注册成binder线程,可以参考ServiceManager的做法,这样也就不会有多线程的问题了。启动进程这种事是个重量级的任务,zygote没必要开多个线程同时去处理好几个任务。

d.SystemServer与SystemService的区别?

SystemServer只是一个进程,SystemService是描述系统服务的一个类

e.Zygote进程的Socket通信相关的代码会被fork出来的进程继承,但应用进程不需要Socket,这方面怎么做的?

Zygote在收到孵化进程的消息,执行forkAndSpecialize,当返回的pid == 0 即为孵化出的应用进程,会调用zygoteServer.closeServerSocket(),将socket关闭;

if (pid == 0) { //pid为0,表示当前代码执行逻辑运行在新创建的进程中,调用handleChildProc处理进程 zygoteServer.closeServerSocket(); IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr); return true; } else {

f.关于zygote孵化应用进程的作用,也就是说,每启动一个应用,zygote进程都会重新启动一次来完成应用的初始化,是这样吗?

zygote进程只会启动一次,启动之后会在一个循环里等待socket消息,如果收到了要启动应用进程的消息,zygote就会通过fork调用来启动应用进程,然后继续循环等待下一个socket消息。zygote只有遇到严重的系统错误的时候才会重启。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值