1. 程式人生 > >(十二)Android 系統啟動原理(art 虛擬機器)

(十二)Android 系統啟動原理(art 虛擬機器)

一、虛擬機器的啟動

Android 是一個 Linux 的虛擬機器,當虛擬機器啟動的時候,會執行手機根目錄下的 init.rc(實際上就是 .sh 檔案) 這個可執行檔案。

在 init.rc 中,有一行 on init 執行命令。這是呼叫 init.rc 同級檔案 init ,init 是所有安卓手機的入口執行檔案,無法開啟檢視,是亂碼。
這裡寫圖片描述

xpose 的強大功能,就是對 init 進行 hook,然後修改。但是替換 init 這個檔案是需要 root 許可權的,所以使用 xpose 這個框架,是需要進行 root 的。

1.init 原始碼

inti 檔案的原始碼是在 \system\core\init

這個資料夾下,會把裡面所有的東西編譯成 init 這個可執行檔案,各個手機廠商會對這塊檔案進行修改。
這裡寫圖片描述

init 的唯一入口是改資料夾下的 init.cpp 這個檔案,裡面有一個 main 函式,處理環境變數,開啟服務,渲染等。
這裡寫圖片描述

main 部分程式碼:

    // If we're in the kernel domain, re-exec init to transition to the init domain now
    // that the SELinux policy has been loaded.
    if (is_first_stage) {
        if
(restorecon("/init") == -1) { ERROR("restorecon failed: %s\n", strerror(errno)); security_failure(); } char* path = argv[0]; char* args[] = { path, const_cast<char*>("--second-stage"), nullptr }; if (execv(path, args) == -1) { ERROR("execv(\"%s\") failed: %s\n"
, path, strerror(errno)); security_failure(); } }

程式碼中的 path 是指系統定義好的一些環境變數,這些路徑是 \frameworks\base\cmds 下的所有東西。
所以在這裡是判斷是否是第一次啟動,如果是第一次啟動,則會執行 \frameworks\base\cmds 下所有的可執行檔案,包括開啟虛擬機器的檔案 app_process。

2.app_process 原始碼

\frameworks\base\cmds\app_process 下有個 app_main.cpp 檔案,裡面就是 app_process 原始碼。

app_process 部分程式碼:

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }

在 app_main 裡面的 main 方法最後,呼叫了 runtime.start(“com.android.internal.os.ZygoteInit”, args, zygote);
點選檢視 run 是第一 AppRuntime。

所以 app_process 呼叫了 com.android.internal.os.ZygoteInit 這個類,這是第一個被呼叫的 java 類。對應原始碼位置是 \frameworks\base\core\java\com\android\internal\os

3.AndroidRuntime

AppRuntime 繼承於 AndroidRuntime,AndroidRuntime 位於\frameworks\base\core\jni

start 部分程式碼:

    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }

在 AndroidRuntime 的 start 方法中,呼叫了 startVm,這個方法,這個方法才是真正的去開啟虛擬機器。手機啟動的時候只是開啟 Linux 系統,當執行到這裡的時候,Linux 系統開啟安卓執行的虛擬機器。

startVm 部分程式碼:

    /*
     * Initialize the VM.
     *
     * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
     * If this call succeeds, the VM is ready, and we can start issuing
     * JNI calls.
     */
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }

在 startVm 末尾呼叫 JNI_CreateJavaVM,去建立一個虛擬機器。

4.JNI_CreateJavaVM

JNI_CreateJavaVM 方法位於 \art\runtime\jni_internal.cc 檔案中。

JNI_CreateJavaVM :

// JNI Invocation interface.

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
  if (IsBadJniVersion(args->version)) {
    LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;
    return JNI_EVERSION;
  }
  Runtime::Options options;
  for (int i = 0; i < args->nOptions; ++i) {
    JavaVMOption* option = &args->options[i];
    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
  }
  bool ignore_unrecognized = args->ignoreUnrecognized;
  if (!Runtime::Create(options, ignore_unrecognized)) {
    return JNI_ERR;
  }
  Runtime* runtime = Runtime::Current();
  bool started = runtime->Start();
  if (!started) {
    delete Thread::Current()->GetJniEnv();
    delete runtime->GetJavaVM();
    LOG(WARNING) << "CreateJavaVM failed";
    return JNI_ERR;
  }
  *p_env = Thread::Current()->GetJniEnv();
  *p_vm = runtime->GetJavaVM();
  return JNI_OK;
}

其中最主要的最後兩行程式碼,例項化了 p_env 和 p_vm ,p_env 就是我們編寫 jni 方法的第一個引數 JNIEnv *env ,p_vm 就是虛擬機器。

//JNIEnv *env 例項化
*p_env = Thread::Current()->GetJniEnv();
//例項化虛擬機器的地方
*p_vm = runtime->GetJavaVM();

注:虛擬機器在 Linux 就是一個結構體的方式儲存著。

5.p_env

GetJniEnv() 這個函式定義在檔案 \art\runtime 下的 thread.h 中。
* thread.h *

  // Every thread may have an associated JNI environment
  JNIEnvExt* jni_env_;

  // JNI methods
  JNIEnvExt* GetJniEnv() const {
    return jni_env_;
  }

JNI 方法的第一個引數是 JNIEnv,JNIEnv 是一個介面, JNIEnvExt 是 JNIEnv子類。

二、載入 java 檔案

\frameworks\base\core\jni\AndroidRuntime 中繼續往下,會發現載入 java 類,實際上是呼叫 env->FindClass(slashClassName) 進行載入的。(java 中 雙親委託機制 ClassLoader 進行載入 java 檔案,最底層的實現也是使用 FindClass 這個方法)

1.FindClass

FindClass 是在 libnativehelper\include\nativehelper\jni.h 中,
jni.h 下 FindClass :

    jclass FindClass(const char* name)
    { return functions->FindClass(this, name); }

這裡的 functions 是 JNINativeInterface,最終呼叫的是 \art\runtime\jni_internal.cc 下的 FindClass 。
\jni_internal.cc 下 FindClass:

  static jclass FindClass(JNIEnv* env, const char* name) {
    CHECK_NON_NULL_ARGUMENT(name);
    Runtime* runtime = Runtime::Current();
    ClassLinker* class_linker = runtime->GetClassLinker();
    std::string descriptor(NormalizeJniClassDescriptor(name));
    ScopedObjectAccess soa(env);
    mirror::Class* c = nullptr;
    //判斷虛擬機器是否開啟
    if (runtime->IsStarted()) {
      StackHandleScope<1> hs(soa.Self());
      Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));
      c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
    } else {
      //還沒開啟虛擬機器,即載入的是系統的類
      c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
    }
    return soa.AddLocalReference<jclass>(c);
  }

最終程式呼叫到 class_linker 的 FindClass 方法進行載入類。

2. class_linker 的 FindClass

class_linker 所在目錄 \art\runtime 下有一個 class_linker.cc 檔案,找到裡面的 FindClass 方法。

FindClass 部分程式碼:

    if (pair.second != nullptr) {
      return DefineClass(self,
                         descriptor,
                         hash,
                         ScopedNullHandle<mirror::ClassLoader>(),
                         *pair.first,
                         *pair.second);
    } 

在這邊呼叫了 DefineClass。

DefineClass 部分程式碼:

  // Add the newly loaded class to the loaded classes table.
  mirror::Class* existing = InsertClass(descriptor, klass.Get(), hash);
  if (existing != nullptr) {
    // We failed to insert because we raced with another thread. Calling EnsureResolved may cause
    // this thread to block.
    return EnsureResolved(self, descriptor, existing);
  }

  // Load the fields and other things after we are inserted in the table. This is so that we don't
  // end up allocating unfree-able linear alloc resources and then lose the race condition. The
  // other reason is that the field roots are only visited from the class table. So we need to be
  // inserted before we allocate / fill in these fields.
  LoadClass(self, dex_file, dex_class_def, klass);

這是呼叫了兩個比較重要的方法, InsertClass 和 LoadClass。

InsertClass(descriptor, klass.Get(), hash); 有一個引數是 hash,這樣會把類進行快取,在 DefineClass 執行 InsertClass 之前,會先進行這個判斷,如果已經載入的就不再進行載入。

LoadClass(self, dex_file, dex_class_def, klass); 是真正的去進行載入 Class。

LoadClass:

void ClassLinker::LoadClass(Thread* self,
                            const DexFile& dex_file,
                            const DexFile::ClassDef& dex_class_def,
                            Handle<mirror::Class> klass) {
  const uint8_t* class_data = dex_file.GetClassData(dex_class_def);
  if (class_data == nullptr) {
    return;  // no fields or methods - for example a marker interface
  }
  bool has_oat_class = false;
  if (Runtime::Current()->IsStarted() && !Runtime::Current()->IsAotCompiler()) {
    OatFile::OatClass oat_class = FindOatClass(dex_file, klass->GetDexClassDefIndex(),
                                               &has_oat_class);
    if (has_oat_class) {
      LoadClassMembers(self, dex_file, class_data, klass, &oat_class);
    }
  }
  if (!has_oat_class) {
    LoadClassMembers(self, dex_file, class_data, klass, nullptr);
  }
}

最開始是通過 DexFile 去獲取到 ClassData。因為在類還沒載入的時候,class 是以 dex格式 存在在 磁碟 檔案下,這時候需要先把 dex 轉為 class,再把 class 載入到記憶體中。

然後通過 LoadClassMembers 進行載入類的資訊,分配記憶體。LoadClassMembers 中分別對 ArtField 和 ArtMethod 進行初始化。