Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android運行時ART執行類方法的過程分析

Android運行時ART執行類方法的過程分析

編輯:關於Android編程

在前面一篇文章中,我們分析了ART運行時加載類以及查找其方法的過程。一旦找到了目標類方法,我們就可以獲得它的DEX字節碼或者本地機器指令,這樣就可以對它進行執行了。在ART運行時中,類方法的執行方式有兩種。一種是像Dalvik虛擬機一樣,將其DEX字節碼交給解釋器執行;另一種則是直接將其本地機器指令交給CPU執行。在本文中,我們就將通過分析ART運行時執行類方法的過程來理解ART運行時的運行原理。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

我們先來看看圖1,它描述了ART運行時執行一個類方法的流程,如下所示:

\

圖1 ART運行時執行類方法的過程

圖1綜合了我們在前面Android運行時ART加載OAT文件的過程分析和Android運行時ART加載類和方法的過程分析這兩篇文章中提到的兩個知識點,我們先來回顧一下。

第一個知識點是ART運行時將DEX字節碼翻譯成本地機器指令時,使用的後端(Backend)是Quick類型還是Portable類型。ART運行時在編譯的時候,默認使用的後端是Quick類型的,不過可以通過將環境變量ART_USE_PORTABLE_COMPILER的值設置為true來指定使用Portable類型的後端,如下所示:

......
LIBART_CFLAGS :=
ifeq ($(ART_USE_PORTABLE_COMPILER),true)
  LIBART_CFLAGS += -DART_USE_PORTABLE_COMPILER=1
endif
......
上述編譯腳本定義在文件art/runtime/Android.mk中。

一旦我們將環境變量ART_USE_PORTABLE_COMPILER的值設置為true,那麼就會定義一個宏ART_USE_PORTABLE_COMPILER。參考前面Android運行時ART加載OAT文件的過程分析這篇文章,宏ART_USE_PORTABLE_COMPILER的定義與否會影響到加載OAT文件所使用的方法,如下所示:

OatFile* OatFile::Open(const std::string& filename,
                       const std::string& location,
                       byte* requested_base,
                       bool executable) {
  CHECK(!filename.empty()) << location;
  CheckLocation(filename);
#ifdef ART_USE_PORTABLE_COMPILER
  // If we are using PORTABLE, use dlopen to deal with relocations.
  //
  // We use our own ELF loader for Quick to deal with legacy apps that
  // open a generated dex file by name, remove the file, then open
  // another generated dex file with the same name. http://b/10614658
  if (executable) {
    return OpenDlopen(filename, location, requested_base);
  }
#endif
  // If we aren't trying to execute, we just use our own ElfFile loader for a couple reasons:
  //
  // On target, dlopen may fail when compiling due to selinux restrictions on installd.
  //
  // On host, dlopen is expected to fail when cross compiling, so fall back to OpenElfFile.
  // This won't work for portable runtime execution because it doesn't process relocations.
  UniquePtr file(OS::OpenFileForReading(filename.c_str()));
  if (file.get() == NULL) {
    return NULL;
  }
  return OpenElfFile(file.get(), location, requested_base, false, executable);
}
這個函數定義在文件art/runtime/oat_file.cc中。

這個函數的詳細解釋可以參考前面Android運行時ART加載OAT文件的過程分析一文,這裡只對結論進行進一步的解釋。從注釋可以知道,通過Portable後端和Quick後端生成的OAT文件的本質區別在於,前者使用標准的動態鏈接器加載,而後者使用自定義的加載器加載。

標准動態鏈接器在加載SO文件(這裡是OAT文件)的時候,會自動處理重定位問題。也就是說,在生成的本地機器指令中,如果有依賴其它的SO導出的函數,那麼標准動態鏈接器就會將被依賴的SO也加載進來,並且從裡面找到被引用的函數的地址,用來重定位引用了該函數的符號。生成的本地機器指令引用的一般都是些SO呢?其實就是ART運行時庫(libart.so)。例如,如果在生成的本地機器指令需要分配一個對象,那麼就需要調用ART運行時的堆管理器提供的AllocObject接口來分配。

自定義加載器的做法就不一樣了。它在加載OAT文件時,並不需要做上述的重定位操作。因為Quick後端生成的本地機器指令需要調用一些外部庫提供的函數時,是通過一個函數跳轉表來實現的。由於在加載過程中不需要執行重定位,因此加載過程就會更快,Quick的名字就是這樣得來的。Portable後端生成的本地機器指令在調用外部庫提供的函數時,使用了標准的方法,因此它不但可以在ART運行時加載,也可以在其它運行時加載,因此就得名於Portable。

接下來我們的重點是分析Quick後端生成的本地機器指令在調用外部庫函數時所使用的函數跳轉表是如何定義的。

在前面分析Dalvik虛擬機的文章Dalvik虛擬機進程和線程的創建過程分析中,我們提到每一個Dalvik虛擬機線程在內部都通過一個Thread對象描述。這個Thread對象包含了一些與虛擬機相關的信息。例如,JNI函數調用函數表。在ART運行時中創建的線程,和Davik虛擬機線程一樣,在內部也會通過一個Thread對象來描述。這個新的Thread對象內部除了定義JNI調用函數表之外,還定義了我們在上面提到的外部函數調用跳轉表。

在前面Android運行時ART加載OAT文件的過程分析一文,我們提到了ART運行時的啟動和初始化過程。其中的一個初始化過程便是將主線程關聯到ART運行時去,如下所示:

bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) {  
  ......  

  java_vm_ = new JavaVMExt(this, options.get());  
  ......  
      
  Thread* self = Thread::Attach("main", false, NULL, false);  
  ......  
      
  return true;  
}  
這個函數定義在文件art/runtime/runtime.cc中。

在Runtime類的成員函數Init中,通過調用Thread類的靜態成員函數Attach將當前線程,也就是主線程,關聯到ART運行時去。在關聯的過程中,就會初始化一個外部庫函數調用跳轉表。

Thread類的靜態成員函數Attach的實現如下所示:

Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group,
                       bool create_peer) {
  Thread* self;
  Runtime* runtime = Runtime::Current();
  ......

  {
    MutexLock mu(NULL, *Locks::runtime_shutdown_lock_);
    if (runtime->IsShuttingDown()) {
      LOG(ERROR) << "Thread attaching while runtime is shutting down: " << thread_name;
      return NULL;
    } else {
      Runtime::Current()->StartThreadBirth();
      self = new Thread(as_daemon);
      self->Init(runtime->GetThreadList(), runtime->GetJavaVM());
      Runtime::Current()->EndThreadBirth();
    }
  }

  ......

  return self;
}
這個函數定義在文件art/runtime/thread.cc中。

在Thread類的靜態成員函數Attach中,最重要的就是創建了一個Thread對象來描述當前被關聯到ART運行時的線程。創建了這個Thread對象之後,馬上就調用它的成員函數Init來對它進行初始化。

Thread類的成員函數Init的實現如下所示:

void Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm) {
  ......

  InitTlsEntryPoints();
  ......

  jni_env_ = new JNIEnvExt(this, java_vm);
  ......
}
這個函數定義在文件art/runtime/thread.cc中。

Thread類的成員函數Init除了給當前的線程創建一個JNIEnvExt對象來描述它的JNI調用接口之外,還通過調用另外一個成員函數InitTlsEntryPoints來初始化一個外部庫函數調用跳轉表。

Thread類的成員函數InitTlsEntryPoints的實現如下所示:

void InitEntryPoints(InterpreterEntryPoints* ipoints, JniEntryPoints* jpoints,
                     PortableEntryPoints* ppoints, QuickEntryPoints* qpoints);

void Thread::InitTlsEntryPoints() {
  ......
  InitEntryPoints(&interpreter_entrypoints_, &jni_entrypoints_, &portable_entrypoints_,
                  &quick_entrypoints_);
}
這個函數定義在文件art/runtime/thread.cc中。

Thread類定義了四個成員變量interpreter_entrypoints_、jni_entrypoints_、portable_entrypoints_和quick_entrypoints_,如下所示:

class PACKED(4) Thread {
  ......

 public:
  // Entrypoint function pointers
  // TODO: move this near the top, since changing its offset requires all oats to be recompiled!
  InterpreterEntryPoints interpreter_entrypoints_;
  JniEntryPoints jni_entrypoints_;
  PortableEntryPoints portable_entrypoints_;
  QuickEntryPoints quick_entrypoints_;

 ......
};
Thread類的聲明定義在文件art/runtime/thread.h中。

Thread類將外部庫函數調用跳轉表劃分為4個,其中,interpreter_entrypoints_描述的是解釋器要用到的跳轉表,jni_entrypoints_描述的是JNI調用相關的跳轉表,portable_entrypoints_描述的是Portable後端生成的本地機器指令要用到的跳轉表,而quick_entrypoints_描述的是Quick後端生成的本地機器指令要用到的跳轉表。從這裡可以看出,Portable後端生成的本地機器指令也會使用到ART運行時內部的函數跳轉表。不過與Quick後端生成的本地機器指令使用到的ART運行時內部的函數跳轉表相比,它裡面包含的函數項會少很多很多。接下來我們將會看到這一點。

回到Thread類的成員函數InitTlsEntryPoints中,它通過調用一個全局函數InitEntryPoints來初始化上述的四個跳轉表。全局函數InitEntryPoints的實現是和CPU體系結構相關的,因為跳轉表裡面的函數調用入口是用匯編語言來實現的。

我們以ARM體系架構為例,來看全局函數InitEntryPoints的實現,如下所示:

void InitEntryPoints(InterpreterEntryPoints* ipoints, JniEntryPoints* jpoints,
                     PortableEntryPoints* ppoints, QuickEntryPoints* qpoints) {
  // Interpreter
  ipoints->pInterpreterToInterpreterBridge = artInterpreterToInterpreterBridge;
  ipoints->pInterpreterToCompiledCodeBridge = artInterpreterToCompiledCodeBridge;

  // JNI
  jpoints->pDlsymLookup = art_jni_dlsym_lookup_stub;

  // Portable
  ppoints->pPortableResolutionTrampoline = art_portable_resolution_trampoline;
  ppoints->pPortableToInterpreterBridge = art_portable_to_interpreter_bridge;

  // Alloc
  qpoints->pAllocArray = art_quick_alloc_array;
  qpoints->pAllocArrayWithAccessCheck = art_quick_alloc_array_with_access_check;
  qpoints->pAllocObject = art_quick_alloc_object;
  qpoints->pAllocObjectWithAccessCheck = art_quick_alloc_object_with_access_check;
  qpoints->pCheckAndAllocArray = art_quick_check_and_alloc_array;
  qpoints->pCheckAndAllocArrayWithAccessCheck = art_quick_check_and_alloc_array_with_access_check;

  // Cast
  qpoints->pInstanceofNonTrivial = artIsAssignableFromCode;
  qpoints->pCanPutArrayElement = art_quick_can_put_array_element;
  qpoints->pCheckCast = art_quick_check_cast;

  // DexCache
  qpoints->pInitializeStaticStorage = art_quick_initialize_static_storage;
  qpoints->pInitializeTypeAndVerifyAccess = art_quick_initialize_type_and_verify_access;
  qpoints->pInitializeType = art_quick_initialize_type;
  qpoints->pResolveString = art_quick_resolve_string;

  // Field
  qpoints->pSet32Instance = art_quick_set32_instance;
  qpoints->pSet32Static = art_quick_set32_static;
  qpoints->pSet64Instance = art_quick_set64_instance;
  qpoints->pSet64Static = art_quick_set64_static;
  qpoints->pSetObjInstance = art_quick_set_obj_instance;
  qpoints->pSetObjStatic = art_quick_set_obj_static;
  qpoints->pGet32Instance = art_quick_get32_instance;
  qpoints->pGet64Instance = art_quick_get64_instance;
  qpoints->pGetObjInstance = art_quick_get_obj_instance;
  qpoints->pGet32Static = art_quick_get32_static;
  qpoints->pGet64Static = art_quick_get64_static;
  qpoints->pGetObjStatic = art_quick_get_obj_static;

  // FillArray
  qpoints->pHandleFillArrayData = art_quick_handle_fill_data;

  // JNI
  qpoints->pJniMethodStart = JniMethodStart;
  qpoints->pJniMethodStartSynchronized = JniMethodStartSynchronized;
  qpoints->pJniMethodEnd = JniMethodEnd;
  qpoints->pJniMethodEndSynchronized = JniMethodEndSynchronized;
  qpoints->pJniMethodEndWithReference = JniMethodEndWithReference;
  qpoints->pJniMethodEndWithReferenceSynchronized = JniMethodEndWithReferenceSynchronized;

  // Locks
  qpoints->pLockObject = art_quick_lock_object;
  qpoints->pUnlockObject = art_quick_unlock_object;

  // Math
  qpoints->pCmpgDouble = CmpgDouble;
  qpoints->pCmpgFloat = CmpgFloat;
  qpoints->pCmplDouble = CmplDouble;
  qpoints->pCmplFloat = CmplFloat;
  qpoints->pFmod = fmod;
  qpoints->pSqrt = sqrt;
  qpoints->pL2d = __aeabi_l2d;
  qpoints->pFmodf = fmodf;
  qpoints->pL2f = __aeabi_l2f;
  qpoints->pD2iz = __aeabi_d2iz;
  qpoints->pF2iz = __aeabi_f2iz;
  qpoints->pIdivmod = __aeabi_idivmod;
  qpoints->pD2l = art_d2l;
  qpoints->pF2l = art_f2l;
  qpoints->pLdiv = __aeabi_ldivmod;
  qpoints->pLdivmod = __aeabi_ldivmod;  // result returned in r2:r3
  qpoints->pLmul = art_quick_mul_long;
  qpoints->pShlLong = art_quick_shl_long;
  qpoints->pShrLong = art_quick_shr_long;
  qpoints->pUshrLong = art_quick_ushr_long;

  // Intrinsics
  qpoints->pIndexOf = art_quick_indexof;
  qpoints->pMemcmp16 = __memcmp16;
  qpoints->pStringCompareTo = art_quick_string_compareto;
  qpoints->pMemcpy = memcpy;

  // Invocation
  qpoints->pQuickResolutionTrampoline = art_quick_resolution_trampoline;
  qpoints->pQuickToInterpreterBridge = art_quick_to_interpreter_bridge;
  qpoints->pInvokeDirectTrampolineWithAccessCheck = art_quick_invoke_direct_trampoline_with_access_check;
  qpoints->pInvokeInterfaceTrampoline = art_quick_invoke_interface_trampoline;
  qpoints->pInvokeInterfaceTrampolineWithAccessCheck = art_quick_invoke_interface_trampoline_with_access_check;
  qpoints->pInvokeStaticTrampolineWithAccessCheck = art_quick_invoke_static_trampoline_with_access_check;
  qpoints->pInvokeSuperTrampolineWithAccessCheck = art_quick_invoke_super_trampoline_with_access_check;
  qpoints->pInvokeVirtualTrampolineWithAccessCheck = art_quick_invoke_virtual_trampoline_with_access_check;

  // Thread
  qpoints->pCheckSuspend = CheckSuspendFromCode;
  qpoints->pTestSuspend = art_quick_test_suspend;

  // Throws
  qpoints->pDeliverException = art_quick_deliver_exception;
  qpoints->pThrowArrayBounds = art_quick_throw_array_bounds;
  qpoints->pThrowDivZero = art_quick_throw_div_zero;
  qpoints->pThrowNoSuchMethod = art_quick_throw_no_such_method;
  qpoints->pThrowNullPointer = art_quick_throw_null_pointer_exception;
  qpoints->pThrowStackOverflow = art_quick_throw_stack_overflow;
};
這個函數定義在文件art/runtime/arch/arm/entrypoints_init_arm.cc中。

從函數InitEntryPoints的實現就可以看到Quick後端和Portable後端生成的本地機器指令要使用到的外部庫函數調用跳轉表的初始化過程了。例如,如果在生成的本地機器指令中,需要調用一個JNI函數,那麼就需要通過art_jni_dlsym_lookup_stub函數來間接地調用,以便可以找到正確的JNI函數來調用。

此外,我們還可以看到,解釋器要用到的跳轉表只包含了兩項,分別是artInterpreterToInterpreterBridge和artInterpreterToCompiledCodeBridge。前者用來從一個解釋執行的類方法跳到另外一個也是解釋執行的類方法去執行,後者用來從一個解釋執行的類方法跳到另外一個以本地機器指令執行的類方法去執行。

Portable後端生成的本地機器指令要用到的跳轉表也只包含了兩項,分別是art_portable_resolution_trampoline和art_portable_to_interpreter_bridge。前者用作一個還未鏈接好的類方法的調用入口點,後者用來從一個以本地機器指令執行的類方法跳到另外一個解釋執行的類方法去執行。

剩下的其它代碼均是用來初始化Quick後端生成的本地機器指令要用到的跳轉表,它包含的項非常多,但是可以劃分為Alloc(對象分配)、Cast(類型轉換)、DexCache(Dex緩訪問)、Field(成員變量訪問)、FillArray(數組填充)、JNI(JNI函數調用)、Locks(鎖)、Math(數學計算)、Intrinsics(內建函數調用)、Invocation(類方法調用)、Thread(線程操作)和Throws(異常處理)等12類。

有了這些跳轉表之後,當我們需要在生成的本地機器指令中調用一個外部庫提供的函數時,只要找到用來描述當前線程的Thread對象,然後再根據上述的四個跳轉表在該Thread對象內的偏移位置,那麼就很容易找到所需要的跳轉項了。

以上就是我們需要了解的第一個知識點,也就是Portable後端和Quick後端生成的本地機器指令的區別。接下來我們現來看另外一個知識點,它們是涉及到類方法的執行方式,也就是是通過解釋器來執行,還是直接以本地機器指令來執行,以及它們之間是如何穿插執行的。

在前面Android運行時ART加載類和方法的過程分析這篇文章中,我們提到,在類的加載過程中,需要對類的各個方法進行鏈接,實際上就是確定它們是通過解釋器來執行,還是以本地機器指令來直接執行,如下所示:

static void LinkCode(SirtRef& method, const OatFile::OatClass* oat_class,
                     uint32_t method_index)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  // Method shouldn't have already been linked.
  DCHECK(method->GetEntryPointFromCompiledCode() == NULL);
  // Every kind of method should at least get an invoke stub from the oat_method.
  // non-abstract methods also get their code pointers.
  const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index);
  oat_method.LinkMethod(method.get());

  // Install entry point from interpreter.
  Runtime* runtime = Runtime::Current();
  bool enter_interpreter = NeedsInterpreter(method.get(), method->GetEntryPointFromCompiledCode());
  if (enter_interpreter) {
    method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
  } else {
    method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
  }

  if (method->IsAbstract()) {
    method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
    return;
  }

  if (method->IsStatic() && !method->IsConstructor()) {
    // For static methods excluding the class initializer, install the trampoline.
    // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
    // after initializing class (see ClassLinker::InitializeClass method).
    method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
  } else if (enter_interpreter) {
    // Set entry point from compiled code if there's no code or in interpreter only mode.
    method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
  }

  if (method->IsNative()) {
    // Unregistering restores the dlsym lookup stub.
    method->UnregisterNative(Thread::Current());
  }

  // Allow instrumentation its chance to hijack code.
  runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),
                                                   method->GetEntryPointFromCompiledCode());
}
這個函數定義在文件art/runtime/class_linker.cc中。

函數LinkCode的詳細解釋可以參考前面Android運行時ART加載類和方法的過程分析一文,這裡我們只對結論進行總結,以及對結論進行進一步的分析:

1. ART運行時有兩種執行方法:解釋執行模式和本地機器指令執行模式。默認是本地機器指令執行模式,但是在啟動ART運行時時可以通過-Xint選項指定為解釋執行模式。

2. 即使是在本地機器指令模式中,也有類方法可能需要以解釋模式執行。反之亦然。解釋執行的類方法通過函數artInterpreterToCompiledCodeBridge的返回值調用本地機器指令執行的類方法;本地機器指令執行的類方法通過函數GetCompiledCodeToInterpreterBridge的返回值調用解釋執行的類方法;解釋執行的類方法通過函數artInterpreterToInterpreterBridge的返回值解釋執行的類方法。

3. 在解釋執行模式下,除了JNI方法和動態Proxy方法,其余所有的方法均通過解釋器執行,它們的入口點設置為函數GetCompiledCodeToInterpreterBridge的返回值。

4. 無論是哪一種執行模式,抽象方法都是解釋執行,這是由於需要通過解釋器來確定要執行的具體子類方法。它們的入口點同樣是設置為函數GetCompiledCodeToInterpreterBridge的返回值。

5. 靜態類方法的執行模式延遲至類初始化確定。在類初始化之前,它們的入口點由函數GetResolutionTrampoline的返回值代理。

接下來,我們就著重分析artInterpreterToCompiledCodeBridge、GetCompiledCodeToInterpreterBridge、artInterpreterToInterpreterBridge和GetResolutionTrampoline這四個函數以及它們所返回的函數的實現,以便可以更好地理解上述5個結論。

函數artInterpreterToCompiledCodeBridge用來在解釋器中調用以本地機器指令執行的函數,它的實現如下所示:

extern "C" void artInterpreterToCompiledCodeBridge(Thread* self, MethodHelper& mh,
                                                   const DexFile::CodeItem* code_item,
                                                   ShadowFrame* shadow_frame, JValue* result) {
  mirror::ArtMethod* method = shadow_frame->GetMethod();
  // Ensure static methods are initialized.
  if (method->IsStatic()) {
    Runtime::Current()->GetClassLinker()->EnsureInitialized(method->GetDeclaringClass(), true, true);
  }
  uint16_t arg_offset = (code_item == NULL) ? 0 : code_item->registers_size_ - code_item->ins_size_;
#if defined(ART_USE_PORTABLE_COMPILER)
  ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
  arg_array.BuildArgArrayFromFrame(shadow_frame, arg_offset);
  method->Invoke(self, arg_array.GetArray(), arg_array.GetNumBytes(), result, mh.GetShorty()[0]);
#else
  method->Invoke(self, shadow_frame->GetVRegArgs(arg_offset),
                 (shadow_frame->NumberOfVRegs() - arg_offset) * 4,
                 result, mh.GetShorty()[0]);
#endif
}
這個函數定義在文件art/runtime/entrypoints/interpreter/interpreter_entrypoints.cc中。

被調用的類方法通過一個ArtMethod對象來描述,並且可以在調用棧幀shadow_frame中獲得。獲得了用來描述被調用方法的ArtMehtod對象之後,就可以調用它的成員函數Invoke來對它進行執行。後面我們就會看到,ArtMethod類的成員函數Invoke會找到類方法的本地機器指令來執行。

在調用類方法的本地機器指令的時候,從解釋器調用棧獲取的傳入參數根據ART運行時使用的是Quick後端還是Portable後端來生成本地機器指令有所不同。不過最終都會ArtMethod類的成員函數Invoke來執行被調用類方法的本地機器指令。

函數GetCompiledCodeToInterpreterBridge用來返回一個函數指針,這個函數指針指向的函數用來從以本地機器指令執行的類方法中調用以解釋執行的類方法,它的實現如下所示:

static inline const void* GetCompiledCodeToInterpreterBridge() {
#if defined(ART_USE_PORTABLE_COMPILER)
  return GetPortableToInterpreterBridge();
#else
  return GetQuickToInterpreterBridge();
#endif
}
這個函數定義在文件art/runtime/entrypoints/entrypoint_utils.h中。

根據ART運行時使用的是Quick後端還是Portable後端,函數GetCompiledCodeToInterpreterBridge的返回值有所不同,不過它們的作用是一樣的。我們假設ART運行時使用的是Quick後端,那麼函數GetCompiledCodeToInterpreterBridge的返回值通過調用函數GetQuickToInterpreterBridge來獲得。

函數GetQuickToInterpreterBridge的實現如下所示:

extern "C" void art_quick_to_interpreter_bridge(mirror::ArtMethod*);
static inline const void* GetQuickToInterpreterBridge() {
  return reinterpret_cast(art_quick_to_interpreter_bridge);
}
這個函數定義在文件art/runtime/entrypoints/entrypoint_utils.h中。

函數GetQuickToInterpreterBridge的返回值實際上指向的是函數art_quick_to_interpreter_bridge。函數art_quick_to_interpreter_bridge是使用匯編代碼來實現的,用來從本地機器指令進入到解釋器的。

以ARM體系結構為例,函數art_quick_to_interpreter_bridge的實現如下所示:

    .extern artQuickToInterpreterBridge
ENTRY art_quick_to_interpreter_bridge
    SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME
    mov     r1, r9                 @ pass Thread::Current
    mov     r2, sp                 @ pass SP
    blx     artQuickToInterpreterBridge    @ (Method* method, Thread*, SP)
    ldr     r2, [r9, #THREAD_EXCEPTION_OFFSET]  @ load Thread::Current()->exception_
    ldr     lr,  [sp, #44]         @ restore lr
    add     sp,  #48               @ pop frame
    .cfi_adjust_cfa_offset -48
    cbnz    r2, 1f                 @ success if no exception is pending
    bx    lr                       @ return on success
1:
    DELIVER_PENDING_EXCEPTION
END art_quick_to_interpreter_bridge
這個函數定義在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。

很明顯,函數art_quick_to_interpreter_bridge通過調用另外一個函數artQuickToInterpreterBridge從本地機器指令進入到解釋器中去。

函數artQuickToInterpreterBridge的實現如下所示:

extern "C" uint64_t artQuickToInterpreterBridge(mirror::ArtMethod* method, Thread* self,
                                                mirror::ArtMethod** sp)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  // Ensure we don't get thread suspension until the object arguments are safely in the shadow
  // frame.
  FinishCalleeSaveFrameSetup(self, sp, Runtime::kRefsAndArgs);

  if (method->IsAbstract()) {
    ThrowAbstractMethodError(method);
    return 0;
  } else {
    const char* old_cause = self->StartAssertNoThreadSuspension("Building interpreter shadow frame");
    MethodHelper mh(method);
    const DexFile::CodeItem* code_item = mh.GetCodeItem();
    uint16_t num_regs = code_item->registers_size_;
    void* memory = alloca(ShadowFrame::ComputeSize(num_regs));
    ShadowFrame* shadow_frame(ShadowFrame::Create(num_regs, NULL,  // No last shadow coming from quick.
                                                  method, 0, memory));
    size_t first_arg_reg = code_item->registers_size_ - code_item->ins_size_;
    BuildQuickShadowFrameVisitor shadow_frame_builder(sp, mh.IsStatic(), mh.GetShorty(),
                                                 mh.GetShortyLength(),
                                                 *shadow_frame, first_arg_reg);
    shadow_frame_builder.VisitArguments();
    // Push a transition back into managed code onto the linked list in thread.
    ManagedStack fragment;
    self->PushManagedStackFragment(&fragment);
    self->PushShadowFrame(shadow_frame);
    self->EndAssertNoThreadSuspension(old_cause);

    if (method->IsStatic() && !method->GetDeclaringClass()->IsInitializing()) {
      // Ensure static method's class is initialized.
      if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(method->GetDeclaringClass(),
                                                                   true, true)) {
        DCHECK(Thread::Current()->IsExceptionPending());
        self->PopManagedStackFragment(fragment);
        return 0;
      }
    }

    JValue result = interpreter::EnterInterpreterFromStub(self, mh, code_item, *shadow_frame);
    // Pop transition.
    self->PopManagedStackFragment(fragment);
    return result.GetJ();
  }
}
這個函數定義在文件art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc中。

函數artQuickToInterpreterBridge的作用實際上就是找到被調用類方法method的DEX字節碼code_item,然後根據調用傳入的參數構造一個解釋器調用棧幀shadow_frame,最後就可以通過函數interpreter::EnterInterpreterFromStub進入到解釋器去執行了。

既然已經知道了要執行的類方法的DEX字節碼,以及已經構造好了要執行的類方法的調用棧幀,我們就不難理解解釋器是如何執行該類方法了,具體可以參考一下Dalvik虛擬機的運行過程分析這篇文章描述的Dalvik虛擬機解釋器的實現。

如果要執行的類方法method是一個靜態方法,那麼我們就需要確保它的聲明類是已經初始化過了的。如果還沒有初始化過,那麼就需要調用ClassLinker類的成員函數EnsureInitialized來對它進行初始化。

函數artInterpreterToInterpreterBridge用來從解釋執行的函數調用到另外一個也是解釋執行的函數,它的實現如下所示:

extern "C" void artInterpreterToInterpreterBridge(Thread* self, MethodHelper& mh,
                                                  const DexFile::CodeItem* code_item,
                                                  ShadowFrame* shadow_frame, JValue* result) {
  if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEnd())) {
    ThrowStackOverflowError(self);
    return;
  }

  ArtMethod* method = shadow_frame->GetMethod();
  if (method->IsStatic() && !method->GetDeclaringClass()->IsInitializing()) {
    if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(method->GetDeclaringClass(),
                                                                 true, true)) {
      DCHECK(Thread::Current()->IsExceptionPending());
      return;
    }
    CHECK(method->GetDeclaringClass()->IsInitializing());
  }

  self->PushShadowFrame(shadow_frame);

  if (LIKELY(!method->IsNative())) {
    result->SetJ(Execute(self, mh, code_item, *shadow_frame, JValue()).GetJ());
  } else {
    // We don't expect to be asked to interpret native code (which is entered via a JNI compiler
    // generated stub) except during testing and image writing.
    CHECK(!Runtime::Current()->IsStarted());
    Object* receiver = method->IsStatic() ? NULL : shadow_frame->GetVRegReference(0);
    uint32_t* args = shadow_frame->GetVRegArgs(method->IsStatic() ? 0 : 1);
    UnstartedRuntimeJni(self, method, receiver, args, result);
  }

  self->PopShadowFrame();
  return;
}
這個函數定義在文件art/runtime/interpreter/interpreter.cc中。

對比函數artInterpreterToInterpreterBridge和artQuickToInterpreterBridge的實現就可以看出,雖然都是要跳入到解釋器去執行一個被調用類方法,但是兩者的實現是不一樣的。前者由於調用方法本來就是在解釋器中執行的,因此,調用被調用類方法所需要的解釋器棧幀實際上已經准備就緒,並且被調用方法的DE字節碼也已經知曉,因此這時候就可以直接調用另外一個函數Execute來繼續在解釋器中執行。

同樣,如果被調用的類方法是一個靜態方法,並且它的聲明類還沒有被初始化,那麼就需要調用ClassLinker類的成員函數EnsureInitialized來確保它的聲明類是已經初始化好了的。

如果被調用的類方法是一個JNI方法,那麼此種情況在ART運行時已經啟動之後不允許的(ART運行時啟動之前允許,但是只是測試ART運行時時才會用到),因為JNI方法在解釋器中有自己的調用方式,而函數函數artInterpreterToInterpreterBridge僅僅是用於調用非JNI方法,因此這時候就會調用另外一個函數UnstartedRuntimeJni記錄和拋出錯誤。

函數GetResolutionTrampoline用來獲得一個延遲鏈接類方法的函數。這個延遲鏈接類方法的函數用作那些在類加載時還沒有鏈接好的方法的調用入口點,也就是還沒有確定調用入口的類方法。對於已經鏈接好的類方法來說,無論它是解釋執行,還是本地機器指令執行,相應的調用入口都是已經通過ArtMehtod類的成員函數SetEntryPointFromCompiledCode和SetEntryPointFromInterpreter設置好了的。如上所術,這類典型的類方法就是靜態方法,它們需要等到類初始化的時候才會進行鏈接。

函數GetResolutionTrampoline的實現如下所示:

static inline const void* GetResolutionTrampoline(ClassLinker* class_linker) {
#if defined(ART_USE_PORTABLE_COMPILER)
  return GetPortableResolutionTrampoline(class_linker);
#else
  return GetQuickResolutionTrampoline(class_linker);
#endif
}

這個函數定義在文件art/runtime/entrypoints/entrypoint_utils.h中。

我們假設沒有定義宏ART_USE_PORTABLE_COMPILER,那麼接下來就會調用GetQuickResolutionTrampoline來獲得一個函數指針。

函數GetQuickResolutionTrampoline的實現如下所示:

static inline const void* GetQuickResolutionTrampoline(ClassLinker* class_linker) {
  return class_linker->GetQuickResolutionTrampoline();
}
這個函數定義在文件art/runtime/entrypoints/entrypoint_utils.h中。

函數GetQuickResolutionTrampoline又是通過調用參數class_linker指向的ClassLinker對象的成員函數GetQuickResolutionTrampoline來獲得一個函數指針的。

ClassLinker類的成員函數GetQuickResolutionTrampoline的實現如下所示:

class ClassLinker {
 public:
  ......

  const void* GetQuickResolutionTrampoline() const {
    return quick_resolution_trampoline_;
  }

  ......

 private:
  ......

  const void* quick_resolution_trampoline_;
  
  ......
};
這個函數定義在文件art/runtime/class_linker.h中。

ClassLinker類的成員函數GetQuickResolutionTrampoline返回的是成員變量quick_resolution_trampoline_的值。那麼ClassLinker類的成員變量quick_resolution_trampoline_的值是什麼時候初始化的呢?是在ClassLinker類的成員函數InitFromImage中初始化。如下所示:

void ClassLinker::InitFromImage() {
  ......

  gc::Heap* heap = Runtime::Current()->GetHeap();
  gc::space::ImageSpace* space = heap->GetImageSpace();
  ......

  OatFile& oat_file = GetImageOatFile(space);
  ......

  quick_resolution_trampoline_ = oat_file.GetOatHeader().GetQuickResolutionTrampoline();
 
  ......
}
這個函數定義在文件art/runtime/class_linker.h中。

從前面Android運行時ART加載OAT文件的過程分析這篇文章可以知道,ART運行時在啟動的時候,會加載一個Image文件,並且根據這個Image文件創建一個Image空間。這個Image空間屬於ART運行時堆的一部分。後面我們分析ART運行時的垃圾收集機制再詳細分析。

Image文件是ART運行時第一次啟動時翻譯系統啟動類的DEX字節碼創建的OAT文件的過程中創建的。我們將這個OAT文件稱為啟動OAT文件。這個啟動OAT文件的OAT頭部包含有一個quick_resolution_trampoline_offset_字段。這個quick_resolution_trampoline_offset_字段指向一小段Trampoline代碼。這一小段Trampoline代碼的作用是找到當前線程類型為Quick的函數跳轉表中的pQuickResolutionTrampoline項,並且跳到這個pQuickResolutionTrampoline項指向的函數去執行。

從上面的分析可以知道,類型為Quick的函數跳轉表中的pQuickResolutionTrampoline項指向的函數為art_quick_resolution_trampoline,它是一個用匯編語言實現的函數,如下所示:

    .extern artQuickResolutionTrampoline
ENTRY art_quick_resolution_trampoline
    SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME
    mov     r2, r9                 @ pass Thread::Current
    mov     r3, sp                 @ pass SP
    blx     artQuickResolutionTrampoline  @ (Method* called, receiver, Thread*, SP)
    cbz     r0, 1f                 @ is code pointer null? goto exception
    mov     r12, r0
    ldr  r0, [sp, #0]              @ load resolved method in r0
    ldr  r1, [sp, #8]              @ restore non-callee save r1
    ldrd r2, [sp, #12]             @ restore non-callee saves r2-r3
    ldr  lr, [sp, #44]             @ restore lr
    add  sp, #48                   @ rewind sp
    .cfi_adjust_cfa_offset -48
    bx      r12                    @ tail-call into actual code
1:
    RESTORE_REF_AND_ARGS_CALLEE_SAVE_FRAME
    DELIVER_PENDING_EXCEPTION
END art_quick_resolution_trampoline
這個函數定義在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。

函數art_quick_resolution_trampoline首先是調用另外一個函數artQuickResolutionTrampoline來獲得真正要調用的函數的地址,並且通過bx指令跳到該地址去執行。函數artQuickResolutionTrampoline的作用就是用來延遲鏈接類方法的,也就是等到該類方法被調用時才會對它進行解析鏈接,確定真正要調用的函數。

函數artQuickResolutionTrampoline的實現如下所示:

// Lazily resolve a method for quick. Called by stub code.
extern "C" const void* artQuickResolutionTrampoline(mirror::ArtMethod* called,
                                                    mirror::Object* receiver,
                                                    Thread* thread, mirror::ArtMethod** sp)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  FinishCalleeSaveFrameSetup(thread, sp, Runtime::kRefsAndArgs);  
  ......

  // Start new JNI local reference state
  JNIEnvExt* env = thread->GetJniEnv();
  ScopedObjectAccessUnchecked soa(env);
  ......

  // Compute details about the called method (avoid GCs)
  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  mirror::ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp);
  InvokeType invoke_type;
  const DexFile* dex_file;
  uint32_t dex_method_idx;
  if (called->IsRuntimeMethod()) {
    uint32_t dex_pc = caller->ToDexPc(QuickArgumentVisitor::GetCallingPc(sp));
    const DexFile::CodeItem* code;
    {
      MethodHelper mh(caller);
      dex_file = &mh.GetDexFile();
      code = mh.GetCodeItem();
    }
    const Instruction* instr = Instruction::At(&code->insns_[dex_pc]);
    Instruction::Code instr_code = instr->Opcode();
    bool is_range;
    switch (instr_code) {
      case Instruction::INVOKE_DIRECT:
        invoke_type = kDirect;
        is_range = false;
        break;
      case Instruction::INVOKE_DIRECT_RANGE:
        invoke_type = kDirect;
        is_range = true;
        break;
      case Instruction::INVOKE_STATIC:
        invoke_type = kStatic;
        is_range = false;
        break;
      case Instruction::INVOKE_STATIC_RANGE:
        invoke_type = kStatic;
        is_range = true;
        break;
      case Instruction::INVOKE_SUPER:
        invoke_type = kSuper;
        is_range = false;
        break;
      case Instruction::INVOKE_SUPER_RANGE:
        invoke_type = kSuper;
        is_range = true;
        break;
      case Instruction::INVOKE_VIRTUAL:
        invoke_type = kVirtual;
        is_range = false;
        break;
      case Instruction::INVOKE_VIRTUAL_RANGE:
        invoke_type = kVirtual;
        is_range = true;
        break;
      case Instruction::INVOKE_INTERFACE:
        invoke_type = kInterface;
        is_range = false;
        break;
      case Instruction::INVOKE_INTERFACE_RANGE:
        invoke_type = kInterface;
        is_range = true;
        break;
      default:
        LOG(FATAL) << "Unexpected call into trampoline: " << instr->DumpString(NULL);
        // Avoid used uninitialized warnings.
        invoke_type = kDirect;
        is_range = false;
    }
    dex_method_idx = (is_range) ? instr->VRegB_3rc() : instr->VRegB_35c();

  } else {
    invoke_type = kStatic;
    dex_file = &MethodHelper(called).GetDexFile();
    dex_method_idx = called->GetDexMethodIndex();
  }
  ......

  // Resolve method filling in dex cache.
  if (called->IsRuntimeMethod()) {
    called = linker->ResolveMethod(dex_method_idx, caller, invoke_type);
  }

  const void* code = NULL;
  if (LIKELY(!thread->IsExceptionPending())) {
    ......
    // Refine called method based on receiver.
    if (invoke_type == kVirtual) {
      called = receiver->GetClass()->FindVirtualMethodForVirtual(called);
    } else if (invoke_type == kInterface) {
      called = receiver->GetClass()->FindVirtualMethodForInterface(called);
    }
    // Ensure that the called method's class is initialized.
    mirror::Class* called_class = called->GetDeclaringClass();
    linker->EnsureInitialized(called_class, true, true);
    if (LIKELY(called_class->IsInitialized())) {
      code = called->GetEntryPointFromCompiledCode();
    } else if (called_class->IsInitializing()) {
      if (invoke_type == kStatic) {
        // Class is still initializing, go to oat and grab code (trampoline must be left in place
        // until class is initialized to stop races between threads).
        code = linker->GetOatCodeFor(called);
      } else {
        // No trampoline for non-static methods.
        code = called->GetEntryPointFromCompiledCode();
      }
    } 
    ......
  }
 
  ......

  // Place called method in callee-save frame to be placed as first argument to quick method.
  *sp = called;
  return code;
}

這個函數定義在文件art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc中。

第一個參數called表示被調用的類方法,第二個參數receiver表示被調用的對象,也就是接收消息的對象,第三個參數thread表示當前線程,第四個參數sp指向調用棧頂。通過調用QuickArgumentVisitor類的靜態成員函數GetCallingMethod可以在調用棧找到類方法called的調用者,保存在變量caller中。

被調用類方法called有可能是一個運行時方法(Runtime Method)。運行時方法相當是一個替身,它是用來找到被替換的類方法。當調用類方法called是一個運行時方法時,調用它的成員函數IsRuntimeMethod得到的返回值為true,這時候我們就需要找到被替換的類方法。那麼問題就來了,怎麼找到此時被替換的類方法呢?運行時方法只是一個空殼,沒有任何線索可以提供給我們,不過我們卻可以在DEX字節碼的調用指令中找到一些蜘絲馬跡。在DEX字節碼中,我們在一個類方法中通過invoke-static/invoke-direct/invoke-interface/invoke-super/invoke-virtual等指令來調用另外一個類方法。在這些調用指令中,有一個寄存器記錄了被調用的類方法在DEX文件中的方法索引dex_method_index。有了這個DEX文件方法索引之後,我們就可以在相應的DEX文件找到被替換的類方法了。現在第二個問題又來了,我們要在哪一個DEX文件查找被替換的類方法呢?函數artQuickResolutionTrampoline適用的是調用方法caller和被調用方法called均是位於同一個DEX文件的情況。因此,我們可以通過調用方法caller來得到要查找的DEX文件dex_file。有了上述兩個重要的信息之後,函數artQuickResolutionTrampoline接下來就可以調用ClassLinker類的成員函數ResolveMethod來查找被替換的類方法了,並且繼續保存在參數called中。另一方面,如果被調用類方法called不是運行時方法,那麼情況就簡單多了,因為此時called描述的便是要調用的類方法。

經過上面的處理之後,參數called指向的ArtMethod對象還不一定是最終要調用的類方法。這是因為當前發生的可能是一個虛函數調用或者接口調用。在上述兩種情況下,我們需要通過接收消息的對象receiver來確定真正被調用的類方法。為了完成這個任務,我們首先通過調用Object類的成員函數GetClass獲得接收消息的對象receiver的類對象,接著再通過調用過Class類的成員函數FindVirtualMethodForVirtual或者FindVirtualMethodForInterface來獲得真正要被調用的類方法。前者針對的是虛函數調用,而後者針對的是接口調用。

最終我們得到的真正被調用的類方法仍然是保存在參數called中。這時候事情還沒完,因為此時被調用的類方法所屬的類可能還沒有初始化好。因此,在繼續下一步操作之前,我們需要調用ClassLinker類的成員函數EnsureInitialized來確保存被調用類方法called所屬的類已經初始好了。在調用ClassLinker類的成員函數EnsureInitialized的時候,如果被調用類方法called所屬的類還沒有初始化,那麼就會對它進行初始化,不過不等它初始化完成就返回了。因此,這時候就可能會出現兩種情況。

第一種情況是被調用類方法called所屬的類已經初始好了。這時候我們就可以直接調用它的成員函數GetEntryPointFromCompiledCode來獲得它的本地機器指令或者DEX字節碼,取決於它是以本地機器指令方式執行還是通過解釋器來執行。

第二種情況是被調用方法called所屬的類正在初始化中。這時候需要區分靜態和非靜態調用兩種情況。在進一步解釋之前,我們需要明確,類加載的類初始化是兩個不同的操作。類加載的過程並不一定會伴隨著類的初始化。此時我們唯一確定的是被調用方法called所屬的類已經被加載(否則它的類方法無法被調用)。又從前面Android運行時ART加載類和方法的過程分析這篇文章可以知道,當一個類被加載時,除了它的靜態成員函數,其余所有的成員函數均已加載完畢。這意味著我們可以直接調用被調用方法called的成員函數GetEntryPointFromCompiledCode來獲得它的本地機器指令或者DEX字節碼。對於靜態成員函數的情況,我們就唯有到DEX文件去查找到被調用方法called的本地機器指令了。這是通過調用ClassLinker類的成員函數GetOatCodeFor來實現的。當然,如果該靜態成員函數不存在本地機器指令,那麼ClassLinker類的成員函數GetOatCodeFor返回的是進入解釋器的入口函數地址。這樣我們就可以通過解釋器來執行該靜態成員函數了。

最後,函數artQuickResolutionTrampoline將獲得的真正被調用的類方法的執行入口地址code返回給前一個函數,即art_quick_resolution_trampoline,以便後者可以通過bx跳過去執行。函數artQuickResolutionTrampoline在返回之前,同時還會將此時棧頂的內容設置為真正被調用的類方法對象,以便真正被調用的類方法在運行時,可以獲得正確的調用棧幀。

到這裡,函數artQuickResolutionTrampoline的實現就分析完成了。不過對於上面提到的運行時方法,我們還需要繼續解釋。只有了理解了運行時方法的作用之後,我們才能真正理解函數artQuickResolutionTrampoline的作用。

運行時方法與另一個稱為Dex Cache的機制有關。在ART運行時中,每一個DEX文件都有一個關聯的Dex Cache,用來緩存對應的DEX文件中已經被解析過的信息,例如類方法和類屬性等。這個Dex Cache使用類DexCache來描述,它的定義如下所示:

class MANAGED DexCache : public Object {
 public:
  ......

 private:
  ......
  ObjectArray* resolved_methods_;
  .....
  uint32_t dex_file_;
  ......
};
這個類定義在文件rt/runtime/mirror/dex_cache.h中。

這裡我們只關注Dex Cache中的類方法,它通過成員變量resolved_methods_來描述。

在ART運行時中,每當一個類被加載時,ART運行時都會檢查該類所屬的DEX文件是否已經關聯有一個Dex Cache。如果還沒有關聯,那麼就會創建一個Dex Cache,並且建立好關聯關系。以Java層的DexFile類為例,當我們通過調用它的成員函數loadClass來加載一個類的時候,最終會調用到C++層的JNI函數DexFile_defineClassNative來執行真正的加載操作。

函數DexFile_defineClassNative的實現如下所示:

static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
                                        jint cookie) {
  ScopedObjectAccess soa(env);
  const DexFile* dex_file = toDexFile(cookie);
  ......

  ScopedUtfChars class_name(env, javaName);
  ......

  const std::string descriptor(DotToDescriptor(class_name.c_str()));
  const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str());
  ......

  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
  class_linker->RegisterDexFile(*dex_file);
  mirror::ClassLoader* class_loader = soa.Decode(javaLoader);
  mirror::Class* result = class_linker->DefineClass(descriptor.c_str(), class_loader, *dex_file,
                                                    *dex_class_def);
  ......
  return soa.AddLocalReference(result);
}

這個函數定義在文件art/runtime/native/dalvik_system_DexFile.cc中。

參數cookie指向之前已經打開了的DEX文件,因此這裡首先將它轉換為一個DexFile指針dex_file。這個DEX文件是包含在OAT文件裡面的,它們的打開過程可以參考Android運行時ART加載OAT文件的過程分析一文。得到了之前打開的DEX文件之後,接下來就調用ClassLinker類的成員函數RegisterDexFile將它注冊到ART運行時中去,以便以後可以查詢使用。最後再通過ClassLinker類的成員函數DefineClass來加載參數javaName指定的類。

類的加載過程可以參考前面Android運行時ART加載類和方法的過程分析一文,接下來我們主要關注ClassLinker類的成員函數RegisterDexFile的實現,如下所示:

void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file, SirtRef& dex_cache) {
  dex_lock_.AssertExclusiveHeld(Thread::Current());
  CHECK(dex_cache.get() != NULL) << dex_file.GetLocation();
  CHECK(dex_cache->GetLocation()->Equals(dex_file.GetLocation()))
      << dex_cache->GetLocation()->ToModifiedUtf8() << " " << dex_file.GetLocation();
  dex_caches_.push_back(dex_cache.get());
  dex_cache->SetDexFile(&dex_file);
  dex_caches_dirty_ = true;
}

void ClassLinker::RegisterDexFile(const DexFile& dex_file) {
  Thread* self = Thread::Current();
  {
    ReaderMutexLock mu(self, dex_lock_);
    if (IsDexFileRegisteredLocked(dex_file)) {
      return;
    }
  }
  // Don't alloc while holding the lock, since allocation may need to
  // suspend all threads and another thread may need the dex_lock_ to
  // get to a suspend point.
  SirtRef dex_cache(self, AllocDexCache(self, dex_file));
  CHECK(dex_cache.get() != NULL) << "Failed to allocate dex cache for " << dex_file.GetLocation();
  {
    WriterMutexLock mu(self, dex_lock_);
    if (IsDexFileRegisteredLocked(dex_file)) {
      return;
    }
    RegisterDexFileLocked(dex_file, dex_cache);
  }
}
這個函數定義在文件art/runtime/class_linker.cc中。

ClassLinker類的成員函數RegisterDexFile首先將調用另外一個成員函數IsDexFileRegisteredLocked檢查參數dex_file指定的DEX文件是否已經注冊過了。如果已經注冊過了,那麼就什麼也不用做。否則的話,就調用ClassLinker類的成員函數AllocDexCache為其分配一個Dex Cache,並且調用ClassLinker類的成員函數RegisterDexFileLocked執行真正的注冊工作。

從上面的分析就可以看出,注冊DEX文件實際上就是創建一個與之關聯的Dex Cache,並且將該Dex Cache保存在ClassLinker類的成員變量dex_caches_所描述的一個向量中。不過,這裡我們關注的是注冊過程中所創建的Dex Cache。因此,接下來我們繼續分析ClassLinker類的成員函數AllocDexCache的實現,如下所示:

mirror::DexCache* ClassLinker::AllocDexCache(Thread* self, const DexFile& dex_file) {
  gc::Heap* heap = Runtime::Current()->GetHeap();
  mirror::Class* dex_cache_class = GetClassRoot(kJavaLangDexCache);
  SirtRef dex_cache(self,
                              down_cast(heap->AllocObject(self, dex_cache_class,
                                                                dex_cache_class->GetObjectSize())));
  ......

  SirtRef
      location(self, intern_table_->InternStrong(dex_file.GetLocation().c_str()));
  ......

  SirtRef >
      strings(self, AllocStringArray(self, dex_file.NumStringIds()));
  ......

  SirtRef >
      types(self, AllocClassArray(self, dex_file.NumTypeIds()));
  ......

  SirtRef >
      methods(self, AllocArtMethodArray(self, dex_file.NumMethodIds()));
  ......

  SirtRef >
      fields(self, AllocArtFieldArray(self, dex_file.NumFieldIds()));
  ......

  SirtRef >
      initialized_static_storage(self,
                          AllocObjectArray(self, dex_file.NumTypeIds()));
  ......

  dex_cache->Init(&dex_file,
                  location.get(),
                  strings.get(),
                  types.get(),
                  methods.get(),
                  fields.get(),
                  initialized_static_storage.get());
  return dex_cache.get();
}
這個函數定義在文件art/runtime/class_linker.cc中。

我們要創建的Dex Cache使用java.lang.DexCache類來描述。java.lang.DexCache類對象保存在ART運行時的Image空間中,我們可以通過ClassLinker類的成員函數GetClassRoot來獲得的。獲得了用來描述java.lang.DexCache類的類對象之後,我們就可以調用Heap類的成員函數AllocObject在ART運行堆上分配一個DexCache對象了。關於ART運行時的Image空間和堆,我們接下來的文章中將會詳細分析。

Dex Cache的作用是用來緩存包含在一個DEX文件裡面的類型(Type)、方法(Method)、域(Filed)、字符串(String)和靜態儲存區(Static Storage)等信息。因此,我們需要為上述提到的信息分配儲存空間。例如,對於類方法來說,我們需要創建一個ArtMethod對象指針數組,其中每一個ArtMethod對象都用來描述DEX文件裡面的一個類方法。有了這些儲存空間之後,最後就可以調用DexCache類的成員函數Init對剛才創建的Dex Cache進行初始化了。

DecCache類的成員函數Init的實現如下所示:

void DexCache::Init(const DexFile* dex_file,
                    String* location,
                    ObjectArray* strings,
                    ObjectArray* resolved_types,
                    ObjectArray* resolved_methods,
                    ObjectArray* resolved_fields,
                    ObjectArray* initialized_static_storage) {
  ......

  SetFieldObject(ResolvedMethodsOffset(), resolved_methods, false);
  ......

  Runtime* runtime = Runtime::Current();
  if (runtime->HasResolutionMethod()) {
    // Initialize the resolve methods array to contain trampolines for resolution.
    ArtMethod* trampoline = runtime->GetResolutionMethod();
    size_t length = resolved_methods->GetLength();
    for (size_t i = 0; i < length; i++) {
      resolved_methods->SetWithoutChecks(i, trampoline);
    }
  }
}
這個函數定義在文件art/runtime/mirror/dex_cache.cc中。

我們重點關注Dex Cache中的類方法對象數組的初始化。前面提到,DexCache類有一個類型為ObjectArray的resolved_methods_指針數組。我們通過DexCache類的成員函數ResolvedMethodsOffset可以知道它在DexCache類中的偏移值。有了這個偏移值之後,就可以調用父類Object的成員函數SetFieldObject來將參數resolved_methods描述的ArtMethod對象指針數組設置到當前正在初始化的DexCache對象的成員變量resolved_methods_去了。

接下來重點就來了,DexCache類的成員變量resolved_methods_指向的ArtMethod對象指針數組中的每一個ArtMethod指針都會指向同一個Resolution Method。這個Resolution Method可以通過Runtime類的成員函數GetResolutionMethod獲得,它的實現如下所示:

class Runtime {
 public:
  ......

  // Returns a special method that calls into a trampoline for runtime method resolution
  mirror::ArtMethod* GetResolutionMethod() const {
    CHECK(HasResolutionMethod());
    return resolution_method_;
  }
  
  ......
 
 private:
  ......

  mirror::ArtMethod* resolution_method_;

  ......
};
這個函數定義在文件rt/runtime/runtime.h中。

Runtime類的成員函數GetResolutionMethod返回的是成員變量resolution_method_指向的一個ArtMethod對象。

那麼Runtime類的成員變量resolution_method_是什麼時候初始化的呢?是在ART運行時的Image空間初始化過程中初始化的。在前面Android運行時ART加載OAT文件的過程分析一篇文章中,我們提到,ART運行時的Image空間創建完成之後,會調用ImageSpace類的成員函數Init來對它進行初始化。

ImageSpace類的成員函數Init的實現如下所示:

ImageSpace* ImageSpace::Init(const std::string& image_file_name, bool validate_oat_file) {
  ......

  UniquePtr file(OS::OpenFileForReading(image_file_name.c_str()));
  ......

  ImageHeader image_header;
  bool success = file->ReadFully(&image_header, sizeof(image_header));
  ......

  UniquePtr image_map(MemMap::MapFileAtAddress(nullptr, image_header.GetImageBitmapSize(),
                                                       PROT_READ, MAP_PRIVATE,
                                                       file->Fd(), image_header.GetBitmapOffset(),
  ......

  Runtime* runtime = Runtime::Current();
  mirror::Object* resolution_method = image_header.GetImageRoot(ImageHeader::kResolutionMethod);
  runtime->SetResolutionMethod(down_cast(resolution_method));
 
  ......
}
這個函數定義在文件art/runtime/gc/space/image_space.cc中。

ImageSpace類的成員函數Init首先是將Image文件的頭部讀取出來,並且根據得到的Image頭部信息將Image文件映射到指定的內存位置。Image頭部指定了ART運行時使用的Resolution Method在內存中的位置,可以通過ImageHeader類的成員函數GetImageRoot來獲得。獲得了這個Resolution Method之後,就可以調用Runtime類的成員函數SetResolutionMethod將它設置到ART運行時去了。

前面說了那麼多,好像還是沒有發現為什麼要給ART運行時設置一個Resolution Method。迷局就要准備解開了。在解開之前,我們首先要知道ART運行時使用的Resolution Method是長什麼樣的,也就是它是如何創建的。

Resolution Method本質上就一個ArtMethod對象。當我們調用dex2oat工具將系統啟動類翻譯成本地機器指令時,會創建這個Resolution Method,並且將它保存在Image文件中。這樣以後要使用這個Resolution Method時,就可以將對應的Image文件加載到內存獲得。

Resolution Method是通過調用Runtime類的成員函數CreateResolutionMethod來創建的,如下所示:

mirror::ArtMethod* Runtime::CreateResolutionMethod() {
  mirror::Class* method_class = mirror::ArtMethod::GetJavaLangReflectArtMethod();
  Thread* self = Thread::Current();
  SirtRef
      method(self, down_cast(method_class->AllocObject(self)));
  method->SetDeclaringClass(method_class);
  // TODO: use a special method for resolution method saves
  method->SetDexMethodIndex(DexFile::kDexNoIndex);
  // When compiling, the code pointer will get set later when the image is loaded.
  Runtime* r = Runtime::Current();
  ClassLinker* cl = r->GetClassLinker();
  method->SetEntryPointFromCompiledCode(r->IsCompiler() ? NULL : GetResolutionTrampoline(cl));
  return method.get();
}
這個函數定義在文件art/runtime/runtime.cc中。

從Runtime類的成員函數CreateResolutionMethod的實現就可以看出,ART運行時的Resoultion Method有以下兩個特點:

1. 它的Dex Method Index為DexFile::kDexNoIndex,這是因為它不代表任何的類方法。

2. 由於上述原因,它沒有相應的本地機器指令,困此它不能執行。

回想我們前面分析的函數artQuickResolutionTrampoline,它通過ArtMethod類的成員函數IsRuntimeMethod來判斷一個ArtMethod對象是否是一個運行時方法。ArtMethod類的成員函數IsRuntimeMethod的實現如下所示:

inline bool ArtMethod::IsRuntimeMethod() const {
  return GetDexMethodIndex() == DexFile::kDexNoIndex;
}
這個函數定義在文件art/runtime/mirror/art_method-inl.h文件中。

如果一個ArtMethod的Dex Method Index等於DexFile::kDexNoIndex,那麼它就被認為是運行時方法。當然,Resoultion Method只是運行方法的其中一種。其中類型的運行時方法我們後面分析ART運行時的Image空間的文章時再講解。

如前面所述,函數artQuickResolutionTrampoline一旦發現一個接著要調用的類方法是一個運行時方法時,就會調用ClassLinker類的成員函數ResolveMethod來對其進行解析,也就是找到真正要被調用的類方法。

ClassLinker類的成員函數ResolveMethod的實現如下所示:

inline mirror::ArtMethod* ClassLinker::ResolveMethod(uint32_t method_idx,
                                                     const mirror::ArtMethod* referrer,
                                                     InvokeType type) {
  mirror::ArtMethod* resolved_method =
      referrer->GetDexCacheResolvedMethods()->Get(method_idx);
  if (UNLIKELY(resolved_method == NULL || resolved_method->IsRuntimeMethod())) {
    mirror::Class* declaring_class = referrer->GetDeclaringClass();
    mirror::DexCache* dex_cache = declaring_class->GetDexCache();
    mirror::ClassLoader* class_loader = declaring_class->GetClassLoader();
    const DexFile& dex_file = *dex_cache->GetDexFile();
    resolved_method = ResolveMethod(dex_file, method_idx, dex_cache, class_loader, referrer, type);
  }
  return resolved_method;
} 
這個函數定義在文件art/runtime/class_linker-inl.h中。

參數method_idx描述的是接下來將要被調用類方法在DEX文件的索引。注意,每一個類方法在宿主類中有一個索引,在對應的DEX文件中也有一個索引。這兩個索引是不一樣的,根據前面Android運行時ART加載類和方法的過程分析一文,前一個索引用來查找一個類方法的本地機器指令。而後面一個索引,自然的就是用來DEX文件中找到對應的類方法描述信息了。這意味著一旦知道一個類方法在DEX文件的索引,那麼就可以在對應的DEX文件中對該類方法進行解析了。一旦解析完成,自然就可以知道接下來要被調用的類方法是什麼了。

參數referrer指向的ArtMethod對象描述的是調用者(類方法)。每一個類方法都關聯有一個ArtMethod對象指針數組,這個ArtMethod對象指針數組實際上就是我們在前面提到的Dex Cache中的ArtMethod對象指針數組。同時,每一個類對象(Class)也關聯有一個Dex Cache。這個Dex Cache實際上就是與包含該類的DEX文件相關聯的Dex Cache。為了搞清楚上述關系,我們回顧一下前面Android運行時ART加載類和方法的過程分析一文提到的ClassLinker類的兩個成員函數DefineClass和LoadMethod。

在ClassLinker類的成員函數DefineClass中,會給每一個加載的類關聯一個Dex Cache,如下所示:

mirror::Class* ClassLinker::DefineClass(const char* descriptor,
                                        mirror::ClassLoader* class_loader,
                                        const DexFile& dex_file,
                                        const DexFile::ClassDef& dex_class_def) {
  Thread* self = Thread::Current();
  SirtRef klass(self, NULL);
  ......

  klass->SetDexCache(FindDexCache(dex_file));
  LoadClass(dex_file, dex_class_def, klass, class_loader);
  ......

}
這個函數定義在文件art/runtime/class_linker.cc中。

變量klass描述的就是正在加載的類,在對其進行加載之前,首先會調用ClassLinker類的成員函數FindDexCache找到與參數dex_file描述的DEX文件相關聯的Dex Cache。有了這個Dex Cache,就可以將它設置到kclass指向的Class對象中去了。注意,參數dex_file描述的DEX文件就是包含正在加載的類的文件。

在ClassLinker類的成員函數LoadMethod中,會給每一個加載的類方法設置一個DEX文件類方法索引,以及關聯一個ArtMethod對象指針數組,如下所示:

mirror::ArtMethod* ClassLinker::LoadMethod(Thread* self, const DexFile& dex_file,
                                           const ClassDataItemIterator& it,
                                           SirtRef& klass) {
  uint32_t dex_method_idx = it.GetMemberIndex();
  ......

  mirror::ArtMethod* dst = AllocArtMethod(self);
  ......

  dst->SetDexMethodIndex(dex_method_idx);
  ......

  dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods());
  ......

}
這個函數定義在文件art/runtime/class_linker.cc中。

變量dst描述的便是正在加載的類方法,我們可以通過參數it獲得它在DEX文件中的類方法索引,並且將該索引設置到變量dst指向的ArtMethod對象中去。

參數klass描述是正在加載的類方法所屬的類,前面我們已經給這個類關聯過一個Dex Cache了,因此,只要將重新獲得該Dex Cache,並且獲得該Dex Cache裡面的ArtMethod對象指針數組,那麼就可以將ArtMethod對象指針數組設置到正在加載的類方法去了。

從ClassLinker類的兩個成員函數DefineClass和LoadMethod的實現就可以看出,同一個DEX文件的所有類關聯的Dex Cache都是同一個Dex Cache,並且屬於這些類的所有類方法關聯的ArtMethod對象指針數組都是該Dex Cache內部的ArtMethod對象指針數組。這個結論對我們理解ClassLinker類的成員函數ResolveMethod的實現很重要。

在ClassLinker類的成員函數ResolveMethod中,我們知道的是調用者以及被調用者在DEX文件中的類方法索引,因此,我們就可以從與調用者關聯的ArtMethod對象指針數組中找到接下來真正要被調用的類方法了。

Dex Cache內部的ArtMethod對象指針數組的每一個ArtMethod指針一開始都是指向ART運行時的Resolution Method。但是每當一個類方法第一次被調用的時候,函數artQuickResolutionTrampoline能夠根據捕捉到這種情況,並且根據調用者和調用指令的信息,通過ClassLinker類的成員函數ResolveMethod找到接下來真正要被調用的類方法。查找的過程就是解析類方法的過程,這是一個漫長的過程,因為要解析DEX文件。不過一旦接下來要被調用的類方法解析完成,就會創建另外一個ArtMethod對象來描述解析得到的信息,並且將該ArtMethod對象保存在對應的Dex Cache內部的ArtMethod對象指針數組的相應位置去。這樣下次該類方法再被調用時,就不用再次解析了。

從上面的分析我們還可以進一步得到以下的結論:

1. 在生成的本地機器指令中,一個類方法調用另外一個類方法並不是直接進行的,而是通過Dex Cache來間接進行的。

2. 通過Dex Cache間接調用類方法,可以做到延時解析類方法,也就是等到類方法第一次時才解析,這樣可以避免解析那些永遠不會被調用的類方法。

3. 一個類方法只會被解析一次,解析的結果保存在Dex Cache中,因此當該類方法再次被調用時,就可以直接從Dex Cache中獲得所需要的信息。

以上就是Dex Cache在ART運行時所起到的作用了,理解這一點對閱讀ART運行時的源代碼非常重要。

有了以上的知識點之後,接下來我們就可以真正地分析類方法的調用過程了。在Android運行時ART加載類和方法的過程分析一文中,我們通過AndroidRuntime類的成員函數start來分析類和類方法的加載過程。本文同樣是從這個函數開始分析類方法的執行過程,如下所示:

void AndroidRuntime::start(const char* className, const char* options)
{
    ......

    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        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); 
            ......
        }
    }

    ......
}
這個函數定義在文件frameworks/base/core/jni/AndroidRuntime.cpp。

找到要調用類方法之後,就可以調用JNI接口CallStaticVoidMethod來執行它了。

根據我們在Android運行時ART加載類和方法的過程分析一文的分析可以知道,JNI接口CallStaticVoidMethod由JNI類的成員函數CallStaticVoidMethod實現,如下所示:

class JNI {
 public:
  ......

  static void CallStaticVoidMethod(JNIEnv* env, jclass, jmethodID mid, ...) {
    va_list ap;
    va_start(ap, mid);
    CHECK_NON_NULL_ARGUMENT(CallStaticVoidMethod, mid);
    ScopedObjectAccess soa(env);
    InvokeWithVarArgs(soa, NULL, mid, ap);
    va_end(ap);
  }

  ......
};
這個函數定義在文件art/runtime/jni_internal.cc中。

JNI類的成員函數CallStaticVoidMethod實際上又是通過全局函數InvokeWithVarArgs來調用參數mid指定的方法的,如下所示:

static JValue InvokeWithVarArgs(const ScopedObjectAccess& soa, jobject obj,
                                jmethodID mid, va_list args)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  ArtMethod* method = soa.DecodeMethod(mid);
  Object* receiver = method->IsStatic() ? NULL : soa.Decode(obj);
  MethodHelper mh(method);
  JValue result;
  ArgArray arg_array(mh.GetShorty(), mh.GetShortyLength());
  arg_array.BuildArgArray(soa, receiver, args);
  InvokeWithArgArray(soa, method, &arg_array, &result, mh.GetShorty()[0]);
  return result;
}
這個函數定義在文件art/runtime/jni_internal.cc中。

函數InvokeWithVarArgs將調用參數封裝在一個數組中,然後再調用另外一個函數InvokeWithArgArray來參數mid指定的方法。參數mid實際上是一個ArtMethod對象指針,因此,我們可以將它轉換為一個ArtMethod指針,於是就可以得到被調用類方法的相關信息了。

函數InvokeWithArgArray的實現如下所示:

void InvokeWithArgArray(const ScopedObjectAccess& soa, ArtMethod* method,
                        ArgArray* arg_array, JValue* result, char result_type)
    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
  uint32_t* args = arg_array->GetArray();
  if (UNLIKELY(soa.Env()->check_jni)) {
    CheckMethodArguments(method, args);
  }
  method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, result_type);
}
這個函數定義在文件art/runtime/jni_internal.cc中。

函數InvokeWithArgArray通過ArtMethod類的成員函數Invoke來調用參數method指定的類方法。

ArtMethod類的成員函數Invoke的實現如下所示:

void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
                       char result_type) {
  ......

  // Push a transition back into managed code onto the linked list in thread.
  ManagedStack fragment;
  self->PushManagedStackFragment(&fragment);

  Runtime* runtime = Runtime::Current();
  // Call the invoke stub, passing everything as arguments.
  if (UNLIKELY(!runtime->IsStarted())) {
    ......
    if (result != NULL) {
      result->SetJ(0);
    }
  } else {
    const bool kLogInvocationStartAndReturn = false;
    if (GetEntryPointFromCompiledCode() != NULL) {
      ......
#ifdef ART_USE_PORTABLE_COMPILER
      (*art_portable_invoke_stub)(this, args, args_size, self, result, result_type);
#else
      (*art_quick_invoke_stub)(this, args, args_size, self, result, result_type);
#endif
      if (UNLIKELY(reinterpret_cast(self->GetException(NULL)) == -1)) {
        // Unusual case where we were running LLVM generated code and an
        // exception was thrown to force the activations to be removed from the
        // stack. Continue execution in the interpreter.
        self->ClearException();
        ShadowFrame* shadow_frame = self->GetAndClearDeoptimizationShadowFrame(result);
        self->SetTopOfStack(NULL, 0);
        self->SetTopOfShadowStack(shadow_frame);
        interpreter::EnterInterpreterFromDeoptimize(self, shadow_frame, result);
      }
      ......
    } else {
      ......
      if (result != NULL) {
        result->SetJ(0);
      }
    }
  }

  // Pop transition.
  self->PopManagedStackFragment(fragment);
}

這個函數定義在文件rt/runtime/mirror/art_method.cc中。

ArtMethod類的成員函數Invoke的執行邏輯如下所示:

1. 構造一個類型為ManagedStack的調用棧幀。這些調用棧幀會保存在當前線程對象的一個鏈表中,在進行垃圾收集會使用到。

2. 如果ART運行時還沒有啟動,那麼這時候是不能夠調用任何類方法的,因此就直接返回。否則,繼續往下執行。

3. 從前面的函數LinkCode可以知道,無論一個類方法是通過解釋器執行,還是直接以本地機器指令執行,均可以通過ArtMethod類的成員函數GetEntryPointFromCompiledCode獲得其入口點,並且該入口不為NULL。不過,這裡並沒有直接調用該入口點,而是通過Stub來間接調用。這是因為我們需要設置一些特殊的寄存器。如果ART運行時使用的是Quick類型的後端,那麼使用的Stub就為art_quick_invoke_stub。否則的話,使用的Stub就為art_portable_invoke_stub。

4. 如果在執行類方法的過程中,出現了一個值為-1的異常,那麼就在運行生成的本地機器指令出現了問題,這時候就通過解釋器來執繼續執行。每次通過解釋器執行一個類方法的時候,都需要構造一個類型為ShadowFrame的調用棧幀。這些調用棧幀同樣是在垃圾回收時使用到。

接下來我們主要是分析第3步,並且假設目標CPU體系架構為ARM,以及ART運行時使用的是Quick類型的後端,這樣第3步使用的Stub就為函數art_quick_invoke_stub,它的實現如下所示:

    /*
     * Quick invocation stub.
     * On entry:
     *   r0 = method pointer
     *   r1 = argument array or NULL for no argument methods
     *   r2 = size of argument array in bytes
     *   r3 = (managed) thread pointer
     *   [sp] = JValue* result
     *   [sp + 4] = result type char
     */
ENTRY art_quick_invoke_stub
    push   {r0, r4, r5, r9, r11, lr}       @ spill regs
    .save  {r0, r4, r5, r9, r11, lr}
    .pad #24
    .cfi_adjust_cfa_offset 24
    .cfi_rel_offset r0, 0
    .cfi_rel_offset r4, 4
    .cfi_rel_offset r5, 8
    .cfi_rel_offset r9, 12
    .cfi_rel_offset r11, 16
    .cfi_rel_offset lr, 20
    mov    r11, sp                         @ save the stack pointer
    .cfi_def_cfa_register r11
    mov    r9, r3                          @ move managed thread pointer into r9
    mov    r4, #SUSPEND_CHECK_INTERVAL     @ reset r4 to suspend check interval
    add    r5, r2, #16                     @ create space for method pointer in frame
    and    r5, #0xFFFFFFF0                 @ align frame size to 16 bytes
    sub    sp, r5                          @ reserve stack space for argument array
    add    r0, sp, #4                      @ pass stack pointer + method ptr as dest for memcpy
    bl     memcpy                          @ memcpy (dest, src, bytes)
    ldr    r0, [r11]                       @ restore method*
    ldr    r1, [sp, #4]                    @ copy arg value for r1
    ldr    r2, [sp, #8]                    @ copy arg value for r2
    ldr    r3, [sp, #12]                   @ copy arg value for r3
    mov    ip, #0                          @ set ip to 0
    str    ip, [sp]                        @ store NULL for method* at bottom of frame
    ldr    ip, [r0, #METHOD_CODE_OFFSET]   @ get pointer to the code
    blx    ip                              @ call the method
    mov    sp, r11                         @ restore the stack pointer
    ldr    ip, [sp, #24]                   @ load the result pointer
    strd   r0, [ip]                        @ store r0/r1 into result pointer
    pop    {r0, r4, r5, r9, r11, lr}       @ restore spill regs
    .cfi_adjust_cfa_offset -24
    bx     lr
END art_quick_invoke_stub
這個函數定義在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。

函數art_quick_invoke_stub前面的注釋列出了 函數art_quick_invoke_stub被調用的時候,寄存器r0-r3的值,以及調用棧頂端的兩個值。其中,r0指向當前被調用的類方法,r1指向一個參數數組地址,r2記錄參數數組的大小,r3指向當前線程。調用棧頂端的兩個元素分別用來保存調用結果及其類型。

無論一個類方法是通過解釋器執行,還是直接以本地機器指令執行,當它被調用時,都有著特殊的調用約定。其中,寄存器r9指向用來描述當前調用線程的一個Thread對象地址,這樣本地機器指令在執行的過程中,就可以通過它來定位線程的相關信息,例如我們在前面描述的各種函數跳轉表;寄存器r4初始化為一個計數值,當計數值遞減至0時,就需要檢查當前線程是否已經被掛起;寄存器r0指向用來描述被調用類方法的一個ArtMethod對象地址。

所有傳遞給被調用方法的參數都會保存在調用棧中,因此,在進入類方法的入口點之前,需要在棧中預留足夠的位置,並且通過調用memcpy函數將參數都拷貝到預留的棧位置去。同時,前面三個參數還會額外地保存在寄存器r1、r2和r3中。這樣對於少於等於3個參數的類方法,就可以通過訪問寄存器來快速地獲得參數。

注意,傳遞給被調用類方法的參數並不是從棧頂第一個位置(一個位置等於一個字長,即4個字節)開始保存的,而是從第二個位置開始的,即sp + 4。這是因為棧頂的第一個位置是預留用來保存用來描述當調用類方法(Caller)的ArtMethod對象地址的。由於函數art_quick_invoke_stub是用來從外部進入到ART運行時的,即不存在調用類方法,因此這時候棧頂第一個位置會被設置為NULL。

准備好調用棧幀之後,就找到從用來描述當前調用類方法的ArtMethod對象地址偏移METHOD_CODE_OFFSET處的值,並且以該值作為類方法的執行入口點,最後通過blx指令跳過去執行。

METHOD_CODE_OFFSET的值定義在文件art/runtime/asm_support.h中,值為40,如下所示:

// Offset of field Method::entry_point_from_compiled_code_
#define METHOD_CODE_OFFSET 40
參照注釋,可以知道,在ArtMethod對象中,偏移值為40的成員變量為entry_point_from_compiled_code_,而該成員變量就是調用函數LinkCode鏈接類方法時調用ArtMethod類的成員函數SetEntryPointFromCompiledCode設置的。相應的,可以通過ArtMethod類的成員函數GetEntryPointFromCompiledCode獲得該成員變量的值,如前面ArtMethod類的成員函數Invoke所示。

在ARM體系架構中,當調用blx指令跳轉到指定的地址執行行,那麼位於blx下面的一條指令會保存在寄存器lr中,而被調用類方法在執行前,一般又會通過SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME宏保存指定的寄存器。

宏SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME的定義如下所示:

    /*
     * Macro that sets up the callee save frame to conform with
     * Runtime::CreateCalleeSaveMethod(kRefsAndArgs). Restoration assumes non-moving GC.
     */
.macro SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME
    push {r1-r3, r5-r8, r10-r11, lr}  @ 10 words of callee saves
    .save {r1-r3, r5-r8, r10-r11, lr}
    .cfi_adjust_cfa_offset 40
    .cfi_rel_offset r1, 0
    .cfi_rel_offset r2, 4
    .cfi_rel_offset r3, 8
    .cfi_rel_offset r5, 12
    .cfi_rel_offset r6, 16
    .cfi_rel_offset r7, 20
    .cfi_rel_offset r8, 24
    .cfi_rel_offset r10, 28
    .cfi_rel_offset r11, 32
    .cfi_rel_offset lr, 36
    sub sp, #8                        @ 2 words of space, bottom word will hold Method*
    .pad #8
    .cfi_adjust_cfa_offset 8
.endm
這個宏定義在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。

寄存器r1-r3、r5-r8、r10-r11和lr在棧上依次從低地址往高地址保存,即從棧頂開始保存。除了保存上述寄存器之外,還會在棧上預留兩個位置(每個位置等於一個字長,即4個字節),其中,棧頂第一個位置用來保存用來描述當前被調用的類方法的ArtMethod對象地址,第二個位置可能是設計用來保存寄存器r0的,但是目前還沒有發現這樣使用的地方。宏SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME的使用情景之一可能參考我們前面分析的函數art_quick_resolution_trampoline。

現在還有一個問題是,上面提到的棧頂第一位置是什麼時候會被設置為用來描述當前被調用的類方法的ArtMethod對象地址的呢?因為宏SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME只是在棧上預留這個位置,但是沒有設置這個位置的值。以我們前面分析的函數artQuickResolutionTrampoline為例,一開始這個位置被函數FinishCalleeSaveFrameSetup設置為一個類型為Runtime::kRefsAndArgs的運行時方法,這是因為這時候我們還不知道真正被調用的類方法是什麼。類型為Runtime::kRefsAndArgs的運行時方法與我們前面提到的Resolution Method的作用有點類似,雖然它們本身是一個ArtMethod對象,但是它們的真正作用是用來描述其它的ArtMethod。例如,類型為Runtime::kRefsAndArgs的運行時方法要描述的就是真正要被調用類的類方法會在棧上保存r1-r3、r5-r8、r10-r11和lr這10個寄器,並且會在棧上預留兩個額外的位置,其中的一個位置用來保存真正被調用的類方法。果然,等到函數artQuickResolutionTrampoline找到真正被調用的類方法之後,就會將相應的ArtMethod對象地址保存在棧頂上。這正是到函數artQuickResolutionTrampoline返回前所做的事情。

綜合上面的分析,我們就得到ART運行時類方法的調用約定,如下所示:

   | argN       |  |                       -------------                  
   | ...        |  |                        |
   | arg4       |  |                        |  
   | arg3 spill |  |  Caller's frame        |  art_quick_invoke_stub
   | arg2 spill |  |                        |
   | arg1 spill |  |                        |
   | Method*    | ---                       |
   | LR         |                          ------------
   | ...        |    callee saves              |    |
   | R3         |    arg3                      |    |SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME
   | R2         |    arg2                      |    |
   | R1         |    arg1                      | -------
   | R0         |                              | art_quick_resolution_trampoline
   | Method*    |  <- sp                   ------------- artQuickResolutionTrampoline
左邊顯示了一個類方法(Caller)調用另外一個類方法(Callee)時棧的內存布局情況,右邊顯示了負責安排這些內存布局每一部分的一個情景。

回到前面的函數art_quick_invoke_stub中,通過blx指令調用指定的類方法結束後,結果就保存在r0和r1兩個寄存器中,其中一個表示返回值,另外一個表示返回值類型,最後通過strd指令將這兩個寄存器的值拷貝到棧上指定的內存地址去,實際上就將調用結果返回給調用者指定的兩個變量去。

這樣,我們就分析完成類方法的執行過程了,也基本上解釋上前面分析的函數LinkCode所涉及到關鍵函數,包括artInterpreterToCompiledCodeBridge、GetCompiledCodeToInterpreterBridge/GetQuickToInterpreterBridge/art_quick_to_interpreter_bridge/artQuickToInterpreterBridge、artInterpreterToInterpreterBridge、GetResolutionTrampoline/GetQuickResolutionTrampoline/GetQuickResolutionTrampoline/art_quick_resolution_trampoline/artQuickResolutionTrampoline、ArtMethod::SetEntryPointFromCompiledCode/ArtMethod::GetEntryPointFromCompiledCode和ArtMethod::SetEntryPointFromInterpreter等。

為了完整性,接下來我們繼續分析一下與函數ArtMethod::SetEntryPointFromInterpreter相對應的函數ArtMethod::GetEntryPointFromInterpreter,以便可以看到由前者設置的函數入口點是在什麼情況下被使用的。

前面提到,ArtMethod類的成員函數SetEntryPointFromInterpreter設置的入口點是用來給解釋器調用另外一個類方法時使用的。ART運行時的解釋器主要由art/runtime/interpreter/interpreter.cc文件中,當它需要調用另外一個類方法時,就會通過函數DoInvoke來實現,如下所示:

template
static bool DoInvoke(Thread* self, ShadowFrame& shadow_frame,
                     const Instruction* inst, JValue* result) {
  ......
  uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
  uint32_t vregC = (is_range) ? inst->VRegC_3rc() : inst->VRegC_35c();
  Object* receiver = (type == kStatic) ? NULL : shadow_frame.GetVRegReference(vregC);
  ArtMethod* method = FindMethodFromCode(method_idx, receiver, shadow_frame.GetMethod(), self,
                                         do_access_check, type);
  ......

  if (LIKELY(Runtime::Current()->IsStarted())) {
    (method->GetEntryPointFromInterpreter())(self, mh, code_item, new_shadow_frame, result);
  } else {
    UnstartedRuntimeInvoke(self, mh, code_item, new_shadow_frame, result, num_regs - num_ins);
  }
  return !self->IsExceptionPending();
}
這個函數定義在art/runtime/interpreter/interpreter.cc文件中。

通過調用指令中的指定的Dex Method Index,我們可以通過另外一個函數FindMethodFromCode找到被調用的類方法,通過ArtMethod對象method來描述。有了這個ArtMethod對象對象後,我們就可以調用它的成員函數GetEntryPointFromInterpreter來獲得接下來要被調用類方法的執行入口點。從函數LinkCode的實現可以知道,通過ArtMethod類的成員函數GetEntryPointFromInterpreter獲得的類方法執行入口點有可能是用來進入解釋器的,也有可能是用來進入到類方法的本地機器指令去的。

至此,我們就分析完成ART運行時執行一個類方法的過程以及在執行過程中涉及到的各種關鍵函數了。本文與其說是分析類方法的執行過程,不如說是分析ART運行時的實現原理。理解了本文分析到的各個關鍵函數之後,相信對ART運行時就會有一個清晰的認識了。在接下來的文章中,我們將繼續發力,分析聽起來很高大上的ART運行時垃圾收集機制。敬請關注!想了解更多信息,也可以關注老羅的新浪微博:http://weibo.com/shengyangluo。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved