[原文出處-----------------Android運行時ART執行類方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/40289405)
在前面一篇文章中,我們分析了ART運行時加載類以及查找其方法的過程。一旦找到了目標類方法,我們就可以獲得它的DEX字節碼或者本地機器指令,這樣就可以對它進行執行了。在ART運行時中,類方法的執行方式有兩種。一種是像Dalvik虛擬機一樣,將其DEX字節碼交給解釋器執行;另一種則是直接將其本地機器指令交給CPU執行。在本文中,我們就將通過分析ART運行時執行類方法的過程來理解ART運行時的運行原理。
我們先來看看圖1,它描述了ART運行時執行一個類方法的流程,如下所示:

圖1 ART運行時執行類方法的過程
圖1綜合了我們在前面[Android運行時ART加載OAT文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)和[Android運行時ART加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)這兩篇文章中提到的兩個知識點,我們先來回顧一下。
第一個知識點是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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)這篇文章,宏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> 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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文,這里只對結論進行進一步的解釋。從注釋可以知道,通過Portable后端和Quick后端生成的OAT文件的本質區別在于,前者使用標準的動態鏈接器加載,而后者使用自定義的加載器加載。
標準動態鏈接器在加載SO文件(這里是OAT文件)的時候,會自動處理重定位問題。也就是說,在生成的本地機器指令中,如果有依賴其它的SO導出的函數,那么標準動態鏈接器就會將被依賴的SO也加載進來,并且從里面找到被引用的函數的地址,用來重定位引用了該函數的符號。生成的本地機器指令引用的一般都是些SO呢?其實就是ART運行時庫(libart.so)。例如,如果在生成的本地機器指令需要分配一個對象,那么就需要調用ART運行時的堆管理器提供的AllocObject接口來分配。
自定義加載器的做法就不一樣了。它在加載OAT文件時,并不需要做上述的重定位操作。因為Quick后端生成的本地機器指令需要調用一些外部庫提供的函數時,是通過一個函數跳轉表來實現的。由于在加載過程中不需要執行重定位,因此加載過程就會更快,Quick的名字就是這樣得來的。Portable后端生成的本地機器指令在調用外部庫提供的函數時,使用了標準的方法,因此它不但可以在ART運行時加載,也可以在其它運行時加載,因此就得名于Portable。
接下來我們的重點是分析Quick后端生成的本地機器指令在調用外部庫函數時所使用的函數跳轉表是如何定義的。
在前面分析Dalvik虛擬機的文章[Dalvik虛擬機進程和線程的創建過程分析](http://blog.csdn.net/luoshengyang/article/details/8923484)中,我們提到每一個Dalvik虛擬機線程在內部都通過一個Thread對象描述。這個Thread對象包含了一些與虛擬機相關的信息。例如,JNI函數調用函數表。在ART運行時中創建的線程,和Davik虛擬機線程一樣,在內部也會通過一個Thread對象來描述。這個新的Thread對象內部除了定義JNI調用函數表之外,還定義了我們在上面提到的外部函數調用跳轉表。
在前面[Android運行時ART加載OAT文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文,我們提到了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來初始化上述的4個跳轉表。全局函數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加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)這篇文章中,我們提到,在類的加載過程中,需要對類的各個方法進行鏈接,實際上就是確定它們是通過解釋器來執行,還是以本地機器指令來直接執行,如下所示:
~~~
static void LinkCode(SirtRef<mirror::ArtMethod>& 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加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)一文,這里我們只對結論進行總結,以及對結論進行進一步的分析:
1. ART運行時有兩種執行方法:解釋執行模式和本地機器指令執行模式。默認是本地機器指令執行模式,但是在啟動ART運行時時可以通過-Xint選項指定為解釋執行模式。
2. 即使是在本地機器指令模式中,也有類方法可能需要以解釋模式執行。反之亦然。解釋執行的類方法通過函數artInterpreterToCompiledCodeBridge的返回值調用本地機器指令執行的類方法;本地機器指令執行的類方法通過函數GetCompiledCodeToInterpreterBridge的返回值調用解釋執行的類方法;解釋執行的類方法通過函數artInterpreterToInterpreterBridge的返回值解釋執行的類方法。
3. 在解釋執行模式下,除了JNI方法和動態Proxy方法,其余所有的方法均通過解釋器執行,它們的入口點設置為函數GetCompiledCodeToInterpreterBridge的返回值。
4. 抽象方法不能執行,它必須要由子類實現,因此會將抽象方法的入口點設置為函數GetCompiledCodeToInterpreterBridge的返回值,目的檢測是否在本地機器指令中調用了抽象方法。如果調用了,上述入口點就會拋出一個異常。
5. 靜態類方法的執行模式延遲至類初始化確定。在類初始化之前,它們的入口點由函數GetResolutionTrampoline的返回值代理。
接下來,我們就著重分析artInterpreterToCompiledCodeBridge、GetCompiledCodeToInterpreterBridge、artInterpreterToInterpreterBridge和GetResolutionTrampoline這4個函數以及它們所返回的函數的實現,以便可以更好地理解上述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<void*>(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虛擬機的運行過程分析](http://blog.csdn.net/luoshengyang/article/details/8914953)這篇文章描述的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的實現就可以看出,雖然都是要跳入到解釋器去執行一個被調用類方法,但是兩者的實現是不一樣的。前者由于調用方法本來就是在解釋器中執行的,因此,調用被調用類方法所需要的解釋器棧幀實際上已經準備就緒,并且被調用方法的DEX字節碼也已經知曉,因此這時候就可以直接調用另外一個函數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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)這篇文章可以知道,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加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)這篇文章可以知道,當一個類被加載時,除了它的靜態成員函數,其余所有的成員函數均已加載完畢。這意味著我們可以直接調用ArtMethod類的成員函數GetEntryPointFromCompiledCode來獲得被調用方法called的本地機器指令或者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<ArtMethod>* 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<mirror::ClassLoader*>(javaLoader);
mirror::Class* result = class_linker->DefineClass(descriptor.c_str(), class_loader, *dex_file,
*dex_class_def);
......
return soa.AddLocalReference<jclass>(result);
}
~~~
這個函數定義在文件art/runtime/native/dalvik_system_DexFile.cc中。
參數cookie指向之前已經打開了的DEX文件,因此這里首先將它轉換為一個DexFile指針dex_file。這個DEX文件是包含在OAT文件里面的,它們的打開過程可以參考[Android運行時ART加載OAT文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文。得到了之前打開的DEX文件之后,接下來就調用ClassLinker類的成員函數RegisterDexFile將它注冊到ART運行時中去,以便以后可以查詢使用。最后再通過ClassLinker類的成員函數DefineClass來加載參數javaName指定的類。
類的加載過程可以參考前面[Android運行時ART加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)一文,接下來我們主要關注ClassLinker類的成員函數RegisterDexFile的實現,如下所示:
~~~
void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file, SirtRef<mirror::DexCache>& 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<mirror::DexCache> 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<mirror::DexCache> dex_cache(self,
down_cast<mirror::DexCache*>(heap->AllocObject(self, dex_cache_class,
dex_cache_class->GetObjectSize())));
......
SirtRef<mirror::String>
location(self, intern_table_->InternStrong(dex_file.GetLocation().c_str()));
......
SirtRef<mirror::ObjectArray<mirror::String> >
strings(self, AllocStringArray(self, dex_file.NumStringIds()));
......
SirtRef<mirror::ObjectArray<mirror::Class> >
types(self, AllocClassArray(self, dex_file.NumTypeIds()));
......
SirtRef<mirror::ObjectArray<mirror::ArtMethod> >
methods(self, AllocArtMethodArray(self, dex_file.NumMethodIds()));
......
SirtRef<mirror::ObjectArray<mirror::ArtField> >
fields(self, AllocArtFieldArray(self, dex_file.NumFieldIds()));
......
SirtRef<mirror::ObjectArray<mirror::StaticStorageBase> >
initialized_static_storage(self,
AllocObjectArray<mirror::StaticStorageBase>(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)、域(Field)、字符串(String)和靜態儲存區(Static Storage)等信息。因此,我們需要為上述提到的信息分配儲存空間。例如,對于類方法來說,我們需要創建一個ArtMethod對象指針數組,其中每一個ArtMethod對象都用來描述DEX文件里面的一個類方法。有了這些儲存空間之后,最后就可以調用DexCache類的成員函數Init對剛才創建的Dex Cache進行初始化了。
DexCache類的成員函數Init的實現如下所示:
~~~
void DexCache::Init(const DexFile* dex_file,
String* location,
ObjectArray<String>* strings,
ObjectArray<Class>* resolved_types,
ObjectArray<ArtMethod>* resolved_methods,
ObjectArray<ArtField>* resolved_fields,
ObjectArray<StaticStorageBase>* 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<ArtMethod>的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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一篇文章中,我們提到,ART運行時的Image空間創建完成之后,會調用ImageSpace類的成員函數Init來對它進行初始化。
ImageSpace類的成員函數Init的實現如下所示:
~~~
ImageSpace* ImageSpace::Init(const std::string& image_file_name, bool validate_oat_file) {
......
UniquePtr<File> file(OS::OpenFileForReading(image_file_name.c_str()));
......
ImageHeader image_header;
bool success = file->ReadFully(&image_header, sizeof(image_header));
......
UniquePtr<MemMap> 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<mirror::ArtMethod*>(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<mirror::ArtMethod>
method(self, down_cast<mirror::ArtMethod*>(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運行時的Resolution 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加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)一文,前一個索引用來查找一個類方法的本地機器指令。而后面一個索引,自然的就是用來DEX文件中找到對應的類方法描述信息了。這意味著一旦知道一個類方法在DEX文件的索引,那么就可以在對應的DEX文件中對該類方法進行解析了。一旦解析完成,自然就可以知道接下來要被調用的類方法是什么了。
參數referrer指向的ArtMethod對象描述的是調用者(類方法)。每一個類方法都關聯有一個ArtMethod對象指針數組,這個ArtMethod對象指針數組實際上就是我們在前面提到的Dex Cache中的ArtMethod對象指針數組。同時,每一個類對象(Class)也關聯有一個Dex Cache。這個Dex Cache實際上就是與包含該類的DEX文件相關聯的Dex Cache。為了搞清楚上述關系,我們回顧一下前面[Android運行時ART加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)一文提到的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<mirror::Class> 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<mirror::Class>& 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加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)一文中,我們通過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加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503)一文的分析可以知道,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<Object*>(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<int32_t>(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<InvokeType type, bool is_range, bool do_access_check>
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運行時垃圾收集機制。
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析