1. 程式人生 > >Android6.0中ART替換DVM的過程分析

Android6.0中ART替換DVM的過程分析

Zygote程序中的Dalvik虛擬機器是從AndroidRuntime::start這個函式開始建立的,所以從該函式開始:

 999 /*
1000  * Start the Android runtime.  This involves starting the virtual machine
1001  * and calling the "static void main(String[] args)" method in the class
1002  * named by "className".
1003  *
1004  * Passes the main function two arguments, the class name and the specified
1005
* options string. 1006 */ 1007 void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) 1008 { ... 1039 /* start the virtual machine */ 1040 JniInvocation jni_invocation; 1041 jni_invocation.Init(NULL); 1042 JNIEnv* env; 1043 if (startVm(&mJavaVM, &env, zygote) != 0
) { 1044 return; 1045 } 1046 onVmCreated(env); ... 1079 /* 1080 * Start VM. This thread becomes the main thread of the VM, and will 1081 * not return until the VM exits. 1082 */ 1083 char* slashClassName = toSlashClassName(className); 1084 jclass startClass = env->FindClass(slashClassName); 1085
if (startClass == NULL) { 1086 ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); 1087 /* keep going */ 1088 } else { 1089 jmethodID startMeth = env->GetStaticMethodID(startClass, "main", 1090 "([Ljava/lang/String;)V"); 1091 if (startMeth == NULL) { 1092 ALOGE("JavaVM unable to find main() in '%s'\n", className); 1093 /* keep going */ 1094 } else { 1095 env->CallStaticVoidMethod(startClass, startMeth, strArray); 1096 1097 #if 0 1098 if (env->ExceptionCheck()) 1099 threadExitUncaughtException(env); 1100 #endif 1101 } 1102 } ... }

這個函式定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。
AndroidRuntime類的成員函式start最主要是做了以下三件事情:
1. 建立一個JniInvocation例項,並且呼叫它的成員函式init來初始化JNI環境;
2. 呼叫AndroidRuntime類的成員函式startVm來建立一個虛擬機器及其對應的JNI介面,即建立一個JavaVM介面和一個JNIEnv介面;
3. 有了上述的JavaVM介面和JNIEnv介面之後,就可以在Zygote程序中載入指定的class了。
其中,第1件事情和第2件事情又是最關鍵的。因此,接下來我們繼續分析它們所對應的函式的實現。
JniInvocation類的成員函式init的實現如下所示:


 56 static const char* kLibraryFallback = "libart.so";

 97 bool JniInvocation::Init(const char* library) {
 98 #ifdef HAVE_ANDROID_OS
 99   char buffer[PROPERTY_VALUE_MAX];
100 #else
101   char* buffer = NULL;
102 #endif
103   library = GetLibrary(library, buffer);
104 
105   handle_ = dlopen(library, RTLD_NOW);
106   if (handle_ == NULL) {
107     if (strcmp(library, kLibraryFallback) == 0) {
108       // Nothing else to try.
109       ALOGE("Failed to dlopen %s: %s", library, dlerror());
110       return false;
111     }
112     // Note that this is enough to get something like the zygote
113     // running, we can't property_set here to fix this for the future
114     // because we are root and not the system user. See
115     // RuntimeInit.commonInit for where we fix up the property to
116     // avoid future fallbacks. http://b/11463182
117     ALOGW("Falling back from %s to %s after dlopen error: %s",
118           library, kLibraryFallback, dlerror());
119     library = kLibraryFallback;
120     handle_ = dlopen(library, RTLD_NOW);
121     if (handle_ == NULL) {
122       ALOGE("Failed to dlopen %s: %s", library, dlerror());
123       return false;
124     }
125   }
126   if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
127                   "JNI_GetDefaultJavaVMInitArgs")) {
128     return false;
129   }
130   if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
131                   "JNI_CreateJavaVM")) {
132     return false;
133   }
134   if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
135                   "JNI_GetCreatedJavaVMs")) {
136     return false;
137   }
138   return true;
139 }

這個函式定義在檔案libnativehelper/JniInvocation.cpp中。
JniInvocation類的成員函式init所做的事情很簡單。它首先是讀取系統屬性persist.sys.dalvik.vm.lib的值。前面提到,系統屬性persist.sys.dalvik.vm.lib的值要麼等於libdvm.so,要麼等於libart.so。因此,接下來通過函式dlopen載入到程序來的要麼是libdvm.so,要麼是libart.so。無論載入的是哪一個so,都要求它匯出JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs這三個介面,並且分別儲存在JniInvocation類的三個成員變數JNI_GetDefaultJavaVMInitArgs_、JNI_CreateJavaVM_和JNI_GetCreatedJavaVMs_中。這三個介面也就是前面我們提到的用來抽象Java虛擬機器的三個介面。
三個介面的作用:
1. JNI_GetDefaultJavaVMInitArgs – 獲取虛擬機器的預設初始化引數
2. JNI_CreateJavaVM – 在程序中建立虛擬機器例項
3. JNI_GetCreatedJavaVMs – 獲取程序中建立的虛擬機器例項
從這裡就可以看出,JniInvocation類的成員函式init實際上就是根據系統屬性persist.sys.dalvik.vm.lib來初始化Dalvik虛擬機器或者ART虛擬機器環境。
在103 library = GetLibrary(library, buffer);中,該方法被用來指明哪一個庫被從給定的名字中載入進來。

接下來我們繼續看AndroidRuntime類的成員函式startVm的實現:

 553 /*
 554  * Start the Dalvik Virtual Machine.
 555  *
 556  * Various arguments, most determined by system properties, are passed in.
 557  * The "mOptions" vector is updated.
 558  *
 559  * CAUTION: when adding options in here, be careful not to put the
 560  * char buffer inside a nested scope.  Adding the buffer to the
 561  * options using mOptions.add() does not copy the buffer, so if the
 562  * buffer goes out of scope the option may be overwritten.  It's best
 563  * to put the buffer at the top of the function so that it is more
 564  * unlikely that someone will surround it in a scope at a later time
 565  * and thus introduce a bug.
 566  *
 567  * Returns 0 on success. 
 568  */
 569 int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
 570 {
     ...

 959     /*
 960      * Initialize the VM.
 961      *
 962      * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
 963      * If this call succeeds, the VM is ready, and we can start issuing
 964      * JNI calls.
 965      */
 966     if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
 967         ALOGE("JNI_CreateJavaVM failed\n");
 968         return -1;
 969     }
 970 
 971     return 0;
 972 }

這個函式定義在檔案frameworks/base/core/jni/AndroidRuntime.cpp中。
AndroidRuntime類的成員函式startVm最主要就是呼叫函式JNI_CreateJavaVM來建立一個JavaVM介面及其對應的JNIEnv介面:

174 extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
175   return JniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm, p_env, vm_args);
176 }

這個函式定義在檔案libnativehelper/JniInvocation.cpp中。
JniInvocation類的靜態成員函式GetJniInvocation返回的便是前面所建立的JniInvocation例項。有了這個JniInvocation例項之後,就繼續呼叫它的成員函式JNI_CreateJavaVM來建立一個JavaVM介面及其對應的JNIEnv介面:

145 jint JniInvocation::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
146   return JNI_CreateJavaVM_(p_vm, p_env, vm_args);
147 }

這個函式定義在檔案libnativehelper/JniInvocation.cpp中。
JniInvocation類的成員變數JNI_CreateJavaVM_指向的就是前面所載入的libdvm.so或者libart.so所匯出的函式JNI_CreateJavaVM,因此,JniInvocation類的成員函式JNI_CreateJavaVM返回的JavaVM介面指向的要麼是Dalvik虛擬機器,要麼是ART虛擬機器。
通過上面的分析,我們就很容易知道,Android系統通過將ART執行時抽象成一個Java虛擬機器,以及通過系統屬性persist.sys.dalvik.vm.lib和一個適配層JniInvocation,就可以無縫地將Dalvik虛擬機器替換為ART執行時。這個替換過程設計非常巧妙,因為涉及到的程式碼修改是非常少的。
以上就是ART虛擬機器的啟動過程,接下來我們再分析應用程式在安裝過程中將dex位元組碼翻譯為本地機器碼的過程。
Android應用程式的安裝過程可以參考Android應用程式安裝過程原始碼分析這篇文章。 簡單來說,就是Android系統通過PackageManagerService來安裝APK,在安裝的過程,PackageManagerService會通過另外一個類Installer的成員函式installer來對APK裡面的dex位元組碼進行優化:

 63     public int install(String uuid, String name, int uid, int gid, String seinfo) {
 64         StringBuilder builder = new StringBuilder("install");
 65         builder.append(' ');
 66         builder.append(escapeNull(uuid));
 67         builder.append(' ');
 68         builder.append(name);
 69         builder.append(' ');
 70         builder.append(uid);
 71         builder.append(' ');
 72         builder.append(gid);
 73         builder.append(' ');
 74         builder.append(seinfo != null ? seinfo : "!");
 75         return mInstaller.execute(builder.toString());
 76     }

這個函式定義在檔案frameworks/base/services/java/com/android/server/pm/Installer.java中。
Installer通過socket向守護程序installd傳送一個dexopt請求,這個請求是由installd裡面的函式dexopt來處理的:

1084 int dexopt(const char *apk_path, uid_t uid, bool is_public,
1085            const char *pkgname, const char *instruction_set, int dexopt_needed,
1086            bool vm_safe_mode, bool debuggable, const char* oat_dir, bool boot_complete)
1087 {
    ...
1223         if (dexopt_needed == DEXOPT_PATCHOAT_NEEDED
1224             || dexopt_needed == DEXOPT_SELF_PATCHOAT_NEEDED) {
1225             run_patchoat(input_fd, out_fd, input_file, out_path, pkgname, instruction_set);
1226         } else if (dexopt_needed == DEXOPT_DEX2OAT_NEEDED) {
1227             const char *input_file_name = strrchr(input_file, '/');
1228             if (input_file_name == NULL) {
1229                 input_file_name = input_file;
1230             } else {
1231                 input_file_name++;
1232             }
1233             run_dex2oat(input_fd, out_fd, input_file_name, out_path, swap_fd, pkgname,
1234                         instruction_set, vm_safe_mode, debuggable, boot_complete);
1235         } else {
1236             ALOGE("Invalid dexopt needed: %d\n", dexopt_needed);
1237             exit(73);
1238         }
1239         exit(68);   /* only get here on exec failure */
    ...
    }

選擇呼叫run_patchoat或者run_dex2oat其中的一個函式來繼續下面的。
run_patchoat呼叫的是”/system/bin/patchoat”;生成odex檔案
而run_dex2oat呼叫的是”/system/bin/dex2oat”,生成oat檔案。

通過上面的分析,我們就很容易知道,只需要將dex檔案的優化過程替換成dex檔案翻譯成本地機器碼的過程,就可以輕鬆地在應用安裝過程,無縫地將Dalvik虛擬機器替換成ART執行時。
最後,還有一個地方需要注意的是,應用程式的安裝發生在兩個時機,第一個時機是系統啟動的時候,第二個時機系統啟動完成後使用者自行安裝的時候。在第一個時機中,系統除了會對/system/app和/data/app目錄下的所有APK進行dex位元組碼到本地機器碼的翻譯之外,還會對/system/framework目錄下的APK或者JAR檔案,以及這些APK所引用的外部JAR,進行dex位元組碼到本地機器碼的翻譯。這樣就可以保證除了應用之外,系統中使用Java來開發的系統服務,也會統一地從dex位元組碼翻譯成本地機器碼。也就是說,將Android系統中的Dalvik虛擬機器替換成ART執行時之後,系統中的程式碼都是由ART執行時來執行的了,這時候就不會對Dalvik虛擬機器產生任何的依賴。