Android 启动流程 【2】 Zygote 进程

1,233 阅读5分钟

在上一章节的 init 进程启动过程中,最后的工作是解析 init.rc 文件,并且在 Android 8.0 开始, init.rc 根据不同的服务拆分成了不同的 init.xxx.rc 文件,Zygote 就是其中的一个。

Zygote

init 进程创建时,会创建 Zygote 进程, Zygote 进程的最开始名称并不是 zygote ,而是 “app_process” ,这个名称是在 Android.mk 中定义的,Zygote 进程启动后, Linux 系统下的 pctrl 系统会调用 app_process,将其名称替换为 zygote 。 所以这就是 Zygote 的名称由来。 在 Android 操作系统中,DVM 和 ART、应用程序进程和运行系统的关键服务 SystemServer 进程都是由 Zygote 进程 fork 而来, Zygote 进程在创建时,会创建一个 DVM 或 ART , 所以由 Zygote 进程 fork 而来的进程都可以在内部获取一个 DVM 或 ART 的实例副本,实现了进程自带 JVM ,所以 Android 系统可以嵌入到不同类型的设备中,因为 Linux + JVM 可以很好的完成跨平台支持。

启动流程

Init 进程第一个创建的进程就是 Zygote 进程。对应的文件名是 init.zygoteXX.rc 启动脚本文件。XX 是处理器位数,例如 64 位处理器是 init.zygote64.rc 。 init.rc 中的实际引用代码如下:

import /init.${ro.zygote}.rc

ro.zygote 是一个系统属性, init.rc 文件根据属性 ro.zygote 的内容来引入不同的文件。

从 Android5.0 开始, Android 开始支持 64 位程序, Zygote 有了 32 和 64 位的区别, ro.zygote 的取值有以下四种:

  • init.zygote32.rc :表示支持纯 32 位程序。
  • init.zygote64.rc : 表示支持纯 64 位程序。
  • init.zygote32_64.rc :表示即支持 32 位程序也支持 64 位程序;脚本中有两个 Service ,会启动两个 Zygote 进程,一个名为 zygote,执行程序为 app_process32 ,作为主模式,另一个名为 zygotę_secondary ,执行程序为 app_process64 ,作为辅模式。init.zygote64_32.rc:主模式与辅模式正好与 32_64 相反。

启动脚本会根据参数创建一个 Service ,Zygote 就是一个 Service。在创建完 Service 后,会将其存入一个 vector 类型的 Service 链表中。在这个 Service 创建完成后,通过调用 class_start 方法执行 Service 启动操作。

image-20220315022246788.png

class_start 实际对应的方法是 do_class_start 函数。do_class_start 中调用了 Service 的 StartIfNotDisabled 方法:

image-20220315022445914.png

在这个 StartIfNotDisabled 方法中,只调用了一下 Start 方法,这个方法的伪代码逻辑是:

  1. 如果 Service 已经运行,则直接 return 。
  2. 判断需要启动 Service 对应的执行文件是否存在,不存在则不启动该 Service 。
  3. 如果 Service 没有启动,则调用 fork 函数获取 pid 。
  4. 若 pid 为 0 ,调用 RunService 方法。

RunService 方法的作用是进入命名空间、设置环境变量、写入 PID 文件并运行服务可执行文件。它在最后调用了 ExpandArgsAndExecv 函数。这个方法里面最后执行了 execv 函数。execv 是 exec.cpp 里定义的方法,它内部调用了 execve 方法。

execve 函数的作用是执行可执行文件,启动 Service 进程,并进入该 Service 的 main 函数。这个时候如果 Service 是 Zygote,则会执行到 app_main.cpp (Zygote 执行程序的路径是 /system/bin/app/app_process64,对应文件是 app_main.cpp ),这样就会进入到 app_main.cpp 的 main 函数中。

在 Zygote 的 main 函数中,调用了 runtime 的 start 函数来启动 Zygote:

image-20220315025022537.png

由于 Zygote 进程是 Android 系统的根进程,后续 SystemSever 和应用程序进程都是由 Zygote fork 而来,所以 Zygote 的子进程也可以作为一个 Service ,调用到 app_main.cpp 的 main 函数。那么需要有一个标志来区分 zygote 和 它的子进程。 所以, main 函数中,如果参数 arg 中包含了 “—zygote”,说明运行在 Zygote 进程中。 若不包含,会进一步检查 “—start-system-server” ,如果包含说明运行在 SystemServer 进程中。 app_main.cpp 的 main 方法主要代码逻辑是:

  1. 检查 main 函数运行所在线程。
  2. 如果运行在 Zygote 进程,调用 runtime.start。这里的 runtime 类型是 AppRunTime.cpp ,它的 start 方法中,主要做了以下事情:
    1. 调用 startVm 启动 JVM
    2. 为 JVM 注册 JNI 方法,( startReg(env) )
    3. 从 app_main 的 main 函数得知 classname 为 com.android.internal.os.ZygoteInit
    4. 将 classname 中的 “. “ 替换为 “/”
    5. 然后根据替换后的路径找到 ZygoteInit 这个类。
    6. 找到 ZygoteInit 的 main 方法。
    7. 若 main 方法 找不到,直接报错 : “JavaVM unable to find main() in $classname”
    8. 若 main 方法找到,通过 JNI 调用 ZygoteInit 的 main 方法,并调用。这里要使用 JNI 是因为, ZygoteInit 的 main 方法是由 Java 语言编写的,当前仍在 Native 中。 这样 Zygote 就从 Native 层进入了 Java 框架层。

Java 框架层 的 ZygoteInit

ZygoteInit.java 文件中的 main 函数,创建了一个 Socket 作为 Server,用来等待 ActivityManagerService 请求 Zygote 来创建新的应用进程。 main 函数中的具体逻辑是:

a.  创建一个 socketName =  zygote 的 Server 端 Socket。
b.  预加载类和资源。
c.  启动 SystemServer 进程。
d.  等待 AMS 请求创建新的应用程序进程。

创建 Zygote 的 Server Socket

zygoteServer.registerServerSocket(socketName);

zygoteServer 的类型是 ZygoteServer ,它的 registerServerSocket 方法用来创建 Socket 具体逻辑是:

  1. 拼接 socket 的名称
  2. 得到 socket 的环境变量
  3. 将 socket 环境变量的值转换为文件描述符的参数
  4. 创建文件描述符 fd
  5. 根据文件描述符 fd 创建 Socket

启动 SystemServer 进程

在创建完 socket 后,首先会去启动 SystemServer 进程。启动 SystemServer 的方法是 ZygoteInit 中的 startSystemServer 方法。 在这个过程中,实际上是: 首先,构建 args 数组,用来储存启动 SystemServer 的启动参数。 然后,通过 Zygote.forkSystemServer 方法创建一个子进程,也就是 SystemServer 进程。 最后,判断当前代码所在的进程,如果是自己吃,调用 handleSystemServerProcess 方法处理 SystemServer 进程。

开启 Socket 监听

启动 SystemServer 进程后,会执行 ZygoteServer 的 runSelectLoop 方法。在这个方法中,创建了一个 无限循环,用来等待 AMS 请求。

还记得我们在 registerServerSocket 方法中创建 socket 时创建的文件描述符 fd 吗?它的数据结构是:

struct pollfd{
 int fd; // 文件描述符
 short event// 请求的事件
 short revent;// 返回的事件
}

在这个接收 AMS 请求的死循环中,首先从 socket 中取出文件描述符信息,存到一个数组中, 用 fds 表示。接下来无限循环等待 AMS 请求,每次循环都会遍历 fds ,将 fd 转存到 pollFds 中,然后调用系统的 poll 方法:

Os.poll(pollFDs, pollTimeoutMs);

这个方法是最终调用的是 Linux 中的 poll 函数,第一个参数是一个数组,即 poll 函数可以监视多个文件描述符。关于 poll 函数,可以通过以下链接进行了解:man7.org/linux/man-p… 。 这个函数的作用是获取一个 返回值,如果超时或者没有 fd 可以处理时,会返回为 0,这里就实现了阻塞并持续等待消息的效果。 一旦结果不为 0 ,则会对 pollFds 进行反向遍历,如果能够遍历到 0 ,说明服务端 Socket 与客户端连接上了,但没有可处理的fd。当前 Zygote 进程与 AMS 建立了连接,然后获取了一个 ZygoteConnection 对象,并添加到 Socket 的连接列表 peer 中。接着将 ZygoteConnection 的 fd 添加到 fds 中,以便可以接受到 AMS 发来的请求。如果遍历不为 0 就进行了逻辑处理,则说明 AMS 向 Zygote 进程中发送了一个创建应用程序进程的请求。这个时候调用 ZygoteConnection 的 runOnce 函数来创建一个新的应用程序进程,并在创建成功后,将这个连接从 Socket 连接列表 peers 和 fd 列表 fds 中删除。

这里遍历到 0 的判断是因为,AMS 要先与 Socket 建立连接, 然后只能接收一次 AMS 请求,接收到后就会关闭这个连接。

总结一下 runSelectLoop 方法的流程:

  1. 创建了一个无限循环
  2. 取出 Socket 的 文件描述符信息,并存入数组 fds 中。
  3. 遍历 fds ,将 fds 中的数据存到 pollFds 中。
  4. 调用 Linux 的 poll 方法,检查文件描述符是否要处理。
  5. 根据 poll 返回的值判断是否存在 fd 要处理,没有则启动下一次循环,达到阻塞效果。
  6. 有 fd 需要进行处理,对 pollFds 进行反向遍历:
    1. 如果能遍历到 0 ,说明 Socket 和客户端首次建立了连接,获取连接对象 ZygoteConnection ,并将其存到 Socket 的连接列表 peers 中。将 ZygoteConnection 的 fd 添加到 fds 中,以便后续接收 该请求。
    2. 如果还没遍历到 0 就被处理了,说明 AMS 向 Zygote 进程发送了一个创建应用程序进程的请求,调用 runOnce 函数区创建一个新的应用程序进程,并在创建完成后,删除 fds 中的这个 fd 和 peers 中的这个连接。

总结

Zygote 进程启动的流程主要是:

  1. 创建 AppRuntime 并调用 start 方法,启动 Zygote 进程。
  2. 创建 JVM 并为 JVM 注册 JNI 方法。
  3. 通过 JNI 方法调用 ZygoteInit 的 main 函数,由 Native 层进入 Zygote 的 Java 框架层。
  4. 通过 registerZygoteSocket 方法创建服务端 Socket ,并通过 runSelectLoop 方法等待 AMS 发送来的创建应用程序进程的请求。
  5. 启动 SystemServer 进程。