[Dalvik虛擬機的運行過程分析](http://blog.csdn.net/luoshengyang/article/details/8914953)
在前面一篇文章中,我們分析了Dalvik虛擬機在Zygote進程中的啟動過程。Dalvik虛擬機啟動完成之后,也就是在各個子模塊初始化完成以及加載了相應的Java核心類庫之后,就是可以執行Java代碼了。當然,Dalvik虛擬機除了可以執行Java代碼之外,還可以執行Native代碼,也就是C和C++代碼。在本文中,我們就將繼續以Zygote進程的啟動過程為例,來分析Dalvik虛擬機的運行過程。
從前面Dalvik虛擬機的啟動過程分析一文可以知道,Dalvik虛擬機在Zygote進程中啟動完成之后,就會獲得一個JavaVM實例和一個JNIEnv實例。其中,獲得的JavaVM實例就是用來描述Zygote進程的Dalvik虛擬機實例,而獲得的JNIEnv實例描述的是Zygote進程的主線程的JNI環境。緊接著,Zygote進程就會通過前面獲得的JNIEnv實例的成員函數CallStaticVoidMethod來調用com.android.internal.os.ZygoteInit類的靜態成員函數main。這就相當于是將com.android.internal.os.ZygoteInit類的靜態成員函數main作為Java代碼的入口點。
接下來,我們就從JNIEnv類的成員函數CallStaticVoidMethod開始,分析Dalvik虛擬機的運行過程,如圖1所示:

:-: 圖1 Dalvik虛擬機的運行過程
這個過程可以分為9個步驟,接下來我們就詳細分析每一個步驟。
**Step 1. JNIEnv.CallStaticVoidMethod**
~~~
struct _JNIEnv;
......
typedef _JNIEnv JNIEnv;
......
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
......
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
{
va_list args;
va_start(args, methodID);
functions->CallStaticVoidMethodV(this, clazz, methodID, args);
va_end(args);
}
......
};
~~~
這個函數定義在文件**dalvik/libnativehelper/include/nativehelper/jni.h**中。
JNIEnv實際上是一個結構,它有一個成員變量functions,指向的是一個回調函數表。這個回調函數表使用一個JNINativeInterface對象來描述。JNIEnv結構體的成員函數CallStaticVoidMethod的實現很簡單,它只是調用該回調函數表中的CallStaticVoidMethodV函數來執行參數clazz和methodID所描述的Java代碼。
**Step 2. JNINativeInterface.CallStaticVoidMethodV**
~~~
struct JNINativeInterface {
......
void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);
......
};
~~~
這個函數定義在文件dalvik/libnativehelper/include/nativehelper/jni.h中。
JNINativeInterface是一個結構體,它的成員變量CallStaticVoidMethodV是一個函數指針。
從前面[Dalvik虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8885792)一文可以知道,Dalvik虛擬機在內部為Zygote進程的主線程所創建的Java環境是用一個JNIEnvExt結構體來描述的,并且這個JNIEnvExt結構體會被強制轉換成一個JNIEnv結構體返回給Zygote進程。
JNIEnvExt結構體定義在文件dalvik/vm/JniInternal.h中,如下所示:
~~~
typedef struct JNIEnvExt {
const struct JNINativeInterface* funcTable; /* must be first */
......
} JNIEnvExt;
~~~
從這里就可以看出,雖然結構體JNIEnvExt和JNIEnv之間沒有繼承關系,但是它們的第一個成員變量的類型是一致的,也就是它們都是指向一個類型為JNINativeInterface的回調函數表,因此,Dalvik虛擬機可以將一個JNIEnvExt結構體強制轉換成一個JNIEnv結構體返回給Zygote進程,這時候我們通過JNIEnv結構體來訪問其成員變量functions所描述的回調函數表時,實際訪問到的是對應的JNIEnvExt結構體的成員變量funcTable所描述的回調函數表。
為什么不直接讓JNIEnvExt結構體從JNIEnv結構體繼承下來呢?這樣把一個JNIEnvExt結構體轉換為一個JNIEnv結構體就是相當直觀的。然而,Dalvik虛擬機的源代碼并一定是要以C++語言的形式來編譯的,它也可以以C語言的形式來編譯的。由于C語言沒有繼承的概念,因此,為了使得Dalvik虛擬機的源代碼能同時兼容C++和C,這里就使用了一個Trick:只要兩個結構體的內存布局相同,它們就可以相互轉換訪問。當然,這并不要求兩個結構體的內存布局完全相同,但是至少開始部分要求是相同的。在這種情況下,將一個結構體強制轉換成另外一個結構體之外,只要不去訪問內存布局不一致的地方,就沒有問題。在Android系統的Native代碼中,我們可以常常看到這種Trick。
接下來,我們需要搞清楚的是JNIEnvExt結構體的成員變量funcTable指向的回調函數表是什么。同樣是從前面[Dalvik虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8885792)一文可以知道,Dalvik虛擬機在創建一個JNIEnvExt結構體的時候,會將它的成員變量funcTable指向全局變量gNativeInterface所描述的一個回調函數表。
gNativeInterface定義在文件dalvik/vm/Jni.c中,如下所示:
~~~
static const struct JNINativeInterface gNativeInterface = {
......
CallStaticVoidMethodV,
......
};
~~~
在這個回調函數表中,名稱為CallStaticVoidMethodV的函數指針指向的是一個同名函數CallStaticVoidMethodV。
函數CallStaticVoidMethodV同樣是定義在文件dalvik/vm/Jni.c中,不過它是通過宏CALL_STATIC來定義的,如下所示:
~~~
#define CALL_STATIC(_ctype, _jname, _retfail, _retok, _isref) \
...... \
static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass jclazz, \
jmethodID methodID, va_list args) \
{ \
UNUSED_PARAMETER(jclazz); \
JNI_ENTER(); \
JValue result; \
dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);\
if (_isref && !dvmCheckException(_self)) \
result.l = addLocalReference(env, result.l); \
JNI_EXIT(); \
return _retok; \
}
...... \
CALL_STATIC(void, Void, , , false);
~~~
通過上面的分析就可以知道,在JNIEnvExt結構體的成員變量funcTable所描述的回調函數表中,名稱為CallStaticVoidMethodV的函數指針指向的是一個同名函數CallStaticVoidMethodV。這就是說,我們通過JNIEnv結構體的成員變量functions所訪問到的名稱為CallStaticVoidMethodV函數指針實際指向的是函數CallStaticVoidMethodV。
**Step 3. CallStaticVoidMethodV**
我們將上面的CALL_STATIC宏開之后,就可以得到函數CallStaticVoidMethodV的實現,如下所示:
~~~
static _ctype CallStaticVoidMethodV(JNIEnv* env, jclass jclazz,
jmethodID methodID, va_list args)
{
UNUSED_PARAMETER(jclazz);
JNI_ENTER();
JValue result;
dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);
if (_isref && !dvmCheckException(_self))
result.l = addLocalReference(env, result.l);
JNI_EXIT();
return _retok;
}
~~~
函數CallStaticVoidMethodV的實現很簡單,它通過調用另外一個函數dvmCallMethodV來執行由參數jclazz和methodID所描述的Java代碼,因此,接下來我們就繼續分析函數dvmCallMethodV的實現。
**Step 4. dvmCallMethodV**
~~~
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
......
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)(self->curFrame, pResult, method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}
......
}
~~~
這個函數定義在文件dalvik/vm/interp/Stack.c中。
函數dvmCallMethodV首先檢查參數method描述的函數是否是一個JNI方法。如果是的話,那么它所指向的一個Method對象的成員變量nativeFunc就指向該JNI方法的地址,因此就可以直接對它進行調用。否則的話,就說明參數method描述的是一個Java函數,這時候就需要繼續調用函數dvmInterpret來執行它的代碼。
**Step 5. dvmInterpret**
~~~
void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
{
InterpState interpState;
......
/*
* Initialize working state.
*
* No need to initialize "retval".
*/
interpState.method = method;
interpState.fp = (u4*) self->curFrame;
interpState.pc = method->insns;
......
typedef bool (*Interpreter)(Thread*, InterpState*);
Interpreter stdInterp;
if (gDvm.executionMode == kExecutionModeInterpFast)
stdInterp = dvmMterpStd;
#if defined(WITH_JIT)
else if (gDvm.executionMode == kExecutionModeJit)
/* If profiling overhead can be kept low enough, we can use a profiling
* mterp fast for both Jit and "fast" modes. If overhead is too high,
* create a specialized profiling interpreter.
*/
stdInterp = dvmMterpStd;
#endif
else
stdInterp = dvmInterpretStd;
change = true;
while (change) {
switch (interpState.nextMode) {
case INTERP_STD:
LOGVV("threadid=%d: interp STD\n", self->threadId);
change = (*stdInterp)(self, &interpState);
break;
case INTERP_DBG:
LOGVV("threadid=%d: interp DBG\n", self->threadId);
change = dvmInterpretDbg(self, &interpState);
break;
default:
dvmAbort();
}
}
*pResult = interpState.retval;
......
}
~~~
這個函數定義在文件dalvik/vm/interp/Interp.c中。
在前面[Dalvik虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8885792)一文中提到,Dalvik虛擬機支持三種執行模式:portable、fast和jit,它們分別使用kExecutionModeInterpPortable、kExecutionModeInterpFast和kExecutionModeJit三個常量來表示。Dalvik虛擬機在啟動的時候,會通過解析命令行參數獲得所要執行的模式,并且記錄在全局變量gDvm所指向的一個DvmGlobals結構體的成員變量executionMode中。
kExecutionModeInterpPortable表示Dalvik虛擬機以可移植模式來解釋Java代碼,也就是這種執行模式可以應用在任何一個平臺上,例如,可以同時在arm和x86各個平臺上執行。這時候使用函數dvmInterpretStd來作為Java代碼的執行函數。
kExecutionModeInterpFast表示Dalvik虛擬機以快速模式來解釋Java代碼。以這種模式執行的Dalvik虛擬機是針對某一個特定的目標平臺進行過優化的,因此,它可以更快速地對Java代碼進行解釋以及執行。這時候使用函數dvmMterpStd來作為Java代碼的執行函數。
kExecutionModeJit表示Dalvik虛擬機支持JIT模式來執行Java代碼,也就是先將Java代碼動態編譯成Native代碼再執行。這時候使用函數dvmMterpStd來作為Java代碼的執行函數。
我們可以將函數dvmInterpretStd和dvmMterpStd理解為Dalvik虛擬機的解釋器入口點。很顯然,解釋器是虛擬機的核心模塊,它的性能關乎到整個虛擬機的性能。Dalvik虛擬機的解釋器開始的時候都是以C語言來實現的,后來為了提到性能,就改成以匯編語言來實現。注意,無論Dalvik虛擬機的解釋器是以C語言來實現,還是以匯編語言來實現,它們的源代碼都是以一種模塊化方法來自動生成的,并且能夠根據某一個特定的平臺進行優化。
所謂模塊化代碼生成方法,就是說將解釋器的實現劃分成若干個模塊,每一個模塊都對應有一系列的輸入文件(本身也是源代碼文件),最后通過工具(一個Python腳本)將這些輸入文件組裝起來形成一個C語言文件或者匯編語言文件。這個最終得到的C語言文件或者匯編語言文件就是Dalvik虛擬機的解釋器的實現文件。有了這種模塊化代碼生成方法之后,為某一個特定的平臺生成優化過的解釋器就是相當容易的:我們只需要為該平臺的Dalvik虛擬機解釋器的相關模塊提供一個特殊版本的輸入文件即可。也就是說,我們需要為每一個支持的平臺提供一個配置文件,該配置文件描述了該平臺的Dalvik虛擬機解釋器的各個模塊所要使用的輸入文件。這種模塊化代碼生成方法不僅能避免手動編寫解釋器容易出錯的問題,還能方便快速地將Dalvik虛擬機從一個平臺移植到另外一個平臺。
采取了模塊化方法來生成Dalvik虛擬機解釋器的源代碼之后,Dalvik虛擬機解釋器的源代碼整體上就劃分成兩部分:第一部分相當于一個Wrapper,定義在dalvik/vm/interp目錄中;第二部分對應于具體的實現,定義在dalvik/vm/mterp目錄中。
在dalvik/vm/mterp目錄中,又定義了一系列的輸入文件,以及各個平臺所使用的配置文件。有了這些輸入文件和配置文件之后,就可以通過一個Python腳本來為每一個平臺生成一個Dalvik虛擬機解釋器的輸出文件,并且保存在dalvik/vm/mterp/out目錄中。由于針對各個平臺生成的輸出文件是一個匯編語言文件,即一個 *.S文件,為了方便理解它的邏輯,每一個匯編語言文件都對應有一個C語言文件。
雖然Dalvik虛擬機解釋器針對每一個平臺都有一個優化的版本,但是同時也會提供一個通用的版本,也就是一個可移植版本。該可移植版本的解釋器源文件只有C語言實現版本,定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中。在本文中,我們只考慮可移植版本的Dalvik虛擬機解釋器的實現,也就是只考慮執行模式為kExecutionModeInterpPortable的Dalvik虛擬機的運行機制。從前面的分析可以知道,該可移植版本的Dalvik虛擬機解釋器的入口函數為dvmInterpretStd。
函數dvmInterpretStd在執行之前,需要知道解釋器的當前狀態,也就是它所要執行的Java函數及其指令入口,以及當前要執行的線程的堆棧。這些信息都用一個InterpState結構體來描述。其中,這個InterpState結構體的成員變量method描述的是要執行的Java函數,成員變量fp描述的是要執行的線程的當前堆棧幀,成員變量pc描述的是要執行的Java函數的入口點。
在函數dvmInterpret中,參數self描述的是當前用來執行Java代碼的線程,而參數method描述的是要執行的Java函數。通過這兩個參數我們就可以初始化上述的InterpState結構體。Dalvik虛擬機解釋器除了可以在正常模式執行之外,還可以在調試模式執行,即決于上述初始化后得到的InterpState結構體的成員變量nextMode的值。
在本文中,我們只考慮正常模式執行的Dalvik虛擬機解釋器的實現,也就是我們只分析函數dvmInterpretStd的實現。函數dvmInterpretStd解釋完成指定的Java函數之后,獲得的返回值就保存在上述InterpState結構體的成員變量retval中。
**Step 6. dvmInterpretStd**
~~~
#define INTERP_FUNC_NAME dvmInterpretStd
......
bool INTERP_FUNC_NAME(Thread* self, InterpState* interpState)
{
......
DvmDex* methodClassDex; // curMethod->clazz->pDvmDex
JValue retval;
/* core state */
const Method* curMethod; // method we're interpreting
const u2* pc; // program counter
u4* fp; // frame pointer
u2 inst; // current instruction
......
/* copy state in */
curMethod = interpState->method;
pc = interpState->pc;
fp = interpState->fp;
retval = interpState->retval; /* only need for kInterpEntryReturn? */
methodClassDex = curMethod->clazz->pDvmDex;
......
while (1) {
......
/* fetch the next 16 bits from the instruction stream */
inst = FETCH(0);
switch (INST_INST(inst)) {
......
HANDLE_OPCODE(OP_INVOKE_DIRECT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
GOTO_invoke(invokeDirect, false);
OP_END
......
HANDLE_OPCODE(OP_RETURN /*vAA*/)
vsrc1 = INST_AA(inst);
......
retval.i = GET_REGISTER(vsrc1);
GOTO_returnFromMethod();
OP_END
......
}
}
......
/* export state changes */
interpState->method = curMethod;
interpState->pc = pc;
interpState->fp = fp;
/* debugTrackedRefStart doesn't change */
interpState->retval = retval; /* need for _entryPoint=ret */
interpState->nextMode =
(INTERP_TYPE == INTERP_STD) ? INTERP_DBG : INTERP_STD;
......
return true;
}
~~~
這個函數定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
函數dvmInterpretStd對應的是Dalvik虛擬機解釋器的可移植版本實現,它大概可以劃分三個邏輯塊:
1. 初始化當前要解釋的類(methodClassDex)及其成員變量函數(curMethod)、棧幀(fp)、程序計數器(pc)和返回值(retval),這些值都可以從參數interpState獲得。
2. 在一個無限while循環中,通過FETCH宏依次獲得當前程序計數器(pc)中的指令inst,并且通過宏INST_INST獲得指令inst的類型,最后就switch到對應的分支去解釋指令inst。
宏FETCH和INST_INST的定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
~~~
/*
* Get 16 bits from the specified offset of the program counter. We always
* want to load 16 bits at a time from the instruction stream -- it's more
* efficient than 8 and won't have the alignment problems that 32 might.
*
* Assumes existence of "const u2* pc".
*/
#define FETCH(_offset) (pc[(_offset)])
/*
* Extract instruction byte from 16-bit fetch (_inst is a u2).
*/
#define INST_INST(_inst) ((_inst) & 0xff)
~~~
從這里我們就可以看出,pc實際上指向的就是當前要執行的Java函數的方法區,也就是一個指令流。這個指令流包含了很多指令,需要通過一個while循環來依次對它們進行解釋,直到碰到一個return指令為止。這就是Dalvik虛擬機解釋器的核心功能。例如,假設當前遇到的是一條OP_INVOKE_DIRECT指令,它表示要調用當前類的一個非靜態非虛成員函數,這時候就會通過宏GOTO_invoke跳到invokeDirect這個分支去。
宏HANDLE_OPCODE和GOTO_invoke定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
~~~
# define HANDLE_OPCODE(_op) case _op:
#define GOTO_invoke(_target, _methodCallRange) \
do { \
methodCallRange = _methodCallRange; \
goto _target; \
} while(false)
~~~
分支invokeDirect是通過另外一個宏GOTO_TARGET來定義的,在接下來的Step 7中我們再分析。
當遇到return指令時,例如,遇到OP_RETURN指令時,首先會通過宏INST_AA和GET_REGISTER來獲得函數的返回值,接著再通過宏GOTO_returnFromMethod跳到returnFromMethod分支去結束整個while循環。
注意,當指令inst是return指令時,它的執行結果即為當要正在解釋的Java函數返回值。通過宏INST_AA可以知道一條指令的執行結果保存在哪個寄存器中,而通過宏GET_REGISTER可以獲得該寄存器的值。
宏INST_AA和GET_REGISTER定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
~~~
/*
* Get the 8-bit "vAA" 8-bit register index from the instruction word.
* (_inst is u2)
*/
#define INST_AA(_inst) ((_inst) >> 8)
# define GET_REGISTER(_idx) \
( (_idx) < curMethod->registersSize ? \
(fp[(_idx)]) : (assert(!"bad reg"),1969) )
~~~
宏GOTO_returnFromMethod定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
~~~
#define GOTO_returnFromMethod() goto returnFromMethod;
~~~
分支returnFromMethod和invokeDirect一樣,都是通過宏GOTO_TARGET來定義的,在接下來的Step 9中我們再分析。
接下來,我們就以分支invokeDirect為例來說明Dalvik虛擬機解釋一條指令的過程,接著再以分支returnFromMethod的實現來說明Dalvik虛擬機解釋器從一個函數返回的過程。
**Step 7. invokeDirect**
~~~
GOTO_TARGET(invokeDirect, bool methodCallRange)
{
......
vsrc1 = INST_AA(inst); /* AA (count) or BA (count + arg 5) */
ref = FETCH(1); /* method ref */
vdst = FETCH(2); /* 4 regs -or- first reg */
EXPORT_PC();
......
methodToCall = dvmDexGetResolvedMethod(methodClassDex, ref);
......
GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst);
}
GOTO_TARGET_END
~~~
根據Step 6的分析,invokeDirect是一個分支,它通過宏GOTO_TARGET定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
分支invokeDirect用來調用當前類(methodClassDex)的非靜態非虛成員函數。這個被調用的成員函數的引用可以通過宏FETCH(1)來獲取。知道了被調用的成員函數的引用之后,就可以通過調用函數dvmDexGetResolvedMethod來獲得對應的成員函數(methodToCall)。
此外,宏FETCH(2)用來獲得要傳遞給成員函數(methodToCall)的參數列表,而宏EXPORT_PC是用來記錄當前程序計數器pc的位置的,用來幫助實現精確GC。關于精確GC(Extra/Precise GC),可以參考前面[Dalvik虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8885792)一文的介紹。
宏GOTO_invokeMethod定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
~~~
#define GOTO_invokeMethod(_methodCallRange, _methodToCall, _vsrc1, _vdst) goto invokeMethod;
~~~
它表示要跳到分支invokeMethod去解釋當前類的成員函數_methodToCall,接下來我們就繼續分析它的實現。
**Step 8. invokeMethod**
~~~
GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall,
u2 count, u2 regs)
{
STUB_HACK(vsrc1 = count; vdst = regs; methodToCall = _methodToCall;);
StackSaveArea* newSaveArea;
u4* newFp;
......
newFp = (u4*) SAVEAREA_FROM_FP(fp) - methodToCall->registersSize;
newSaveArea = SAVEAREA_FROM_FP(newFp);
......
newSaveArea->prevFrame = fp;
newSaveArea->savedPc = pc;
......
if (!dvmIsNativeMethod(methodToCall)) {
/*
* "Call" interpreted code. Reposition the PC, update the
* frame pointer and other local state, and continue.
*/
curMethod = methodToCall;
methodClassDex = curMethod->clazz->pDvmDex;
pc = methodToCall->insns;
fp = self->curFrame = newFp;
......
FINISH(0); // jump to method start
} else {
/* set this up for JNI locals, even if not a JNI native */
......
self->curFrame = newFp;
......
/*
* Jump through native call bridge. Because we leave no
* space for locals on native calls, "newFp" points directly
* to the method arguments.
*/
(*methodToCall->nativeFunc)(newFp, &retval, methodToCall, self);
......
if (true /*invokeInstr >= OP_INVOKE_VIRTUAL &&
invokeInstr <= OP_INVOKE_INTERFACE*/)
{
FINISH(3);
}
......
}
......
}
GOTO_TARGET_END
~~~
分支invokeMethod通過宏GOTO_TARGET定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
分支invokeMethod首先是為當前要解釋的成員函數methodToCall創建一個新的棧幀newFp,接著再通過調用函數dvmIsNativeMethod來判斷成員函數methodToCall是否是一個JNI方法。
在新創建的棧幀newFp中,分別在其成員變量prevFrame和savedPc中保存了當前棧幀fp和當前程序計數器pc的值,這樣使得在調用完成員函數methodToCall之后,可以返回到當前正在執行的成員函數的下一條指令中去,以及恢復當前線程的棧幀。
如果成員函數methodToCall不是一個JNI方法,那么就說明接下來仍然是要通過Dalvik虛擬機解釋器來執行它。不過這時候需要將當前線程激活的棧幀fp設置為newFp,以及將程序計數器pc指向成員函數methodToCall的方法區。最后通過宏FINISH(0)來跳出當前分支,實際上就是跳出前面Step 6的switch語句,然后重復執行while循環語句。此時傳遞給宏FINISH的參數為0,表示不需要調整程序計數器pc的值,因為前面已經調整過了。
如果成員函數methodToCall是一個JNI方法,那么該JNI方法的地址就保存在methodToCall->nativeFunc中,這時候只需要直接對它進行調用即可。調用JNI方法結束之后,需要通過宏FINISH(3)調整程序計數器pc的值,以及跳出當前分支,以及可以回到前面Step 6的while循環去執行下一條指令。此時傳遞給宏FINISH的參數為3,表示要將程序計數器pc的值增加3,正好是跳過當前執行的指令。
宏FINISH定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
~~~
# define ADJUST_PC(_offset) do { \
pc += _offset; \
EXPORT_EXTRA_PC(); \
} while (false)
#endif
# define FINISH(_offset) { ADJUST_PC(_offset); break; }
~~~
注意,在宏ADJUST_PC中,又會通過宏EXPORT_EXTRA_PC來記錄當前程序計數器pc的值,也是用來幫助實現精確GC的。
這一步執行完成之后,就回到前面的Step 6的while循環中,繼續執行下一條指令,直到遇到reutrn指令為止。接下來我們就以OP_RETURN指令為例,來說明Java函數的返回操作,也就分析分支returnFromMethod的實現。
**Step 9. returnFromMethod**
~~~
GOTO_TARGET(returnFromMethod)
{
StackSaveArea* saveArea;
......
saveArea = SAVEAREA_FROM_FP(fp);
......
fp = saveArea->prevFrame;
......
/* update thread FP, and reset local variables */
self->curFrame = fp;
curMethod = SAVEAREA_FROM_FP(fp)->method;
//methodClass = curMethod->clazz;
methodClassDex = curMethod->clazz->pDvmDex;
pc = saveArea->savedPc;
......
if (true /*invokeInstr >= OP_INVOKE_VIRTUAL &&
invokeInstr <= OP_INVOKE_INTERFACE*/)
{
FINISH(3);
}
......
}
GOTO_TARGET_END
~~~
分支returnFromMethod通過宏GOTO_TARGET定義在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
分支returnFromMethod的實現比較簡單,它主要就是恢復上一個執行的成員函數的棧幀,以及該成員函數下一條要執行的指令。由于在前面的Step 8中,我們在當前棧幀中保存了上一個執行的成員函數的下一條要執行的指令及其棧幀,因此,這里對它們進行恢復是很直覺的。
不過有一點需要注意的是,前面的Step 8保存的是當前正在執行的成員函數的程序計算器,現在由于該程序計算器所指向的指令已經執行完成了,因此,我們需要繼續調整從當前棧幀中恢復回來的指令值,使得它指向的是上一個執行的成員函數的下一條要執行的指令的值,這是通過宏FINISH(3)來完成的。
至此,我們就分析完成Dalvik虛擬機解釋器的執行過程了,這個過程也就相當于是Dalvik虛擬機的運行過程,也就是說,Step 7到Step 9實際上是會不斷地重復執行,直至進程退出為止的。以Zygote進程為例,Dalvik虛擬機解釋器就是以com.android.internal.os.ZygoteInit類的靜態成員函數main為入口點執行,然后在一個Socket上進行循環,用來等待和處理ActivityManagerService服務向它發送創建新應用程序進程的請求,直至系統退出為止。又以Android應用程序進程為例,Dalvik虛擬機解釋器就是以android.app.ActivityThread類的靜態成員函數main為入口點執行,然后在一消息隊列上進行循環,用來等待和處理主線程的消息,直到應用程序退出為止。
當然,Dalvik虛擬機在運行的過程中,除了解釋執行之外,還可能會進行JIT。JIT的目的就是將Java代碼即時編譯成Native代碼之后再直接執行,這樣對于經常運行的代碼來說,可以提高性能。由于在將Java代碼即時編譯成Native代碼的過程中,可以進一步利用運行時信息來進行激進優化,因此,JIT獲得的Native代碼比AOT獲得的Native可能會更優化。關于JIT的實現,可以參考Hello, JIT World: The Joy of Simple JITs一文。
此外,從Step 4和Step 8可以知道,Dalvik虛擬機在運行的過程中,除了需要執行Java函數之外,還可能需要執行Native函數,這些Native函數也就是我們平時所說的JNI方法。在接下來的一篇文章中,我們就將分析這些JNI方法注冊到Dalvik虛擬機里面去的,敬請關注!
- 前言
- 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)的過程分析