Android 10.0 源碼閱讀 | zygote 啓動主函數是如何肯定的 | 番外篇

閱讀源碼的文章會是一個系列,本篇主要內容是 zygote 啓動主函數是如何肯定的。html

zygote 啓動主函數是如何肯定的

上篇文章中提到了屬性服務,我以爲它更像關於這臺手機的各類系統信息,並經過 key / value 的形式供全部程序使用。在手機上進入 adb shell 後輸入 getprop 能夠獲取到各類屬性值,這裏只關注 zygote 的值,拿到它有什麼用呢?實際上是爲了肯定這臺手機上的 zygote 的 rc 配置文件具體是使用的那個。java

generic_x86:/ $ getprop
...
//  zygote 啓動該啓動哪一個
//  ro 表示 read only
[ro.zygote]: [zygote32]
...
複製代碼

由於其實在 AOSP 的 /system/core/rootdir/ 下面其實如今有四個關於 zygote 的 rc 文件。android

而 init.rc (init 進程中會解析這個文件)中是這樣導入的:ios

...
//  這裏就是經過屬性值確認
import /init.${ro.zygote}.rc
...
# Mount filesystems and start core system services.
on late-init
    ...
    //  在這個階段觸發 zygote 的啓動
    # Now we can start zygote for devices with file based encryption
    trigger zygote-start
    ...
...
複製代碼

這時就會根據屬性服務中 ro.zygote 取到的值,來到對應的 zygote rc 文件中了,而對於個人虛擬就是找到了 init.zygote32.rc 文件中,下面就是它的原文內容:c++

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    socket usap_pool_primary 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
複製代碼

看上面內容,能夠知道 zygote 對應的可執行程序是 /system/bin/app_process,不過我看到了很多文章都直接說出對應的代碼文件實際上是 /frameworks/base/cmds/app_process/app_main.cpp,我對這個結論十分困擾,雖然我也看了確實在 app_main.cpp 指定了 com.android.internal.os.ZygoteInit,但仍是不太理解這樣是一個怎麼關係。shell

其實在沒有摸索到答案以前,我也全局搜索過 app_process,也確實找到了 /frameworks/base/cmds/app_process/ 目錄,下面 app_main.cpp 也確實指定了 ZygoteInit 是 zygote 的啓動入口,但容易讓人忽略的實際上是同目錄下的 Android.mk,這裏面纔是解決迷惑的關鍵:bash

//  指定了依賴的庫
app_process_common_shared_libs := \
    libandroid_runtime \
    libbinder \
    libcutils \
    libdl \
    libhidlbase \
    liblog \
    libnativeloader \
    libutils \
//  ...
//  指定了要編譯的源文件
app_process_src_files := \
    app_main.cpp \
LOCAL_SRC_FILES:= $(app_process_src_files)
//  ...
//  指定了編譯後的模塊名
LOCAL_MODULE:= app_process
//  ...
//  這裏居然寫的是 BUILD_EXECUTABLE 而不是 BUILD_SHARED_LIBRARY 或 BUILD_STATIC_LIBRARY
include $(BUILD_EXECUTABLE)
//  ...
複製代碼

若是你在 Android 中開發過 NDK,那你應該對 Android.mk 比較熟悉(雖然如今 CMake 多一些),我以前用它來作 so 文件的編譯配置,因此滿腦子都在想這個應該是用來編譯動態庫或靜態庫的(我還真的全局搜了 libapp_process),沒想到這個 mk 下面直接寫的 BUILD_EXECUTABLE,也就是源文件編譯成可執行程序(若是寫的是 BUILD_SHARED_LIBRARY 表示編譯成動態庫,BUILD_STATIC_LIBRARY 編譯成靜態庫),而且還指定了名字,就叫 app_process,這下我終於明白了爲何啓動 zygote 會來到這裏。app

或許在別人眼裏這並非一個問題,不過對我而言,自己我不太懂 c/c++、編譯這些東西,確實研究了很久才解決了困擾我好久的這個問題,但我依然仍是有一個問題,就是它編譯以後怎麼就跑到 /system/bin/ 下面去了,或許應該是就在這個目錄下面編譯的?socket

固然若是肯定了 /frameworks/base/cmds/app_process/app_main.cpp,再去找 zygote 入口就容易多了。ide

int main(int argc, char* const argv[])
{
    //  ...
    if (zygote) {
        //  這裏就給出了 zygote 的啓動入口,而爲何調用了 main 方法,就是要看這個 start 函數
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } 
    // ...
}

//  由於在 runtime.start 中經過 jni 的方式調用到了 ZygoteInit 的 main 方法
//  對應的文件是 /frameworks/base/core/jni/AndroidRuntime.cpp
//  這裏的 className 就是上面傳進來的 com.android.internal.os.ZygoteInit
//  這樣寫法也就是 JNI 中調用 Java 的寫法,先找到 class,而後找到方法,進行調用
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName);
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
   "([Ljava/lang/String;)V");
if (startMeth == NULL) {
    ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
    env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
複製代碼

這個小問題解開了以後,那麼其實 Android init 進程的啓動入口問題也就一同解決了(對我來講),這個也只須要去看對應目錄下的 Android.mk 便可,最終會編譯爲了 /init 這個可執行程序,在 Linux 啓動 1 號進程時就會找到這個二進制文件。

關於 zygote 的源碼閱讀部分,會後續讀完相關源碼後進行整理輸出。

感謝與參考

Android.mk

android編譯系統makefile(Android.mk)寫法

最後的最後

源碼自己沒有歧義,不過因爲每一個人基礎不一樣,具體理解起來可能會些不一樣,因此有什麼問題,也請你們多指點,多交流。

若是你以爲我寫的還不錯的話,那就經過點贊,點贊,還 tm 是點讚的方式給我反饋吧,感謝你的支持。

相關文章
相關標籤/搜索