Android 系統(tǒng)啟動(dòng) - Zygote 進(jìn)程

前言

前面我們?cè)?Android 系統(tǒng)啟動(dòng) - init 進(jìn)程 中提到,init 進(jìn)程在解析完 init.rc 腳本后,會(huì)啟動(dòng) Zygote 進(jìn)程。具體來講,Zygote 是以服務(wù)(service)的形式配置在 init.rc 中,其對(duì)應(yīng)的可執(zhí)行文件為 /system/bin/app_processXX。init 進(jìn)程會(huì)通過 fork 為每個(gè)服務(wù)創(chuàng)建一個(gè)獨(dú)立的進(jìn)程。對(duì)于 Zygote 來說, /system/bin/app_processXX 就會(huì)被啟動(dòng),然后該進(jìn)程內(nèi)部最終會(huì)通過 AppRuntime.start 真正啟動(dòng) Zygote 進(jìn)程。

接下來,我們就來分析下 Zygote 具體的啟動(dòng)過程:

Zygote 啟動(dòng)過程

我們先來看下啟動(dòng) Zygote 服務(wù)源碼:
framework/cmds/app_process/app_main.cpp

int main(int argc, char* const argv[])
{
    ···
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } 
    ···
}

看到調(diào)用的是 runtime.start("com.android.internal.os.ZygoteInit", args, zygote);,看到 "com.android.internal.os.ZygoteInit"這種包名形式,就大概知道 runtime.start 該函數(shù)內(nèi)部應(yīng)該會(huì)通過 JNI 來調(diào)用 Java 層的類了。究竟是不是,我們進(jìn)入看一下:
frameworks/base/core/jni/AndroidRuntime.cpp

/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
   ···
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    // 創(chuàng)建 Java 虛擬機(jī)
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    // JNI 方法注冊(cè)
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    // 相當(dāng)于 strArray= new String[options.size() + 1];
    stringClass = env->FindClass("java/lang/String");
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);

    // 相當(dāng)于 strArray[0] = "com.android.internal.os.ZygoteInit"
    classNameStr = env->NewStringUTF(className);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    // 等價(jià) strArray[1] = "start-system-server";
    // strArray[2] = "--abi-list=xxx";
    // 其中xxx為系統(tǒng)響應(yīng)的cpu架構(gòu)類型,比如arm64-v8a.
    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    // 將"com.android.internal.os.ZygoteInit"轉(zhuǎn)換為"com/android/internal/os/ZygoteInit"
    char* slashClassName = toSlashClassName(className);
    // 找到類 "com/android/internal/os/ZygoteInit"
    jclass startClass = env->FindClass(slashClassName);
    ···
    // 找到類 "com/android/internal/os/ZygoteInit"的靜態(tài)方法main(String[])
    jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
        "([Ljava/lang/String;)V");
    ···
    // 調(diào)用 ZygoteInit.main(String[])
    env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    //釋放相應(yīng)對(duì)象的內(nèi)存空間
    free(slashClassName);

    ALOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanly\n");
}

該方法主要就是啟動(dòng) Android 運(yùn)行時(shí),主要做了兩件事:創(chuàng)建虛擬機(jī) startVm 和 注冊(cè) JNI 方法 startReg 并啟動(dòng) ZygoteInit.main 函數(shù)。

到這里,Zygote 服務(wù)進(jìn)程通過 JNI 反射調(diào)用 Java 層代碼,正式將 Android 運(yùn)行環(huán)境從 c++ 層轉(zhuǎn)移到 Java 層。Zygote 進(jìn)程也同時(shí)成為 Android 系統(tǒng)第一個(gè) Java 進(jìn)程。

那我們接下來看看 ZygoteInit.main 函數(shù)源碼:
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

public static void main(String argv[]) {
    try {
        // 開啟DDMS功能
        RuntimeInit.enableDdms();
        ···
        // 為Zygote注冊(cè)Socket
        registerZygoteSocket(socketName);
        ···
        // 預(yù)加載類和資源
        preload();
        ···
        // Do an initial gc to clean up after startup
        // GC 釋放資源
        gcAndFinalize();
        ···
        if (startSystemServer) {
            // 啟動(dòng) SystemServer 進(jìn)程
            startSystemServer(abiList, socketName);
        }
        // 進(jìn)入監(jiān)聽狀態(tài),等待客戶端請(qǐng)求
        runSelectLoop(abiList);
        // 關(guān)閉Socket
        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        caller.run(); // 真正啟動(dòng) SystemServer
    } catch (RuntimeException ex) {
        Log.e(TAG, "Zygote died with exception", ex);
        // 關(guān)閉Socket
        closeServerSocket();
        throw ex;
    }
}

可以看到,ZygoteInit.main 方法主要做了四件事:

  • 為 Zygote 進(jìn)程注冊(cè)一個(gè)服務(wù)端 Socket:registerZygoteSocket
  • 預(yù)加載類和資源:preload
  • 啟動(dòng) SystemServer 進(jìn)程:startSystemServer
  • 開啟 Socket 監(jiān)聽,等待客戶端請(qǐng)求:runSelectLoop

下面我們依次分析上述四個(gè)操作:

首先來看下 Zygote 注冊(cè)服務(wù)端 Socket 具體過程:registerZygoteSocket
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

private static void registerZygoteSocket(String socketName) {
        if (sServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                // 從環(huán)境變量中獲取Socket套接字描述符
                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); // 設(shè)置文件描述符
                sServerSocket = new LocalServerSocket(fd);// 創(chuàng)建Socket的本地服務(wù)端 
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }

其實(shí)就是從環(huán)境變量中獲取 Socket 套接字文件描述符(此處的 socket 是由 service_start 創(chuàng)建的,并且創(chuàng)建完后會(huì)通過 publish_socket 方法發(fā)布出去,發(fā)布的途徑就是設(shè)置到環(huán)境變量中),然后創(chuàng)建 Socket 本地服務(wù)端。

接下來看下 Zygote 預(yù)加載類和資源的具體過程:preload
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

static void preload() {
        Log.d(TAG, "begin preload");
        // 預(yù)加載常用類:/system/etc/preloaded-classes
        preloadClasses();
        // 預(yù)加載資源文件,包含drawable和color資源
        preloadResources();
        // 預(yù)加載OpenGL
        preloadOpenGL();
        // 預(yù)加載共享庫(kù):"android","compiler_rt" 和 "jnigraphics"
        preloadSharedLibraries();
        // 預(yù)加載文本連接符資源
        preloadTextResources();
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        // 為了達(dá)到內(nèi)存共享,WebViewFactory 需要進(jìn)行的初始化操作
        WebViewFactory.prepareWebViewInZygote();
        Log.d(TAG, "end preload");
    }

該方法沒有太多好說的,就是預(yù)加載了一些常用類庫(kù)和資源文件等,這樣后續(xù)創(chuàng)建新的虛擬機(jī)進(jìn)程時(shí),直接 fork Zygote 進(jìn)程,就無需再次預(yù)加載這些資源了,節(jié)省時(shí)間,使虛擬機(jī)能更快啟動(dòng)。

ZygoteInit.main 第三個(gè)主要操作就是啟動(dòng) SystemServer 進(jìn)程,該部分詳細(xì)內(nèi)容請(qǐng)查看:Android 系統(tǒng)啟動(dòng) - SystemServer 進(jìn)程

最后我們來看下 Zygote 進(jìn)程實(shí)現(xiàn)響應(yīng)客戶端請(qǐng)求的具體過程:
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

/**
     * Runs the zygote process's select loop. Accepts new connections as
     * they happen, and reads commands from connections one spawn-request's
     * worth at a time.
     *
     * @throws MethodAndArgsCaller in a child process when a main() should
     * be executed.
     */
    private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        // sServerSocket就是Zygote自己創(chuàng)建的Socket:registerZygoteSocket
        // 即 zygote 的 socket 通信服務(wù)端,保存到fds[0
        fds.add(sServerSocket.getFileDescriptor());
        peers.add(null);

        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 {
                // 處理輪詢狀態(tài),當(dāng)pollFds有事件到來時(shí)則往下執(zhí)行,否則阻塞
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                // 采用I/O多路復(fù)用機(jī)制,當(dāng)接收到客戶端發(fā)出連接請(qǐng)求 或者 數(shù)據(jù)處理請(qǐng)求到來,則往下執(zhí)行
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    // 即fds[0],代表的是sServerSocket,意味著有客戶端連接請(qǐng)求,則會(huì)創(chuàng)建ZygoteConnection對(duì)象,并添加到fds。
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    // i>0,說明接收到一個(gè)創(chuàng)建應(yīng)用進(jìn)程的請(qǐng)求,則調(diào)用ZygoteConnection的runOnce函數(shù)來創(chuàng)建一個(gè)新的應(yīng)用程序進(jìn)程。
                    boolean done = peers.get(i).runOnce();
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }

該方法采用 I/O 多路復(fù)用機(jī)制,高效地實(shí)現(xiàn)了 Zygote 進(jìn)程的 Socket 監(jiān)聽。當(dāng)有客戶端的連接請(qǐng)求或數(shù)據(jù)處理時(shí),則響應(yīng)客戶端請(qǐng)求。特別的,當(dāng)接收到客戶端請(qǐng)求創(chuàng)建應(yīng)用進(jìn)程時(shí),就會(huì)調(diào)用 ZygoteConnection.runOnce 方法創(chuàng)建一個(gè)新的應(yīng)用進(jìn)程,具體的進(jìn)程創(chuàng)建方法,請(qǐng)查看:理解Android進(jìn)程創(chuàng)建流程

至此,Zygote 進(jìn)程的啟動(dòng)流程分析結(jié)束。

總結(jié)

Zygote 進(jìn)程是由 init 進(jìn)程啟動(dòng)的,其對(duì)應(yīng)的可執(zhí)行文件為 /system/bin/app_processXX,其內(nèi)部最終會(huì)通過 AppRuntime.start方法啟動(dòng) Zygote 進(jìn)程:具體來說,該方法內(nèi)部會(huì)創(chuàng)建 Java 虛擬機(jī)(startVm) 和 注冊(cè) JNI 本地方法(startReg),最后通過 JNI 調(diào)用啟動(dòng) Java 層 Zygote 進(jìn)程(ZygoteInit.main),從而讓 Android 系統(tǒng)正式開啟第一個(gè) Java 進(jìn)程,即 Zygote。

Java 框架層的 Zygote 主要做的就是:創(chuàng)建服務(wù)端 Socket 并進(jìn)行監(jiān)聽(registerZygoteSocketrunSelectLoop),預(yù)加載常用類和資源,啟動(dòng) SystemServer 進(jìn)程。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評(píng)論 2 379