android虛擬機器詳解
阿新 • • 發佈:2018-12-31
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server @system/core/rootdir/init.rc
/system/bin/app_process
|-->main(): @frameworks/base/cmds/app_process/app_main.cpp
虛擬機器啟動入口函式
if (strcmp(arg, "--zygote") == 0) zygote = true;
if (strcmp(arg, "--start-system-server") == 0) startSystemServer = true;
if (zygote) { runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");
|--> AndroidRuntime::start(const char* className, const char* options) 配置環境(ANDROID_ROOT)目錄變數等 @frameworks/base/core/jni/AndroidRuntime.cpp
|--> AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
| |--> JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) @dalvik/vm/Jni.cpp
| |--> dvmStartup(int argc, const char* const argv[],bool ignoreUnrecognized, JNIEnv* pEnv) @dalvik/vm/Init.cpp
| |-->初始化組建
| |--> dvmCheckAsmConstants() 明確mterp直譯器要用到的一些變數正確
| |--> dvmAllocTrackerStartup() 函式初始化跟蹤顯示系統,跟蹤系統主要用生成除錯系統的資料包
| |--> dvmGcStartup() 初始化垃圾回收器//初始化heap
| |--> dvmThreadStartup() 初始化執行緒列表和主執行緒環境引數
| |--> dvmInlineNativeStartup() 分配內部操作方法的表格記憶體
| |--> dvmRegisterMapStartup() 分配指令暫存器狀態的記憶體
| |--> dvmInstanceofStartup() 分配虛擬機器使用的快取
| |--> dvmClassStartup() 初始化虛擬機器最基本用的JAVA庫
| |--> dvmFindRequiredClassesAndMembers()類查詢相關的初始化
| |--> dvmStringInternStartup() 初始化虛擬機器直譯器使用的字串雜湊表
| |--> dvmNativeStartup() 初始化本地方法庫的表
| |--> dvmInternalNativeStartup() 初始化內部本地方法,建立雜湊表,方便快速查詢到
| |--> dvmJniStartup() 初始化JNI呼叫表,以便快速找到本地方法呼叫的入口
| |--> dvmProfilingStartup()
| |--> dvmCreateInlineSubsTable()建立一個方法表代替inline方法表
| |--> dvmValidateBoxClasses() 始化JAVA基本型別庫
| |--> dvmPrepMainForJni(pEnv) 準備主執行緒裡的解釋棧可以呼叫JNI的方法
| |--> dvmInitClass(gDvm.classJavaLangClass)初始化java.lang.Class
| |--> registerSystemNatives(pEnv)註冊JAVA庫裡的JNI方法
| |--> dvmCreateStockExceptions()分配異常出錯的記憶體
| |--> dvmPrepMainThread()直譯器主執行緒的初始化
| |--> dvmDebuggerStartup()偵錯程式初始化
| |--> dvmGcStartupClasses()
| |--> dvmInitAfterZygote() 此函式執行完虛擬機器程序建立就完成了.
| |--> dvmGcStartupAfterZygote() 進行一次GC
| |--> dvmSignalCatcherStartup() 啟動一個Linux訊號收集執行緒,主要是用來捕捉SIGQUIT訊號,以便可以在程序退出前將各個執行緒的堆疊DUMP出來
| |--> dvmStdioConverterStartup() 啟動一個標準輸出重定向執行緒,該執行緒負責將當前程序的標準輸出(stdout和stderr)重定向到日誌輸出系統中去
| |--> dvmInitJDWP() 啟動一個JDWP執行緒,以便我們可以用DDMS工具來除錯程序中的Dalvik虛擬機器
| |--> dvmCompilerStartup() 啟動JIT,前提是當前使用的Dalvik虛擬機器在編譯時支援JIT,並且該Dalvik虛擬機器在啟動時指定了-Xint:jit選項
| |--> dvmCreateInternalThread(&gDvmJit.compilerHandle, "Compiler",compilerThreadStart, NULL);
| |--> *compilerThreadStart(void *arg) 啟動執行緒初始化dalvik的編譯器,在虛擬機器支援JIT的前提下.
| |--> compilerThreadStartup(void)在虛擬機器執行過程中一直生存的執行緒,while迴圈判斷是否有程式碼需要編譯,如果有,則呼叫dvmCompilerDoWork()對其進行編譯
|--> startReg(env) //註冊android native函式
|--> jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");
|--> env->CallStaticVoidMethod(startClass, startMeth, strArray); step1: JNIEnv.CallStaticVoidMethod setp2: JNINativeInterface.CallStaticVoidMethodV
|--> functions->CallStaticVoidMethodV(this, clazz, methodID, args);
|--> dvmInterpret(Thread* , const Method*, JValue* ) (Main interpreter loop entry point.將上一個啟用狀態儲存到當前直譯器,
初始化工作狀態(比如,執行的method賦值),選擇直譯器執行模式)
if |--> dvmMterpStd :此函式為指標函式函式名,為直譯器彙編實現的入口
else|--> dvmInterpretPortable :此函式為可移植C的直譯器實現入口
# typedef void (*Interpreter)(Thread*);
# Interpreter stdInterp;
# if (gDvm.executionMode == kExecutionModeInterpFast)
# stdInterp = dvmMterpStd;
# #if defined(WITH_JIT)
# else if (gDvm.executionMode == kExecutionModeJit)
# stdInterp = dvmMterpStd;
# #endif
# else
# stdInterp = dvmInterpretPortable;
# // Call the interpreter
# (*stdInterp)(self);
分支到dvmInterpretPortable,比較容易看懂:
dvmInterpretPortable(Thread* self) @vm/mterp/out/InterpC-portable.cpp
void dvmInterpretPortable(Thread* self)
{
#if defined(EASY_GDB)
StackSaveArea* debugSaveArea = SAVEAREA_FROM_FP(self->interpSave.curFrame);
#endif
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
/* instruction decoding */
u4 ref; // 16 or 32-bit quantity fetched directly
u2 vsrc1, vsrc2, vdst; // usually used for register indexes
/* method call setup */
const Method* methodToCall;
bool methodCallRange;
bool jumboFormat;
/* static computed goto table */
DEFINE_GOTO_TABLE(handlerTable);
/* copy state in */
curMethod = self->interpSave.method;
pc = self->interpSave.pc;
fp = self->interpSave.curFrame;
retval = self->interpSave.retval; /* only need for kInterpEntryReturn? */
methodClassDex = curMethod->clazz->pDvmDex;
LOGVV("threadid=%d: %s.%s pc=%#x fp=%p",
self->threadId, curMethod->clazz->descriptor, curMethod->name,
pc - curMethod->insns, fp);
/*
* Handle any ongoing profiling and prep for debugging.
*/
if (self->interpBreak.ctl.subMode != 0) {
TRACE_METHOD_ENTER(self, curMethod);
self->debugIsMethodEntry = true; // Always true on startup
}
/*
* DEBUG: scramble this to ensure we're not relying on it.
*/
methodToCall = (const Method*) -1;
#if 0
if (self->debugIsMethodEntry) {
ILOGD("|-- Now interpreting %s.%s", curMethod->clazz->descriptor,
curMethod->name);
DUMP_REGS(curMethod, self->interpSave.curFrame, false);
}
#endif
FINISH(0); /* fetch and execute first instruction */
/*--- start of opcodes ---*/
/* File: c/OP_NOP.cpp */
HANDLE_OPCODE(OP_NOP)
FINISH(1);
OP_END
/* File: c/OP_MOVE.cpp */
HANDLE_OPCODE(OP_MOVE /*vA, vB*/)
vdst = INST_A(inst);
vsrc1 = INST_B(inst);
ILOGV("|move%s v%d,v%d %s(v%d=0x%08x)",
(INST_INST(inst) == OP_MOVE) ? "" : "-object", vdst, vsrc1,
kSpacing, vdst, GET_REGISTER(vsrc1));
SET_REGISTER(vdst, GET_REGISTER(vsrc1));
FINISH(1);
OP_END
首先直譯器先載入dex檔案,從中取出函式,函式就如x86的函式地址一樣,虛擬機器的opcode如彙編指令。opcode比彙編功能多一些,高階一些,所以個別opcode不想彙編那麼簡潔。須要呼叫函式來實現。
如函式內調轉另一個函式,就需要使用函式來實現,需要傳引數。
在看看彙編:dvmMterpStd:
void dvmMterpStd(Thread* self)
{
/* configure mterp items */
self->interpSave.methodClassDex = self->interpSave.method->clazz->pDvmDex;
IF_LOGVV() {
char* desc = dexProtoCopyMethodDescriptor(
&self->interpSave.method->prototype);
LOGVV("mterp threadid=%d : %s.%s %s",
dvmThreadSelf()->threadId,
self->interpSave.method->clazz->descriptor,
self->interpSave.method->name,
desc);
free(desc);
}
//LOGI("self is %p, pc=%p, fp=%p", self, self->interpSave.pc,
// self->interpSave.curFrame);
//LOGI("first instruction is 0x%04x", self->interpSave.pc[0]);
/*
* Handle any ongoing profiling and prep for debugging
*/
if (self->interpBreak.ctl.subMode != 0) {
TRACE_METHOD_ENTER(self, self->interpSave.method);
self->debugIsMethodEntry = true; // Always true on startup
}
dvmMterpStdRun(self);
#ifdef LOG_INSTR
LOGD("|-- Leaving interpreter loop");
#endif
}
@InterpAsm-armv5te.S
.L_OP_MOVE: /* 0x01 */
/* File: armv5te/OP_MOVE.S */
/* for move, move-object, long-to-int */
/* op vA, vB */
mov r1, rINST, lsr #12 @ r1<- B from 15:12
mov r0, rINST, lsr #8 @ r0<- A from 11:8
FETCH_ADVANCE_INST(1) @ advance rPC, load rINST
GET_VREG(r2, r1) @ r2<- fp[B]
and r0, r0, #15
GET_INST_OPCODE(ip) @ ip<- opcode from rINST
SET_VREG(r2, r0) @ fp[A]<- r2
抓取第一個Dalvik OpCode指令並執行,同時在每個指令執行結束後,再透過FETCH_ADVANCE_INST與GET_INST_OPCODE抓取下一個Dalvik OpCode指令,最後再透過GOTO_OPCODE執行該指令集的實作,如此持續運作下去,藉此得到比用C版本Busy Loop更高的執行效率.
/system/bin/app_process
|-->main(): @frameworks/base/cmds/app_process/app_main.cpp
虛擬機器啟動入口函式
if (strcmp(arg, "--zygote") == 0) zygote = true;
if (strcmp(arg, "--start-system-server") == 0) startSystemServer = true;
if (zygote) { runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");
|--> AndroidRuntime::start(const char* className, const char* options) 配置環境(ANDROID_ROOT)目錄變數等 @frameworks/base/core/jni/AndroidRuntime.cpp
|--> AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
| |--> JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) @dalvik/vm/Jni.cpp
| |--> dvmStartup(int argc, const char* const argv[],bool ignoreUnrecognized, JNIEnv* pEnv) @dalvik/vm/Init.cpp
| |-->初始化組建
| |--> dvmCheckAsmConstants() 明確mterp直譯器要用到的一些變數正確
| |--> dvmAllocTrackerStartup() 函式初始化跟蹤顯示系統,跟蹤系統主要用生成除錯系統的資料包
| |--> dvmGcStartup() 初始化垃圾回收器//初始化heap
| |--> dvmThreadStartup() 初始化執行緒列表和主執行緒環境引數
| |--> dvmInlineNativeStartup() 分配內部操作方法的表格記憶體
| |--> dvmRegisterMapStartup() 分配指令暫存器狀態的記憶體
| |--> dvmInstanceofStartup() 分配虛擬機器使用的快取
| |--> dvmClassStartup() 初始化虛擬機器最基本用的JAVA庫
| |--> dvmFindRequiredClassesAndMembers()類查詢相關的初始化
| |--> dvmStringInternStartup() 初始化虛擬機器直譯器使用的字串雜湊表
| |--> dvmNativeStartup() 初始化本地方法庫的表
| |--> dvmInternalNativeStartup() 初始化內部本地方法,建立雜湊表,方便快速查詢到
| |--> dvmJniStartup() 初始化JNI呼叫表,以便快速找到本地方法呼叫的入口
| |--> dvmProfilingStartup()
| |--> dvmCreateInlineSubsTable()建立一個方法表代替inline方法表
| |--> dvmValidateBoxClasses() 始化JAVA基本型別庫
| |--> dvmPrepMainForJni(pEnv) 準備主執行緒裡的解釋棧可以呼叫JNI的方法
| |--> dvmInitClass(gDvm.classJavaLangClass)初始化java.lang.Class
| |--> registerSystemNatives(pEnv)註冊JAVA庫裡的JNI方法
| |--> dvmCreateStockExceptions()分配異常出錯的記憶體
| |--> dvmPrepMainThread()直譯器主執行緒的初始化
| |--> dvmDebuggerStartup()偵錯程式初始化
| |--> dvmGcStartupClasses()
| |--> dvmInitAfterZygote() 此函式執行完虛擬機器程序建立就完成了.
| |--> dvmGcStartupAfterZygote() 進行一次GC
| |--> dvmSignalCatcherStartup() 啟動一個Linux訊號收集執行緒,主要是用來捕捉SIGQUIT訊號,以便可以在程序退出前將各個執行緒的堆疊DUMP出來
| |--> dvmStdioConverterStartup() 啟動一個標準輸出重定向執行緒,該執行緒負責將當前程序的標準輸出(stdout和stderr)重定向到日誌輸出系統中去
| |--> dvmInitJDWP() 啟動一個JDWP執行緒,以便我們可以用DDMS工具來除錯程序中的Dalvik虛擬機器
| |--> dvmCompilerStartup() 啟動JIT,前提是當前使用的Dalvik虛擬機器在編譯時支援JIT,並且該Dalvik虛擬機器在啟動時指定了-Xint:jit選項
| |--> dvmCreateInternalThread(&gDvmJit.compilerHandle, "Compiler",compilerThreadStart, NULL);
| |--> *compilerThreadStart(void *arg) 啟動執行緒初始化dalvik的編譯器,在虛擬機器支援JIT的前提下.
| |--> compilerThreadStartup(void)在虛擬機器執行過程中一直生存的執行緒,while迴圈判斷是否有程式碼需要編譯,如果有,則呼叫dvmCompilerDoWork()對其進行編譯
|--> startReg(env) //註冊android native函式
|--> jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");
|--> env->CallStaticVoidMethod(startClass, startMeth, strArray); step1: JNIEnv.CallStaticVoidMethod setp2: JNINativeInterface.CallStaticVoidMethodV
|--> functions->CallStaticVoidMethodV(this, clazz, methodID, args);
|--> dvmInterpret(Thread* , const Method*, JValue* ) (Main interpreter loop entry point.將上一個啟用狀態儲存到當前直譯器,
初始化工作狀態(比如,執行的method賦值),選擇直譯器執行模式)
if |--> dvmMterpStd :此函式為指標函式函式名,為直譯器彙編實現的入口
else|--> dvmInterpretPortable :此函式為可移植C的直譯器實現入口
# typedef void (*Interpreter)(Thread*);
# Interpreter stdInterp;
# if (gDvm.executionMode == kExecutionModeInterpFast)
# stdInterp = dvmMterpStd;
# #if defined(WITH_JIT)
# else if (gDvm.executionMode == kExecutionModeJit)
# stdInterp = dvmMterpStd;
# #endif
# else
# stdInterp = dvmInterpretPortable;
# // Call the interpreter
# (*stdInterp)(self);
分支到dvmInterpretPortable,比較容易看懂:
dvmInterpretPortable(Thread* self) @vm/mterp/out/InterpC-portable.cpp
void dvmInterpretPortable(Thread* self)
{
#if defined(EASY_GDB)
StackSaveArea* debugSaveArea = SAVEAREA_FROM_FP(self->interpSave.curFrame);
#endif
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
/* instruction decoding */
u4 ref; // 16 or 32-bit quantity fetched directly
u2 vsrc1, vsrc2, vdst; // usually used for register indexes
/* method call setup */
const Method* methodToCall;
bool methodCallRange;
bool jumboFormat;
/* static computed goto table */
DEFINE_GOTO_TABLE(handlerTable);
/* copy state in */
curMethod = self->interpSave.method;
pc = self->interpSave.pc;
fp = self->interpSave.curFrame;
retval = self->interpSave.retval; /* only need for kInterpEntryReturn? */
methodClassDex = curMethod->clazz->pDvmDex;
LOGVV("threadid=%d: %s.%s pc=%#x fp=%p",
self->threadId, curMethod->clazz->descriptor, curMethod->name,
pc - curMethod->insns, fp);
/*
* Handle any ongoing profiling and prep for debugging.
*/
if (self->interpBreak.ctl.subMode != 0) {
TRACE_METHOD_ENTER(self, curMethod);
self->debugIsMethodEntry = true; // Always true on startup
}
/*
* DEBUG: scramble this to ensure we're not relying on it.
*/
methodToCall = (const Method*) -1;
#if 0
if (self->debugIsMethodEntry) {
ILOGD("|-- Now interpreting %s.%s", curMethod->clazz->descriptor,
curMethod->name);
DUMP_REGS(curMethod, self->interpSave.curFrame, false);
}
#endif
FINISH(0); /* fetch and execute first instruction */
/*--- start of opcodes ---*/
/* File: c/OP_NOP.cpp */
HANDLE_OPCODE(OP_NOP)
FINISH(1);
OP_END
/* File: c/OP_MOVE.cpp */
HANDLE_OPCODE(OP_MOVE /*vA, vB*/)
vdst = INST_A(inst);
vsrc1 = INST_B(inst);
ILOGV("|move%s v%d,v%d %s(v%d=0x%08x)",
(INST_INST(inst) == OP_MOVE) ? "" : "-object", vdst, vsrc1,
kSpacing, vdst, GET_REGISTER(vsrc1));
SET_REGISTER(vdst, GET_REGISTER(vsrc1));
FINISH(1);
OP_END
首先直譯器先載入dex檔案,從中取出函式,函式就如x86的函式地址一樣,虛擬機器的opcode如彙編指令。opcode比彙編功能多一些,高階一些,所以個別opcode不想彙編那麼簡潔。須要呼叫函式來實現。
如函式內調轉另一個函式,就需要使用函式來實現,需要傳引數。
在看看彙編:dvmMterpStd:
void dvmMterpStd(Thread* self)
{
/* configure mterp items */
self->interpSave.methodClassDex = self->interpSave.method->clazz->pDvmDex;
IF_LOGVV() {
char* desc = dexProtoCopyMethodDescriptor(
&self->interpSave.method->prototype);
LOGVV("mterp threadid=%d : %s.%s %s",
dvmThreadSelf()->threadId,
self->interpSave.method->clazz->descriptor,
self->interpSave.method->name,
desc);
free(desc);
}
//LOGI("self is %p, pc=%p, fp=%p", self, self->interpSave.pc,
// self->interpSave.curFrame);
//LOGI("first instruction is 0x%04x", self->interpSave.pc[0]);
/*
* Handle any ongoing profiling and prep for debugging
*/
if (self->interpBreak.ctl.subMode != 0) {
TRACE_METHOD_ENTER(self, self->interpSave.method);
self->debugIsMethodEntry = true; // Always true on startup
}
dvmMterpStdRun(self);
#ifdef LOG_INSTR
LOGD("|-- Leaving interpreter loop");
#endif
}
@InterpAsm-armv5te.S
OP如何連線執行?
/* File: armv5te/OP_MOVE.S */
/* for move, move-object, long-to-int */
/* op vA, vB */
mov r1, rINST, lsr #12 @ r1<- B from 15:12
mov r0, rINST, lsr #8 @ r0<- A from 11:8
FETCH_ADVANCE_INST(1) @ advance rPC, load rINST
GET_VREG(r2, r1) @ r2<- fp[B]
and r0, r0, #15
GET_INST_OPCODE(ip) @ ip<- opcode from rINST
SET_VREG(r2, r0) @ fp[A]<- r2
GOTO_OPCODE(ip) @ execute next instruction