Android system在Kernel初始化玩就会先启动initprocess, 而在init process之中会去启动Zygoteprocess, Zygote process是Android system中的一个程序产生器. 而在Androidsystem中每一道程序都会需要一个DVM用来执行. 所以Zygoteprocess在一开始就要先初始化Dalvik Virtual Machine. 在此针对DVM的初始阶段会分以下三点来分析.
1. VM和JNIENV初始化
2. 直译器初始流程
3. 内存初始配置与管理.
VM和JNIENV初始化
由程序代码来看, Dvm被启动时所会执行的事情, 可由mainfunction中看出如以下的流程:
\dalvik\dalvikvm\Main.cpp
1. 启动VM
/*
* Start VM. The current thread becomes the main thread of the VM.
*/
if (JNI_CreateJavaVM(&vm, &env, &initArgs) < 0) {
fprintf(stderr, "Dalvik VM init failed (check log file)\n");
goto bail;
}
2. 利用env的FindClassfunction来寻找程序的java class.
startClass = env->FindClass(slashClass);
if (startClass == NULL) {
fprintf(stderr, "Dalvik VM unable to locate class '%s'\n", slashClass);
goto bail;
}
3. 利用env的GetStaticMethodIDfunction搭配已找到的java class来取得mainfunction id.
startMeth = env->GetStaticMethodID(startClass,
"main", "([Ljava/lang/String;)V");
if (startMeth == NULL) {
fprintf(stderr, "Dalvik VM unable to find static main(String[]) in '%s'\n",
slashClass);
goto bail;
}
4. 利用env 的CallStaticVoidMethodfunction搭配main function id 来呼叫javaclass的main function
/*
* Invoke main().
*/
env->CallStaticVoidMethod(startClass, startMeth, strArray);
if (!env->ExceptionCheck())
result = 0;
由上面的流程, 不难发现后面的动作都是需要由env的function去执行. 而env这个pointer到底是怎么来的? 答案就是第一步的JNI_CreateJavaVM执行获得的. 以下就是JNI_CreateJavaVM这个函数的流程.
JNI_CreateJavaVM
\dalvik\vm\Jni.cpp
1. 检查JNI version, 若比JNI_VERSION_1_2版本还小, 就直接回传JNI_EVERSION.
if (args->version < JNI_VERSION_1_2) {
return JNI_EVERSION;
}
2. 初始化pVM并注册pVM一个functiontable gInvokeInterface. gInvokeInterface是一个有关VM的life cycle function table.
/*
* Set up structures for JNIEnv and VM.
*/
JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface;
pVM->envList = NULL;
3. 藉由dvmCreateJNIEnv函数来初始化pEnv并注册一个functiontable gNativeInterface, gNativeInterface是一个有关于native和java通道的function table.
/*
* Create a JNIEnv for the main thread. We need to have something set up
* here because some of the class initialization we do when starting
* up the VM will call into native code.
*/
JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
/* Initialize VM. */
gDvm.initializing = true;
4. 启动VM相关的初始功能, 比如gc,thread, class等等.
dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);
5 将初始化完成的pEnv和pVM指定给所传进来的参数p_env跟p_vm.
*p_env = (JNIEnv*) pEnv;
*p_vm = (JavaVM*) pVM;
由第五步骤可以知道, 在mainfunction中所宣告的env的值就是由dvmCreateJNIEnv函数所产生的, 并且此env还带有个functiontable gNativeInterface. 因此我们就可以猜测在mainfunction中的第四个步骤env->CallStaticVoidMethod 的CallStaticVoidMethod函数必定在gNativeInterface function table中. 然而, 却找不到其实作. 这到底是怎么一回事. 以下就来一步一步的分析CallStaticVoidMethod函数的实作.
在程序设计领域中, 一般若有太多不一样的接口拥有重复的动作. 在c的实作中, 通常会用一个macro(宏)来实作, 在c++的实作则用泛型的机制, 由于早期android版本的DVM是用c来实作, 因此在于不同接口同样动作的设计是采用c的宏设计. 依照这原理来看CallStaticVoidMethod这函数的实作, 只要在程序代码搜寻CallStatic相关字眼, 就不难找出其实作的地方.
CallStaticVoidMethod
\dalvik\vm\Jni.cpp
1.定义Static 函数
/*
* Call a static method.
*/
#define CALL_STATIC(_ctype, _jname, _retfail, _retok, _isref) \
static _ctype CallStatic##_jname##Method(JNIEnv* env, jclass jclazz, \
jmethodID methodID, ...) \
{ \
UNUSED_PARAMETER(jclazz); \
ScopedJniThreadState ts(env); \
JValue result; \
va_list args; \
va_start(args, methodID); \
dvmCallMethodV(ts.self(), (Method*)methodID, NULL, true, &result, args); \
va_end(args); \
if (_isref && !dvmCheckException(ts.self())) \
result.l = (Object*)addLocalReference(ts.self(), result.l); \
return _retok; \
} \
static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass jclazz, \
jmethodID methodID, va_list args)
static _ctype CallStatic##_jname##MethodA(JNIEnv* env, jclass jclazz, \
jmethodID methodID, jvalue* args)
CALL_STATIC(jobject, Object, NULL, (jobject) result.l, true);
CALL_STATIC(jboolean, Boolean, 0, result.z, false);
CALL_STATIC(jbyte, Byte, 0, result.b, false);
CALL_STATIC(jchar, Char, 0, result.c, false);
CALL_STATIC(jshort, Short, 0, result.s, false);
CALL_STATIC(jint, Int, 0, result.i, false);
CALL_STATIC(jlong, Long, 0, result.j, false);
CALL_STATIC(jfloat, Float, 0.0f, result.f, false);
CALL_STATIC(jdouble, Double, 0.0, result.d, false);
CALL_STATIC(void, Void, , , false);
由此程序代码可以看到, CALL_STATIC 宏定义了三个函数CallStatic##_jname##Method,CallStatic##_jname##MethodV, CallStatic##_jname##MethodA, 前面两个函数的差异在于函数是否有带型参. 最后一个函数是带有array参数的函数. 此三个函数中的CallStatic##_jname##Method,CallStatic##_jname##MethodV 会呼叫dvmCallMethodV 函数,CallStatic##_jname##MethodA则呼叫dvmCallMethodA 函数. 由于这里讨论的是CallStaticVoidMethod
这函数, 因此只会继续针对dvmCallMethodV函数继续分析下去, 对于dvmCallMethodA 函数的实作就不在这里做分析.
dvmCallMethodV
\dalvik\vm\interp\Stack.cpp
1. 将Callframe 推入堆栈中, 并取得目前Framepointer: self->interpSave.curFrame
clazz = callPrep(self, method, obj, false);
2.判断是否是NativeMethod, 在Android设计中, 所谓的NativeMathod就是一种 Virtual Method, 也就是说此Method只是一种单存的接口, 其实作需要看所指向的数据型态, 算是面向对象中的泛型机制.
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)((u4*)self->interpSave.curFrame, pResult,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}
由上面的程序代码可以看到, 若不是NativeMethod就直接呼叫dvmInterpret函数作Interpreter的动作.
3. 从堆栈中弹出Callframe. 表示目前的函数指令已执行完毕.
dvmPopFrame(self);
由与这里只是简单的介绍一些初始化流程, 因此对于dvmCallMethodV的函数分析也只是点到为止, 里面对于指令的解析, 执行动作就留待之后在研究.
直译器初始流程
DVM直译器的分析先前有篇文章 Dalvik interpreter 笔记 已经有分析过里面的流程运作了, 这里的重点会在于Interpreper初始化的准备工作流程.
dvmInterpret
dalvik\vm\interp\Interp.cpp
1. 先将目前的interpreter状态作备份.
/*
* Save interpreter state from previous activation, linking
* new to last.
*/
interpSaveState = self->interpSave;
2. 开始注册新的interpreter状态.
self->interpSave.method = method;
self->interpSave.curFrame = (u4*) self->interpSave.curFrame;
self->interpSave.pc = method->insns;
3. 针对interpreter的设定来选用interpreter的型态, 在 Dalvik interpreter 笔记 中有介绍到Android的interpreter有分两类, fast 跟portable. 这两类的分析在 Dalvik interpreter 笔记 中有记录.Android 预设的interpreter是 fastinterpreter.
typedef void (*Interpreter)(Thread*);
Interpreter stdInterp;
if (gDvm.executionMode == kExecutionModeInterpFast)
stdInterp = dvmMterpStd;
#if defined(WITH_JIT)
else if (gDvm.executionMode == kExecutionModeJit ||
gDvm.executionMode == kExecutionModeNcgO0 ||
gDvm.executionMode == kExecutionModeNcgO1)
stdInterp = dvmMterpStd;
#endif
else
stdInterp = dvmInterpretPortable;
4. 呼叫interpreter 开始执行直译动作.
// Call the interpreter
(*stdInterp)(self);
5. 所有的指令直译完毕之后, 就回存先前备份的interpreter状态.
/* Restore interpreter state from previous activation */
self->interpSave = interpSaveState;
内存初始配置与管理
Android在DVM的内存管理设计, 在之前的 Android_DVM_MemoryManagement_研究分析 已经有详细的做研究分析了, 在这里会着重在DVM如何初始化内存以及为之后的GC机制作准备的流程. 在这篇一开始的时候有分析到一个dvmStartup函数, 这个函数中用来初始化DVM所需要的一些功能,
dvmAllocTrackerStartup
dvmGcStartup
dvmThreadStartup
dvmInlineNativeStartup
dvmRegisterMapStartup
dvmInstanceofStartup
dvmClassStartup
[...]
其中 dvmGcStartup 函数就是用来初始化GC的动作.
dvmGcStartup
\dalvik\vm\alloc\Alloc.cpp
1. 初始化gcHeapLock锁, 之后呼叫dvmHeapStartup函数开始进入初始化流程.
dvmInitMutex(&gDvm.gcHeapLock);
pthread_cond_init(&gDvm.gcHeapCond, NULL);
return dvmHeapStartup();
dvmHeapStartup
\dalvik\vm\alloc\Heap.cpp
1. 指定所配置的Heapmemory总共size的大小, 在android的默认值是16MB.
if (gDvm.heapGrowthLimit == 0) {
gDvm.heapGrowthLimit = gDvm.heapMaximumSize;
}
2. 初始化GC所需要的HeapSource, Android在这里的Heap Source是用来管理整个系统占用内存的一套系统, GC便能借由HeapSource来做回收机制. 至于HeapSource的产生跟管理流程在 Android_DVM_MemoryManagement_研究分析 有作一些研究心得.在这就不在分析了.
gcHeap = dvmHeapSourceStartup(gDvm.heapStartingSize,
gDvm.heapMaximumSize,
gDvm.heapGrowthLimit);
3. 将以分配好的Heap source指定给gDvm.gcHeap为后续动作做准备.
[...]
gDvm.gcHeap = gcHeap;
4. 设定一个list来记录需要回收的"参考"对象.
/* Set up the lists we'll use for cleared reference objects.
*/
gcHeap->clearedReferences = NULL;
if (!dvmCardTableStartup(gDvm.heapMaximumSize, gDvm.heapGrowthLimit))
{
LOGE_HEAP("card table startup failed.");
return false;
}
由以上的程序代码可以发现GC初始化到最后会呼叫dvmCardTableStartup函数来制作一份CardTable, 最主要是要来记录GC要回收的数据. 如何使用这个Cardtable来做回收机制, GC在作dvmCollectGarbageInternal的流程中作循环scanmarked 对象会用到, 详请可自行研究dvmHeapReScanMarkedObjects 函数.