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閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容