Android学习之zygote启动流程

Posted 小陈乱敲代码

tags:

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

zygote 概述

android系统中,DVM(Dalvik虚拟机)、应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器。
zygote的英文名为受精卵,由于他能不断孵化进程,因此也是名字的由来。
它通过fock(复制进程)的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动时会创建DVM,因此通过fock而创建的应用程序进程和SystemServer进程可以在内部获取一个DVM的实例拷贝

init.rc中配置

在init进程创建时,会读取 init.zygote.rc 中的配置文件

init.zygote.rc

这个是在rc文件中的配置,下面我们会一次介绍各个命令的作用,这些命令具体都会在源码分析中看到

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

service

service指令告诉系统将zygote程序加入到系统服务中,基本语法如下

service service_name 可执行程序的路径 可执行程序自身所需的参数列表

这里我们的服务是 zygote 他的程序路径在 /system/bin/app_process ,后面那一堆,就是程序执行的参数,在 app_process 的main函数中会从argc和argv中取出

-Xzygote

作为虚拟机启动时所需要的参数,在AndroidRuntime.cpp中的 startVm() 函数中调用 JNI_CreateJavaVM 使用到

/system/bin

代表虚拟机程序所在目录,因为 app_process 可以不和虚拟机在一个目录,所以 app_process 需要知道虚拟机所在的目录

–zygote

指明以 ZygoteInit 类作为入口,否则需要指定需要执行的类名

–start-system-server

仅在有 --zygote 参数时可用,告知 ZygoteInit 启动完毕后孵化出的第一个进程是 SystemServer

其他命令

后续的命令是和socket相关,名称、类型、端口号、重启后的操作等等

app_process

该文件在 frameworks/base/cmds/app_process 中,找到 main 函数

//前面的操作都是对参数进行一些赋值等等,就是之前的命令中配置的参数
......
if (zygote) 
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
     else if (className) 
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
     else 
        fprintf(stderr, "Error: no class name or --zygote supplied.\\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
  

这个 runtime 是 AppRuntime

AppRuntime.start

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)


    ......
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    //创建虚拟机相关信息
    if (startVm(&mJavaVM, &env, zygote) != 0) 
        return;
    
    onVmCreated(env);

    //JNI方法的注册
    if (startReg(env) < 0) 
        ALOGE("Unable to register all android natives\\n");
        return;
    

    ......

    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) 
        ......
     else 
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) 
            ......
         else 
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        
    

对虚拟机和JNI方法的一些注册后,通过 CallStaticVoidMethod 来调用传过来的类名的main函数,我们传递过来的类名是 com.android.internal.os.ZygoteInit

ZygoteInit.main

该文件在 frameworks/base/core/java/com/android/internal/os 中

public static void main(String argv[]) 
        ZygoteServer zygoteServer = new ZygoteServer();
        ......
        final Runnable caller;
        try 
            ......

            zygoteServer.registerServerSocket(socketName);
            preload(); 
            ......
            if (startSystemServer) 
                Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
                if (r != null) 
                    r.run();
                    return;
                
            
            caller = zygoteServer.runSelectLoop(abiList);
         catch (Throwable ex) 
            ......
         finally 
            zygoteServer.closeServerSocket();
        
        if (caller != null) 
            caller.run();
        

registerServerSocket : 用于注册socket
preload : 预加载类和资源
forkSystemServer : 启动system_server
runSelectLoop : socket的处理

registerServerSocket

注册一个socket,来接收启动新进程一些命令操作,建立socket通道,zygote作为通信的服务端,用于响应客户端请求

void registerServerSocket(String socketName) 
        if (mServerSocket == null) 
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try 
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
             catch (RuntimeException ex) 
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            

            try 
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
                mServerSocket = new LocalServerSocket(fd);
             catch (IOException ex) 
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            
        

fileDesc 是一个文件描述符

最终会构造一个 LocalServerSocket ,创建socket的本地服务端

关于socket

在Linux系统中,所有的资源都可以看做是文件,socket也是,所以传递了一个文件描述符来构建socket

注意
socket也是IPC的一种方式
socket中有两种方式进行通信

阻塞式

使用listen()监听某个端口,调用read()读取数据,没有数据时,会一直等待

非阻塞式

使用select()将需要检测的文件描述符作为 select() 函数的参数,当文件描述符上出现新的数据后,自动触发一个中断,然后在中断处理函数中再去读取指定文件描述符的数据

LocalServerSocke 用的是第二种 非阻塞式,读取的文件为 /dev/socket/zygote

LocalServerSocke 对应的客户端

前面的代码讲解的是关于socket服务端的,有服务端那就有客户端

它对应的客户端是进程 SystemServer 中创建出来的

preload

为了能够让 zygote 孵化进程进行的资源加载,预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率

static void preload() 
    //预加载位于/system/etc/preloaded-classes文件中的类
    preloadClasses();

    //预加载资源,包含drawable和color资源
    preloadResources();

    //预加载OpenGL
    preloadOpenGL();

    //通过System.loadLibrary()方法,
    //预加载"android","compiler_rt","jnigraphics"这3个共享库
    preloadSharedLibraries();

    //预加载 文本连接符资源
    preloadTextResources();

    //仅用于zygote进程,用于内存共享的进程
    WebViewFactory.prepareWebViewInZygote();

forkSystemServer

这个小节介绍SystemService的启动过程

fork

fork是Linux中的一个系统调用,作用是复制当前进程,除了进程ID不同,两个进程信息完全一致

新进程被创建后,两个进程将共享已经分配的内存空间,直到其中一个进程需要向内存写入数据的时候,系统才会复制一份目标地址空间,并将写的数据写入到新的地址中,这就是 copy-on-write 机制,“仅当写的时候才复制”,可以节省共享物理内存,调用一次,返回两次,返回值有3种类型

● 父进程中,fork返回新创建的子进程的pid
● 子进程中,fork返回0
● 当出现错误时,fork返回负数(当进程数超过上限或者系统内存不足时会出错)

fork()的主要工作是寻找空闲的进程号pid,然后从父进程拷贝进程信息,例如数据段和代码段,fork()后子进程要执行的代码等。 Zygote进程是所有Android进程的母体,包括system_server和各个App进程。zygote利用fork()方法生成新进程,对于新进程A复用Zygote进程本身的资源,再加上新进程A相关的资源,构成新的应用进程A

copy-on-write原理

写时拷贝是指子进程与父进程的页表都所指向同一个块物理内存,fork过程只拷贝父进程的页表,并标记这些页表是只读的。父子进程共用同一份物理内存,如果父子进程任一方想要修改这块物理内存,那么会触发缺页异常(page fault),Linux收到该中断便会创建新的物理内存,并将两个物理内存标记设置为可写状态,从而父子进程都有各自独立的物理内存

为什么要fork

因为两个进程存在大量的共享程序,如果使用fork,可以节省大量的共享内存

SystemServer的启动

private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) 
        ......
        String args[] = 
            "--setuid=1000",
            "--setgid=1000",
            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010",
            "--capabilities=" + capabilities + "," + capabilities,
            "--nice-name=system_server",
            "--runtime-args",
            //注意这里的类名,它会去装载这个类
            "com.android.server.SystemServer",
        ;
        ZygoteConnection.Arguments parsedArgs = null;

        int pid;

        try 
            parsedArgs = new ZygoteConnection.Arguments(args);
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

            //主要的fork操作,会调用到native层的代码进行 fork
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);
         catch (IllegalArgumentException ex) 
            throw new RuntimeException(ex);
        

        /* For child process 这里代表新进程*/
        if (pid == 0) 
            if (hasSecondZygote(abiList)) 
                waitForSecondaryZygote(socketName);
            

            zygoteServer.closeServerSocket();
            return handleSystemServerProcess(parsedArgs);
        

        return null;
    

在 pid == 0 的节点中,关闭socket服务端(注意这里关闭的是从父进程复制过来的子进程里的socket,因为子进程不需要这个socket),
handleSystemServerProcess 函数的作用是调用 SystemServer 的main函数以及一些配置工作(调用新进程指定的class的main函数)

一旦 SystemServer 的配置工作完成后,就会从 SystemServer 的 main 函数开始运行

到此,调用main后,新进程就完全脱离了 zygote 进程的孵化过程了,成为一个真正的应用进程

runSelectLoop

socket创建后就是进入读操作了

Runnable runSelectLoop(String abiList) 
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
        //mServerSocket 是socket通信中的服务端,即zygote进程。保存到fds[0]
        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);
				//无限循环等待AMS的请求
        while (true) 
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) 
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            
            try 
                //处理轮询状态,当pollFds有事件到来则往下执行,否则阻塞在这里
                Os.poll(pollFds, -1);
             catch (ErrnoException ex) 
                throw new RuntimeException("poll failed", ex);
            
            for (int i = pollFds.length - 1; i >= 0; --i) 
                if ((pollFds[i].revents & POLLIN) == 0) 
                    continue;
                
                // 第一个就是刚才的操作 fds.add(mServerSocket.getFileDescriptor());
                if (i == 0) 
                    //接收客户端发送过来的connect()操作,Zygote作为服务端执行accept()操作。 再后面客户端调用write()写数据,Zygote进程调用read()读数据
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                 else 
                    try 
                        ZygoteConnection connection = peers.get(i);
                        final Runnable command = connection.processOneCommand(this);
                        ......
                     catch (Exception e) 
                        ......
                    
                
            
        

在读取数据的操作中的 processOneCommand 里,最为核心的方法就是 Zygote.forkAndSpecialize,看名字就应该知道他的作用就是fork进程,runSelectLoop 函数随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作

总结

zygote主要做了一下几件事:

  1. 创建AppRuntime并调用start方法,启动zygote进程
  2. 创建java虚拟机并未java虚拟机注册JNI方法
  3. 通过JNI调用 zygoteinit的main函数进入zygote的java框架层
  4. 通过 registerZygoteSocket方法创建服务端 Socket,并通过 runSelectLoop 方法等待AMS的请求来创建新的应用进程
  5. 启动 SystemServer进程

zygote的main函数的流程可以简化为下

public static void main(String argv[]) 
    try 
        caller = zygoteServer.runSelectLoop(abiList);
        ....
     catch (RuntimeException ex) 
        closeServerSocket();
        throw ex;
    
    if (caller != null) 
        caller.run();
    

注意,之前在分析acceptCommandPeer部分的时候,父进程是直接返回null的,新进程是返回一个runnable,因此,这部分的代码在不同进程中的执行是不同的.对于父进程也就是Zygote进程,他是一直循环运行的,对于新进程,会完成启动操作后停止循环并调用目标类的main函数

  1. 系统启动时init进程会创建Zygote进程,Zygote进程负责后续Android应用程序框架层的其它进程的创建和启动工作。
  2. Zygote进程会首先创建一个SystemServer进程,SystemServer进程负责启动系统的关键服务,如包管理服务PackageManagerService和应用程序组件管理服务ActivityManagerService。
  3. 当我们需要启动一个Android应用程序时,ActivityManagerService会通过Socket进程间通信机制,通知Zygote进程为这个应用程序创建一个新的进程。

以上是关于Android学习之zygote启动流程的主要内容,如果未能解决你的问题,请参考以下文章

Android 启动过程Android 应用启动流程 | Activity 启动流程

Zygote——Android系统中java世界的受精卵(二Welcome To Java)

安卓Zygote详解

Android5 Zygote 与 SystemServer 启动流程分析

Android5 Zygote 与 SystemServer 启动流程分析

Android5 Zygote 与 SystemServer 启动流程分析