Dalvik VM的啟動過程解析
Dalvik 虛擬機器的啟動要從android另一個著名的模組,Zygote,說起。
我們知道android系統中,每一個java應用被設計成可以運行在一個單獨的Linux進程中。而每個該進程都包含一個運行中的dalvik虛擬機器執行個體,用來執行該應用中的java位元組碼。很多java基礎類(比如,java.lang.*)和一些系統級的共用性資源(drawable/color...)等幾乎會被所有的進程使用。如果每次虛擬機器啟動後都去載入這些資源,必然是個很大浪費。其實每個虛擬機器裡載入這些資源的過程都是一模一樣的。Android系統就使用一個巧妙的方式解決這個問題。那就是Zygote的用處。
由於本系列文章的重點不是Zygote,而且Zygote的實現也很簡單,對於它的介紹網上已經很多了,本文不準備詳細介紹Zygote。而是簡單的說明一下它和Dalvik相連的部分。
Zygote其實是一個系統級本地服務,在init.rc裡定義(如下所示),在系統啟動早期被啟動起來。它原始名字是app_process(原始碼frameworks/base/cmds/app_process/app_main.cpp):
// @ system/core/rootdir/int.rc
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 666 onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart media onrestart restart netd
該服務會在啟動後載入前文提到基礎類和資源,並且建立socket(名稱是如上所示的zygote)。後面其他的java應用程式啟動後,AMS(ActivityManagerService,android中用來管理activity的java服務)會通過該管道請求Zygote fock()一個子進程。改子進程繼承zygote的記憶體區,從而直接用於zygote載入的類和資源。用這種方法,運行java應用的linux進程就避免了重複載入android基礎類和通用資源,從而極大的節省了時間開銷。
Dalvik vm 參考資料一文裡引用 【3】有對該過程形象的描述:
我們看到,所有應用(Maps, Browser, Home)都從 Zygote父進程那裡繼承了共用的庫。。
從以上描述可以猜測,dalvik啟動必然和zygote實現有關聯。的確是,dalvik虛擬機器執行個體啟動是zygote一手導致的。 本文將向你詳細展現這一過程。 但是,本文不打算直接用前面提到的順序跟蹤的方法展現從Zygote啟動dalvik虛擬機器執行個體的過程。相反,我們先採用反向追溯的方法。目的如下 1. 介紹反向追溯的使用 2. 加深對整個過程的印象。
從dalvik虛擬機器實現裡(dalvik/vm)我們看到有兩個檔案Init.h 和Init.cpp,從命名可以猜測出該檔案很可能和虛擬機器初始化有關係。初始化話肯定是指改啟動時完成的。開啟init.h,我們看到如下函數:
從命名來看,這很可能就是dalvik虛擬機器啟動和關閉的函數。 到Init.cpp裡我們可以看到他們的實現。雖然現在還不能完全看懂實現的細節,但可以肯定我們剛才對這兩個函數的判斷。
好,我們找到了虛擬機器初始化的啟動函數 dvmStartup()。由此,我們擁有了使用反向追溯的先決條件(一個後期的執行點)。根據這個點,我們就可以往前追溯出虛擬機器是如何被啟動的。 下面是簡單的流程說明:
1. 在代碼中全文檢索搜尋 改函數 dvmStartup()的使用。很幸運,只有一處真正的調用:
| dalvik/vm/Jni.cpp |
3494 dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv); |
那麼啟動函數必然是從jni.cpp裡調用的,這也和改函數的注釋相符(“usually invoked through JNI”)
2. 到Jni.cpp裡看到,對dvmStartup()調用是在函數JNI_CreateJavaVM()裡完成的。繼續往前搜尋該函數的調用。 過濾掉該函數的聲明和定義,我們看到如下幾處調用:
frameworks/base/services/surfaceflinger/DdmConnection.cpp |
43 if (JNI_CreateJavaVM(&vm, &env, &args) == 0) { |
dalvik/dalvikvm/Main.cpp |
212 if (JNI_CreateJavaVM(&vm, &env, &initArgs) < 0) { |
frameworks/base/core/jni/AndroidRuntime.cpp |
768 if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) { 769 LOGE("JNI_CreateJavaVM failed\n"); |
我們知道surfaceflinger是android中用於底層合成圖層的本地服務,不大可能作為所有虛擬機器執行個體啟動的起點。 另外兩個卻很有可能。
在前文 xxx (TODO),中我們提到, dalvik實現部署套件含兩個部分,dalvikvm可執行程式 和 libdvm共用庫。這個兩個調用正是對應了兩種調用啟動dalvik虛擬機器的方式的入口。可執行程式 dalvikvm在main.cpp中執行JNI_CreateJavaVM啟動虛擬機器執行個體,而其他的模組則通過 AndroidRuntime.cpp來啟動虛擬機器執行個體。
dalvikvm可執行程式我們在單獨的章節介紹,這裡我們來接續追溯普通情況下,邏輯是如何執行到AndroidRuntime.cpp裡啟動 虛擬機器執行個體的。
3. 進入AndroidRuntime.cpp我們看到,它是在 AndroidRuntime::startVm()裡調用的。繼續搜尋該函數的調用,只有一個,並且是同一檔案裡:
| frameworks/base/core/jni/AndroidRuntime.cpp |
464 int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) function in class:android::AndroidRuntime 832 if (startVm(&mJavaVM, &env) != 0) { |
調用它的函數如下所示:
從該函數的注釋可以看出我們追蹤的方向是正確的。
4. 繼續往前,在全文檢索搜尋start時,我們得到巨量的搜尋結果。畢竟這個函數名字太常用了。 搜尋AndroidRuntime::start卻只有它的定義,這也難怪,該方法不是個類方法,使用時不會用AndroidRuntime::start(xxx)調用的。
那隻能先搜尋AndroidRuntime 和 start() 同時出現的使用類,並且只搜尋必須有 start。這樣子一下子是搜尋結果減少到小於十分之一,並且很容易判斷出是不是start函數調用。在結果中,我們一眼就看到了它:
frameworks/base/cmds/app_process/app_main.cpp |
15 #include <android_runtime/AndroidRuntime.h> 25 "Usage: app_process [java-options] cmd-dir start-class-name [options]\n"); 28 class AppRuntime : public AndroidRuntime 68 * The easiest fix is to call FindClass here, early on before we start 88 AndroidRuntime* ar = AndroidRuntime::getRuntime(); 108 AndroidRuntime::onExit(code); 169 } else if (strcmp(arg, "--start-system-server") == 0) { 189 runtime.start("com.android.internal.os.ZygoteInit", 190 startSystemServer ? "start |
這個檔案名稱字是不是很眼熟? 是的,前面我們講過,Zygote實現的原始碼起點正是這個檔案。
5. 展開改檔案對start的調用,發現是AppRuntime執行個體在調用改函數,不是AndroidRuntime! 找到AppRuntime實現,發現它是AndroidRuntime的子類,並且沒有它並沒有覆蓋start()方法,那麼它必然是調用了AndroidRuntime的start()方法了。 至此我們已經追溯到Zygote是如何啟動Dalvik虛擬機器執行個體的。
出於好奇,我們再看看,start啟動的類"com.android.internal.os.ZygoteInit"到底做什麼。它的源碼在 frameworks/base/core/java/com/android/internal/os/ZygoteInit.java 裡面。我們從它的視線裡看到了很多和前面介紹的Zygote與DVM互動的實現。比如註冊socket,載入基礎類以及資源等。
至此我們完全可以順序的勾勒出DVM執行個體啟動的全過程了: