Android进程系列第二篇---Zygote进程的启动流程

内容预览.png

概述:

本文(基于Android O源码)主要讲解Zygote进程创建流程,线程容易创建,但进程的相关的东西都被系统很好的封装了,以至于进程的创建,很多人还是头一回。首先一张图来看看Zygote进程在系统中的地位。

Zygote的地位.png

Zygote进程又称受精卵进程,它由app_process启动,Zygote进程最大意义是作为一个Socket的Server端,接收着四面八方的进程创建请求,Android中所有的应用进程的创建都是一个应用进程通过Binder请求SystemServer进程,SystemServer进程发送socket消息给Zygote进程,统一由Zygote进程创建出来的。典型的C/S架构!!!。图中红色标注为Binder通信方式,蓝色标注为Socket通信方式。

话说为什么Android系统采用这种架构呢,为什么所有进程的创建都是由Zygote来做呢?原因有如下几点

  • Zygote进程在启动的时候会创建一个虚拟机实例,因此通过Zygote进程在父进程,创建的子进程都会继承这个虚拟机实例,App中的JAVA代码可以得到翻译执行。

  • 进程与进程之间需要跨进程通信,由Zygote进程作为父进程还可以获得一个Binder线程池,这样进程之间就可以使用Binder进行跨进程通信了。

  • 进程的“血液”可以理解成Message,启动四大组件靠的都是Looper消息机制,看过老罗的书,说由Zygote进程作为父进程,子进程可以获得一个消息循环。这句话我表示不理解,因为各个应用进程的Looper循环是自己在ActivityThread中创建的,SystemServer进程的消息循环也是自己创建的,那为什么说子进程可以继承Zygote进程的消息循环呢?这点我觉得不严谨,欢迎讨论。

所以这可以理解成Zygote进程作为所有应用进程的父进程的原因。

1、进入JAVA的世界,ZygoteInit的main方法

SystemServer进程的创建.png

这张序列图就是Zygote进程的创建流程,结合代码看一看。

    frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
    public static void main(String argv[]) {
       //1、创建ZygoteServer
        ZygoteServer zygoteServer = new ZygoteServer();
      .......
        try {
             .......
            boolean startSystemServer = false;
            String socketName = "zygote";
            String abiList = null;
            boolean enableLazyPreload = false;
           // 2、解析app_main.cpp传来的参数
            for (int i = 1; i < argv.length; i++) {
                if ("start-system-server".equals(argv[i])) {
                    startSystemServer = true;
                } else if ("--enable-lazy-preload".equals(argv[i])) {
                    enableLazyPreload = true;
                } else if (argv[i].startsWith(ABI_LIST_ARG)) {
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());
                } else {
                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
                }
            }

            if (abiList == null) {
                throw new RuntimeException("No ABI list supplied.");
            }
            //3、创建一个Server端的Socket
            zygoteServer.registerServerSocket(socketName);
            // In some configurations, we avoid preloading resources and classes eagerly.
            // In such cases, we will preload things prior to our first fork.
            if (!enableLazyPreload) {
                bootTimingsTraceLog.traceBegin("ZygotePreload");
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                    SystemClock.uptimeMillis());
               //4、加载进程的资源和类
                preload(bootTimingsTraceLog);
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                    SystemClock.uptimeMillis());
                bootTimingsTraceLog.traceEnd(); // ZygotePreload
            } else {
                Zygote.resetNicePriority();
            }
              ........
            if (startSystemServer) {
                //5、开启SystemServer进程,这是受精卵进程的第一次分裂
                startSystemServer(abiList, socketName, zygoteServer);
            }

            Log.i(TAG, "Accepting command socket connections");
           //6、启动一个死循环监听来自Client端的消息
            zygoteServer.runSelectLoop(abiList);
           //7、关闭SystemServer的Socket
            zygoteServer.closeServerSocket();
        } catch (Zygote.MethodAndArgsCaller caller) {
           //8、这里捕获这个异常调用MethodAndArgsCaller的run方法。
            caller.run();
        } catch (Throwable ex) {
            Log.e(TAG, "System zygote died with exception", ex);
            zygoteServer.closeServerSocket();
            throw ex;
        }
    }

ZygoteInit的main方法大概就做了上面六件事情,一,创建ZygoteServer,在Android O上把与Socket的操作都封装到了ZygoteServer类中;二,解析app_main.cpp传来的参数。三、创建一个Server端的Socket,作用是当Zygote进程将SystemServer进程启动后,就会在这个Socket上来等待ActivityManagerService请求,即请求创建我们自己APP应用程序进程;四,预加载类和资源,包括颜色啊,R文件,drawable、类等;五,启动system_server进程,这是上层framework的运行载体,ActivityManagerService就是运行在这个进程里面的;六,开启一个循环,等待着接收ActivityManagerService的请求,随时待命,当接收到创建新进程的请求时立即唤醒并执行相应工作;

我觉得这段代码的主线是,ZygoteInit进程启动后,会注册一个Socket,在runSelectLoop方法中开启一个while死循环等待ActivityManagerService创建新进程的请求,其次,ZygoteInit启动了SystemServer进程,执行SystemServer的main方法。

这种模式其实可以理解成一个模板格式的代码,不信你在看看WebViewZygoteInit中的写法和ZygoteInit的写法是不是如出一辙呢?

  frameworks/base/core/java/com/android/internal/os/WebViewZygoteInit.java
    public static void main(String argv[]) {
        sServer = new WebViewZygoteServer();

        // Zygote goes into its own process group.
        try {
            Os.setpgid(0, 0);
        } catch (ErrnoException ex) {
            throw new RuntimeException("Failed to setpgid(0,0)", ex);
        }

        try {
            sServer.registerServerSocket("webview_zygote");
            sServer.runSelectLoop(TextUtils.join(",", Build.SUPPORTED_ABIS));
            sServer.closeServerSocket();
        } catch (Zygote.MethodAndArgsCaller caller) {
            caller.run();
        } catch (RuntimeException e) {
            Log.e(TAG, "Fatal exception:", e);
        }

        System.exit(0);
    }

如果要理解的更深一点,就需要再思考几个问题了。

  • 1、ZygoteInit方法是怎么调用的?
  • 2、Socket是怎么注册的?Socket通信的原理是否还记得?
  • 3、SystemServer进程资源如何加载?
  • 4、为什么要抛出MethodAndArgsCaller这个异常?作用是什么?

好,现在解答这些问题,这些问题弄懂,Zygote进程创建流程也就OK了。

1.1、ZygoteInit方法是怎么调用的

从上面的流程图中看到ZygoteInit的main是从app_main.cpp来的。app_main是Zygote进程对应的主文件,Zygote进程被Init启动的时候,就会调用这个app_main.cpp的main函数。

/frameworks/base/cmds/app_process/app_main.cpp
192int main(int argc, char* const argv[])
193{
       ......
282    // Parse runtime arguments.  Stop at first unrecognized option.
283    bool zygote = false;
284    bool startSystemServer = false;
285    bool application = false;
286    String8 niceName;
287    String8 className;
288
289    ++i;  // Skip unused "parent dir" argument.
         //init.rc中会配置一些参数,这里进行比较设置一些变量走进不同的分支
290    while (i < argc) {
291        const char* arg = argv[i++];
292        if (strcmp(arg, "--zygote") == 0) {
               //启动的是Zygote进程
293            zygote = true;
294            niceName = ZYGOTE_NICE_NAME;
295        } else if (strcmp(arg, "--start-system-server") == 0) {
               //启动的是system-server进程
296            startSystemServer = true;
297        } else if (strcmp(arg, "--application") == 0) {
298            application = true;
299        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
300            niceName.setTo(arg + 12);
301        } else if (strncmp(arg, "--", 2) != 0) {
302            className.setTo(arg);
303            break;
304        } else {
305            --i;
306            break;
307        }
308    }
309
           .......
           //设置一个“好听的名字” zygote,之前的名称是app_process
357    if (!niceName.isEmpty()) {
358        runtime.setArgv0(niceName.string(), true /* setProcName */);
359    }
360
361    if (zygote) {
             //通过runtime启动zygote
364        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
365    } else if (className) {
366        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
367    } else {
368        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
369        app_usage();
370        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
371    }
372}

Zygote本身是一个Native的应用程序,刚开始的名字为“app_process”,运行过程中,通过调用setArgv0将名字改为Zygote,真正启动的地方是runtime的start方法,简单看一下runtime的start方法。

/*
987 * Start the Android runtime.  This involves starting the virtual machine
988 * and calling the "static void main(String[] args)" method in the class
989 * named by "className".
990 *
991 * Passes the main function two arguments, the class name and the specified
992 * options string.
993 */
994void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
995{
996    ALOGD(">>>>>> START %s uid %d <<<<<<\n",
997            className != NULL ? className : "(unknown)", getuid());
998
1026    /* start the virtual machine */
1027    JniInvocation jni_invocation;
1028    jni_invocation.Init(NULL);
1029    JNIEnv* env;
1030    if (startVm(&mJavaVM, &env, zygote) != 0) {
1031        return;
1032    }
1033    onVmCreated(env);
1034
1035    /*
1036     * Register android functions.
1037     */
1038    if (startReg(env) < 0) {
1039        ALOGE("Unable to register all android natives\n");
1040        return;
1041    }
1042
1043    /*
1044     * We want to call main() with a String array with arguments in it.
1045     * At present we have two arguments, the class name and an option string.
1046     * Create an array to hold them.
1047     */
1048    jclass stringClass;
1049    jobjectArray strArray;
1050    jstring classNameStr;
1051
1052    stringClass = env->FindClass("java/lang/String");
1053    assert(stringClass != NULL);
1054    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
1055    assert(strArray != NULL);
1056    classNameStr = env->NewStringUTF(className);
1057    assert(classNameStr != NULL);
1058    env->SetObjectArrayElement(strArray, 0, classNameStr);
1059
1060    for (size_t i = 0; i < options.size(); ++i) {
1061        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
1062        assert(optionsStr != NULL);
1063        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
1064    }
1065
1066    /*
1067     * Start VM.  This thread becomes the main thread of the VM, and will
1068     * not return until the VM exits.
1069     */
1070    char* slashClassName = toSlashClassName(className);
1071    jclass startClass = env->FindClass(slashClassName);
1072    if (startClass == NULL) {
1073        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
1074        /* keep going */
1075    } else {
1076        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
1077            "([Ljava/lang/String;)V");
1078        if (startMeth == NULL) {
1079            ALOGE("JavaVM unable to find main() in '%s'\n", className);
1080            /* keep going */
1081        } else {
1082            env->CallStaticVoidMethod(startClass, startMeth, strArray);
1083
1084#if 0
1085            if (env->ExceptionCheck())
1086                threadExitUncaughtException(env);
1087#endif
1088        }
1089    }
1090    free(slashClassName);
1091   //这行Log比较常见,因为其他应用进程也是由zygote 进程fork 出来的,所有其他进程也包含这段代码,如果其他进程在java 层crash,那么也会走到这里
1092    ALOGD("Shutting down VM\n");
1093    if (mJavaVM->DetachCurrentThread() != JNI_OK)
1094        ALOGW("Warning: unable to detach main thread\n");
1095    if (mJavaVM->DestroyJavaVM() != 0)
1096        ALOGW("Warning: VM did not shut down cleanly\n");
1097}
1098

代码很简单,主要做了三件事情,一调用startVm开启虚拟机,二调用startReg注册JNI方法,三就是使用JNI把Zygote进程启动起来。

996    ALOGD(">>>>>> START %s uid %d <<<<<<\n",
997            className != NULL ? className : "(unknown)", getuid());

这个是进入Zygote进程的重要依据,开机的时候一般都会打印这一行Log。如

07-09 14:40:37.788 16504 16504 D AndroidRuntime: >>>>>> START com.android.internal.os.ZygoteInit uid 0 <<<<<<

如果遇到不能开机的情况,这行Log没有打开,极有可能不是上层的问题。

1.2、Socket是怎么注册的?

这个问题还用说嘛,看一下ZygoteServer类的registerServerSocket不就OK了吗,不要觉得这里很容易,其实彻底弄明白还是需要一些思考的。

    frameworks/base/core/java/com/android/internal/os/ZygoteServer.java
    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";

    private LocalServerSocket mServerSocket;
    /**
     * Registers a server socket for zygote command connections
     *
     * @throws RuntimeException when open fails
     */
    void registerServerSocket(String socketName) {
        //看起来是用了一个单例
        if (mServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                //从环境变量中获取名为ANDROID_SOCKET_zygote的fd
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                //构建JAVA中的FD对象
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
               //用上面的FD创建LocalServerSocket
                mServerSocket = new LocalServerSocket(fd);
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }

其中 参数 socketName = "zygote";注册的过程实际上就是生成一个mServerSocket对象,用来接收Client端的请求,这里又有两个小问题了。

  • Socket FD是怎么生成的,在哪里创建?
  • Android中socket整体的通信框架是怎样的。

我们先看第二个问题,看看下面这张图。

Android Socket通信框架.png

LocalSocket就是作为客户端建立于服务端的连接,发送数据。LocalServerSocket作为服务端使用,建立服务端的socket监听客户端请求。典型的C/S架构!!!LocalServerSocket构造函数看到有两种方式:
第一种

frameworks/base/core/java/android/net/LocalServerSocket.java
    /**
    * Creates a new server socket listening at specified name.
    * On the Android platform, the name is created in the Linux
    * abstract namespace (instead of on the filesystem).
    * 
    * @param name address for socket
    * @throws IOException
    */
   public LocalServerSocket(String name) throws IOException
   {
       //1、创建服务端socket对象
       impl = new LocalSocketImpl();

       impl.create(LocalSocket.SOCKET_STREAM);

       //2、设置地址
       localAddress = new LocalSocketAddress(name);
       //3、绑定地址
       impl.bind(localAddress);
       //4、监听
       impl.listen(LISTEN_BACKLOG);
   }

第二种

   /**
    * Create a LocalServerSocket from a file descriptor that's already
    * been created and bound. listen() will be called immediately on it.
    * Used for cases where file descriptors are passed in via environment
    * variables
    *
    * @param fd bound file descriptor
    * @throws IOException
    */
   public LocalServerSocket(FileDescriptor fd) throws IOException
   {
       impl = new LocalSocketImpl(fd);
       impl.listen(LISTEN_BACKLOG);
       localAddress = impl.getSockAddress();
   }

从上面看到在Zygote中创建服务端的socket,使用的就是第二种。对于这种C/S架构,一般性的用法是这样子的。通常服务端会有个死循环不断响应客户端发送来的请求

//创建socket并绑定监听 新创建的
LocalServerSocket server = new LocalServerSocket(SOCKET_ADDRESS);
while (true) {
  //等待建立连接
  LocalSocket receiver = server.accept();

  //接收获取数据流
  InputStream input = receiver.getInputStream();
  
  ……
}

然后,客户端就可以用下面代码向服务端发送请求

String message;

//创建socket
LocalSocket sender = new LocalSocket();

//建立对应地址连接
sender.connect(new LocalSocketAddress(SOCKET_ADDRESS));

//发送写入数据
sender.getOutputStream().write(message.getBytes());

//关闭socket
sender.getOutputStream().close();

在系统创建进程的时候,为了更好的管理LocalSocket的输入流和输出流对象,将这个过程的返回结果封装成了ZygoteState,都在ZygoteProcess的connect方法中。

frameworks/base/core/java/android/os/ZygoteProcess.java
        public static ZygoteState connect(String socketAddress) throws IOException {
            DataInputStream zygoteInputStream = null;
            BufferedWriter zygoteWriter = null;
            final LocalSocket zygoteSocket = new LocalSocket();

            try {
                zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                        LocalSocketAddress.Namespace.RESERVED));

                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());

                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                        zygoteSocket.getOutputStream()), 256);
            } catch (IOException ex) {
                try {
                    zygoteSocket.close();
                } catch (IOException ignore) {
                }

                throw ex;
            }

            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
            Log.i("Zygote", "Process: zygote socket " + socketAddress + " opened, supported ABIS: "
                    + abiListString);

            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                    Arrays.asList(abiListString.split(",")));
        }

好,上面基本就是Android中socket通信框架。主要把握LocalSocket与LocalServerSocket的用法。现在继续研究第一个问题,FD是在哪里创建的呢,为什么可以从环境变量中获取呢。Socket的监听方式为使用Linux系统调用select()函数监听Socket文件描述符,当该文件描述符上有数据时,自动触发中断,在中断处理函数中去读取文件描述符中的数据。

在关机状态下,我们可以看到dev/socket的文件有

android:/dev/socket # ls -la
total 0
drwxr-xr-x  2 root   root    160 1970-12-23 09:50 .
drwxr-xr-x 14 root   root   3440 1970-12-23 09:49 ..
srw-rw----  1 system system    0 1970-12-23 09:50 adbd
srw-rw-rw-  1 root   root      0 1970-12-23 09:49 property_service
srw-rw----  1 system system    0 1970-12-23 09:49 thermal-recv-client
srw-rw-rw-  1 system system    0 1970-12-23 09:49 thermal-recv-passive-client
srw-rw-rw-  1 system system    0 1970-12-23 09:49 thermal-send-client
srw-rw----  1 system system    0 1970-12-23 09:49 thermal-send-rule

开机状态下呢

jason:/dev/socket # ls -la
total 0
drwxr-xr-x  7 root           root       840 2018-07-09 19:24 .
drwxr-xr-x 14 root           root      3660 1970-12-23 06:29 ..
srw-rw----  1 system         system       0 2018-07-09 19:24 adbd
srw-rw-rw-  1 system         system       0 1970-12-23 06:29 audio_hw_socket
srw-rw----  1 root           mount        0 1970-12-23 06:28 cryptd
srw-rw----  1 root           inet         0 2018-07-09 16:05 dnsproxyd
srw-rw----  1 root           system       0 2018-07-09 16:05 dpmd
srw-rw----  1 root           inet         0 2018-07-09 16:05 dpmwrapper
srw-rw----  1 root           inet         0 2018-07-09 16:05 fwmarkd
srw-rw----  1 system         radio        0 2018-07-09 16:05 ims_datad
srw-rw----  1 system         radio        0 1970-12-23 06:29 ims_qmid
srw-rw----  1 radio          radio        0 2018-07-09 16:05 ipacm_log_file
srw-rw----  1 system         system       0 1970-12-23 06:29 lmkd
srw-rw-rw-  1 logd           logd         0 1970-12-23 06:28 logd
srw-rw-rw-  1 logd           logd         0 1970-12-23 06:28 logdr
s-w--w--w-  1 logd           logd         0 1970-12-23 06:28 logdw
srw-rw----  1 root           system       0 2018-07-09 16:05 mdns
srw-rw-rw-  1 gps            gps          0 2018-07-09 16:05 mlid
srw-rw----  1 root           system       0 2018-07-09 16:05 netd
drwxr-x---  2 radio          radio       60 2018-07-09 16:05 netmgr
srw-rw----  1 system         system       0 1970-12-23 06:29 pps
srw-rw-rw-  1 root           root         0 1970-12-23 06:28 property_service
drwxrws---  2 media          audio       40 1970-12-23 06:29 qmux_audio
drwxrws---  2 bluetooth      bluetooth   40 1970-12-23 06:29 qmux_bluetooth
drwxrws---  2 gps            gps         40 1970-12-23 06:29 qmux_gps
drwxrws---  2 radio          radio      120 2018-07-09 16:05 qmux_radio
srw-rw----  1 radio          system       0 2018-07-09 16:05 rild-debug2
srw-rw----  1 root           radio        0 2018-07-09 16:05 rild2
srw-rw-rw-  1 system         system       0 2018-07-09 16:05 seempdw
srw-rw----  1 root           inet         0 2018-07-09 16:05 tcm
srw-rw----  1 system         system       0 1970-12-23 06:29 thermal-recv-client
srw-rw-rw-  1 system         system       0 1970-12-23 06:29 thermal-recv-passive-client
srw-rw-rw-  1 system         system       0 1970-12-23 06:29 thermal-send-client
srw-rw----  1 system         system       0 1970-12-23 06:29 thermal-send-rule
srw-rw-rw-  1 system         system       0 2018-07-09 16:05 tombstoned_crash
srw-rw-rw-  1 system         system       0 2018-07-09 16:05 tombstoned_intercept
srw-rw-rw-  1 system         system       0 2018-07-09 16:05 tombstoned_java_trace
srw-rw----  1 root           mount        0 1970-12-23 06:28 vold
srw-rw----  1 webview_zygote system       0 2018-07-09 16:05 webview_zygote
srw-rw----  1 wifi           wifi         0 2018-07-09 16:05 wpa_wlan0
srw-rw----  1 root           system       0 1970-12-23 06:29 zygote
srw-rw----  1 root           system       0 1970-12-23 06:29 zygote_secondary

最后两行是不是多了一个zygote和zygote_secondary呢?这两个是怎么来的呢,这个就得从init.rc文件了,内核启动完成之后会去读取init.rc文件,启动开机需要启动的进程。

在Android5.0中,Zygote的启动发生了一些变化,以前直接放在init.rc中的代码块放到了单独的文件中,在init.rc中通过import的方式引入文件,如下:

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

所以init.rc并不是直接引入某个固定的文件,而是根据属性“ro.zygote”的内容来引入system/core/init/目录下不同的文件,这个目录下目前有Init.zygote64.rc,Init.zygote32.rc,Init.zygote32_64.rc,Init.zygot64_32.rc。与之对应的属性 ro.zygote 的值可为:zygote32、zygote64、zygote32_64、zygote64_32。

init.zygote32.rc:zygote 进程对应的执行程序是 app_process (纯 32bit 模式)
init.zygote64.rc:zygote 进程对应的执行程序是 app_process64 (纯 64bit 模式)
init.zygote32_64.rc:启动两个 zygote 进程 (名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process32 (主模式)、app_process64。
init.zygote64_32.rc:启动两个 zygote 进程 (名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process64 (主模式)、app_process32。

什么意思呢?举个例子看zygot64_32.rc。

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    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 zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
    class main
    priority -20
    user root
    group root readproc
    socket zygote_secondary stream 660 root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks

其中

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote

这行表示zygote进程以服务的方式启动,对应的native应用程序是/system/bin/app_process64,给这个zygote进程传递了5个参数,分别是-Xzygote,/system/bin,--zygote,--start-system-server,--socket-name=zygote。可以看到zygot64_32.rc里面定义了两个Zygote服务:zygote和zygote_secondary。zygote为主,zygote_secondary为辅。

    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond

onrestart后面跟的Zygote重启需要执行的命令,audioserver,cameraserver,media,netd,wificond,当Zygote进程重启了,这些进程都会重启。

  socket zygote stream 660 root system

这行表示Zygote进程在启动过程中,会在dev/socket目录下创建一个Socket,权限为660,表示所有的用户都可以对他进行读写,当init 解析到这样一条语句,它将做下面两件事:

  • 调用 create_socket() (system/core/init/util.c), 创建一个Socket fd, 将这个fd 与某个文件(/dev/socket/xxx, xxx 就是上面列到的名字,比如,zygote) 绑定(bind), 根据init.rc 里面定义来设定相关的用户,组和权限。最后返回这个fd。
  • 将socket 名字(带‘ANDROID_SOCKET_'前缀)(比如 zygote) 和 fd 注册到init 进程的环境变量里,这样所有的其他进程(所有进程都是init的子进程)都可以通过 getenv(name)获取到这个fd.
system/core/init/descriptors.cpp

45#define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_"
46#define ANDROID_SOCKET_DIR        "/dev/socket"

50void DescriptorInfo::CreateAndPublish(const std::string& globalContext) const {
51  // Create
52  const std::string& contextStr = context_.empty() ? globalContext : context_;
53  int fd = Create(contextStr);
54  if (fd < 0) return;
55
56  // Publish
57  std::string publishedName = key() + name_;
58  std::for_each(publishedName.begin(), publishedName.end(),
59                [] (char& c) { c = isalnum(c) ? c : '_'; });
60
61  std::string val = android::base::StringPrintf("%d", fd);
     //将创建的socket 的fd 放入 环境变量:ANDROID_SOCKET_zygote 中,以便在zygote进程中,获取此socket的fd 
62  add_environment(publishedName.c_str(), val.c_str());
63
64  // make sure we don't close on exec
65  fcntl(fd, F_SETFD, 0);
66}
80int SocketInfo::Create(const std::string& context) const {
81  int flags = ((type() == "stream" ? SOCK_STREAM :
82                (type() == "dgram" ? SOCK_DGRAM :
83                 SOCK_SEQPACKET)));
      //创建名为zygote 的socket 
84  return create_socket(name().c_str(), flags, perm(), uid(), gid(), context.c_str());
85}
86
87const std::string SocketInfo::key() const {
88  return ANDROID_SOCKET_ENV_PREFIX;
89}
123const std::string FileInfo::key() const {
124  return ANDROID_FILE_ENV_PREFIX;
125}

现在应该明白了registerServerSocket的来龙去脉了吧,尤其是这个socket的fd是怎么来的

    frameworks/base/core/java/com/android/internal/os/ZygoteServer.java
    private static final String ANDROID_SOCKET_PREFIX = "ANDROID_SOCKET_";

    private LocalServerSocket mServerSocket;
    /**
     * Registers a server socket for zygote command connections
     *
     * @throws RuntimeException when open fails
     */
    void registerServerSocket(String socketName) {
        //看起来是用了一个单例
        if (mServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                //从环境变量中获取名为ANDROID_SOCKET_zygote的fd
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                //构建JAVA中的FD对象
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
               //用上面的FD创建LocalServerSocket
                mServerSocket = new LocalServerSocket(fd);
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }

总结:init进程中add环境变量,Zygote进程中get环境变量。

1.3、Zygote进程预加载资源

  • 何为预加载?
    android系统资源加载分两种方式,预加载和使用进程中加载。 预加载是指在zygote进程启动的时候就加载,这样系统只在zygote执行一次加载操作,所有APP用到该资源不需要再重新加载,减少资源加载时间,加快了应用启动速度,一般情况下,系统中App共享的资源会被列为预加载资源。
  • 预加载是什么原理?
    预加载的原理很简单,就是在zygote进程启动后将资源读取出来,保存到Resources一个全局静态变量中,下次读取系统资源的时候优先从静态变量中查找。主要代码在zygoteInit.java类中方法preloadResources(),主要代码如下:
  • 系统哪些资源被预加载了?
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
124    static void preload(BootTimingsTraceLog bootTimingsTraceLog) {
125        Log.d(TAG, "begin preload");
126        bootTimingsTraceLog.traceBegin("BeginIcuCachePinning");
127        beginIcuCachePinning();
128        bootTimingsTraceLog.traceEnd(); // BeginIcuCachePinning
129        bootTimingsTraceLog.traceBegin("PreloadClasses");
130        preloadClasses();
131        bootTimingsTraceLog.traceEnd(); // PreloadClasses
132        bootTimingsTraceLog.traceBegin("PreloadResources");
133        preloadResources();
134        bootTimingsTraceLog.traceEnd(); // PreloadResources
135        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
136        preloadOpenGL();
137        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
138        preloadSharedLibraries();
139        preloadTextResources();
140        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
141        // for memory sharing purposes.
142        WebViewFactory.prepareWebViewInZygote();
143        endIcuCachePinning();
144        warmUpJcaProviders();
145        Log.d(TAG, "end preload");
146
147        sPreloadComplete = true;
148    }

系统哪些资源被预加载了?加载的有类,资源,共享库,

1.3.1 加载类----preloadClasses

preload方法中会调用会preloadClasses预加载一些类,这些类记录在[frameworks/base/preloaded-classes](http://androidxref.com/8.0.0_r4/xref/frameworks/base/preloaded-classes)文本文件里。大概有四千多个,下面列举一下。

32[Landroid.accounts.Account;
33[Landroid.animation.Animator;
34[Landroid.animation.Keyframe$FloatKeyframe;
35[Landroid.animation.Keyframe$IntKeyframe;
36[Landroid.animation.PropertyValuesHolder;
37[Landroid.app.LoaderManagerImpl;
38[Landroid.app.Notification$Action;
39[Landroid.app.NotificationChannel;
40[Landroid.app.RemoteInput;
41[Landroid.app.job.JobInfo$TriggerContentUri;
42[Landroid.bluetooth.BluetoothDevice;
43[Landroid.content.ContentProviderResult;
44[Landroid.content.ContentValues;
45[Landroid.content.Intent;
46[Landroid.content.UndoOwner;
47[Landroid.content.pm.ActivityInfo;
48[Landroid.content.pm.ConfigurationInfo;
49[Landroid.content.pm.FeatureGroupInfo;
50[Landroid.content.pm.FeatureInfo;
51[Landroid.content.pm.InstrumentationInfo;
52[Landroid.content.pm.PathPermission;
53[Landroid.content.pm.PermissionInfo;
54[Landroid.content.pm.ProviderInfo;
55[Landroid.content.pm.ServiceInfo;
56[Landroid.content.pm.Signature;
57[Landroid.content.res.Configuration;
58[Landroid.content.res.StringBlock;
59[Landroid.content.res.XmlBlock;
60[Landroid.database.CursorWindow;
61[Landroid.database.sqlite.SQLiteConnection$Operation;
62[Landroid.database.sqlite.SQLiteConnectionPool$AcquiredConnectionStatus;
63[Landroid.graphics.Bitmap$CompressFormat;
64[Landroid.graphics.Bitmap$Config;
65[Landroid.graphics.Bitmap;
66[Landroid.graphics.Canvas$EdgeType;
67[Landroid.graphics.ColorSpace$Model;
68[Landroid.graphics.ColorSpace$Named;
69[Landroid.graphics.ColorSpace;
70[Landroid.graphics.FontFamily;
71[Landroid.graphics.Interpolator$Result;

上面文件中列举的四千多个类都要通过Class.forName加载到系统中,生成字节码。

229    /**
230     * Performs Zygote process initialization. Loads and initializes
231     * commonly used classes.
232     *
233     * Most classes only cause a few hundred bytes to be allocated, but
234     * a few will allocate a dozen Kbytes (in one case, 500+K).
235     */
236    private static void preloadClasses() {
237        final VMRuntime runtime = VMRuntime.getRuntime();
238
239        InputStream is;
240        try {
241            is = new FileInputStream(PRELOADED_CLASSES);
242        } catch (FileNotFoundException e) {
243            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
244            return;
266            droppedPriviliges = true;
267        }
               ........
274        try {
275            BufferedReader br   = new BufferedReader(new InputStreamReader(is), 256);
278            int count = 0;
279            String line;
280            while ((line = br.readLine()) != null) {
281                // Skip comments and blank lines.
282                line = line.trim();
                      //跳过文件中注释的部分
283                if (line.startsWith("#") || line.equals("")) {
284                    continue;
285                }
286
287                Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
288                try {
289                    if (false) {
290                        Log.v(TAG, "Preloading " + line + "...");
291                    }
292                    // Load and explicitly initialize the given class. Use
293                    // Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups
294                    // (to derive the caller's class-loader). Use true to force initialization, and
295                    // null for the boot classpath class-loader (could as well cache the
296                    // class-loader of this class in a variable).
297                    Class.forName(line, true, null);
298                    count++;
299                } catch (ClassNotFoundException e) {
300                    Log.w(TAG, "Class not found for preloading: " + line);
301                } catch (UnsatisfiedLinkError e) {
302                    Log.w(TAG, "Problem preloading " + line + ": " + e);
303                } catch (Throwable t) {
304                 ........
314            }
315
316            Log.i(TAG, "...preloaded " + count + " classes in "
317                    + (SystemClock.uptimeMillis()-startTime) + "ms.");
318        } catch (IOException e) {
319            Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
320        } finally {
                  .....
339        }
340    }
1.3.2 加载资源----preloadResources

系统中有大量的资源可以直接被App所使用,比如一个颜色,一个drawble,这些都是通过preloadResources加载的。

/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
342    /**
343     * Load in commonly used resources, so they can be shared across
344     * processes.
345     *
346     * These tend to be a few Kbytes, but are frequently in the 20-40K
347     * range, and occasionally even larger.
348     */
349    private static void preloadResources() {
350        final VMRuntime runtime = VMRuntime.getRuntime();
351
352        try {
353            mResources = Resources.getSystem();
354            mResources.startPreloading();
355            if (PRELOAD_RESOURCES) {
356                Log.i(TAG, "Preloading resources...");
357
358                long startTime = SystemClock.uptimeMillis();
                     //1、加载preloaded_drawables中定义的资源,这里面定义的基本是图片
359                TypedArray ar = mResources.obtainTypedArray(
360                        com.android.internal.R.array.preloaded_drawables);
361                int N = preloadDrawables(ar);
362                ar.recycle();
363                Log.i(TAG, "...preloaded " + N + " resources in "
364                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
365
366                startTime = SystemClock.uptimeMillis();
                       //2、加载preloaded_color_state_lists中定义的资源,这里面定义的基本是颜色
367                ar = mResources.obtainTypedArray(
368                        com.android.internal.R.array.preloaded_color_state_lists);
                      //如果是颜色资源,需要调用preloadColorStateLists加载,见下面的preloadColorStateLists代码的解释
369                N = preloadColorStateLists(ar);
370                ar.recycle();
371                Log.i(TAG, "...preloaded " + N + " resources in "
372                        + (SystemClock.uptimeMillis()-startTime) + "ms.");
373
                      //3、加载config_freeformWindowManagement中定义的资源,这里面定义的基本是图片
374                if (mResources.getBoolean(
375                        com.android.internal.R.bool.config_freeformWindowManagement)) {
376                    startTime = SystemClock.uptimeMillis();
377                    ar = mResources.obtainTypedArray(
378                            com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
379                    N = preloadDrawables(ar);
380                    ar.recycle();
381                    Log.i(TAG, "...preloaded " + N + " resource in "
382                            + (SystemClock.uptimeMillis() - startTime) + "ms.");
383                }
384            }
385            mResources.finishPreloading();
386        } catch (RuntimeException e) {
387            Log.w(TAG, "Failure preloading resources", e);
388        }
389    }

preloaded_drawables、preloaded_color_state_lists、preloaded_freeform_multi_window_drawables都是在/frameworks/base/core/res/res/values/arrays.xml中定义的

/frameworks/base/core/res/res/values/arrays.xml
20<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
21
22    <!-- Do not translate. These are all of the drawable resources that should be preloaded by
23         the zygote process before it starts forking application processes. -->
24    <array name="preloaded_drawables">
25        <item>@drawable/ab_share_pack_material</item>
26        <item>@drawable/ab_solid_shadow_material</item>
27        <item>@drawable/action_bar_item_background_material</item>
28        <item>@drawable/activated_background_material</item>
               ......
138      <item>@drawable/toast_frame</item>
139    </array>


141    <!-- Do not translate. These are all of the color state list resources that should be
142         preloaded by the zygote process before it starts forking application processes. -->
143    <array name="preloaded_color_state_lists">
144        <item>@color/primary_text_dark</item>
145        <item>@color/primary_text_dark_disable_only</item>
146        <item>@color/primary_text_dark_nodisable</item>
                .......
186       <item>@color/search_url_text_material_light</item>
187    </array>

189   <array name="preloaded_freeform_multi_window_drawables">
190      <item>@drawable/decor_maximize_button_dark</item>
191      <item>@drawable/decor_maximize_button_light</item>
192   </array>

对于颜色资源,需要调用preloadColorStateLists来加载

/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
391    private static int preloadColorStateLists(TypedArray ar) {
392        int N = ar.length();
393        for (int i=0; i<N; i++) {
394            int id = ar.getResourceId(i, 0);
395            if (false) {
396                Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
397            }
398            if (id != 0) {
399                if (mResources.getColorStateList(id, null) == null) {
400                    throw new IllegalArgumentException(
401                            "Unable to find preloaded color resource #0x"
402                            + Integer.toHexString(id)
403                            + " (" + ar.getString(i) + ")");
404                }
405            }
406        }
407        return N;
408    }

对于preloadOpenGL、preloadSharedLibraries和preloadTextResources在此不一一分析了,原来ZygoteInit中的preload方法加载了这么多资源,这个也就是为什么开机慢而打开一个应用快的原因之一。我们能不能把加载资源的这些耗时操作放到子线程中做呢?

671    public static void main(String argv[]) {
672        ZygoteServer zygoteServer = new ZygoteServer();
673
674        // Mark zygote start. This ensures that thread creation will throw
675        // an error.
676        ZygoteHooks.startZygoteNoThreadCreation();
677
                .......
756        ZygoteHooks.stopZygoteNoThreadCreation();
757     }
禁止使用子线程.png

看到为防止多线程带来的同步问题,google这个地方禁止开启多线程。故网上有很多说法,尝试把这些代码放到子线程中去做应该是不对的(Android O)。

下一篇将在此基础上梳理SystemServer进程的创建流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容