Dalvik虚拟机的运行过程分析.docx
《Dalvik虚拟机的运行过程分析.docx》由会员分享,可在线阅读,更多相关《Dalvik虚拟机的运行过程分析.docx(19页珍藏版)》请在冰豆网上搜索。
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所示:
图1Dalvik虚拟机的运行过程
这个过程可以分为9个步骤,接下来我们就详细分析每一个步骤。
Step1. JNIEnv.CallStaticVoidMethod
[cpp] viewplaincopy
1.struct _JNIEnv;
2.......
3.typedef _JNIEnv JNIEnv;
4.......
5.
6.struct _JNIEnv {
7. /* do not rename this; it does not seem to be entirely opaque */
8. const struct JNINativeInterface* functions;
9. ......
10.
11. void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
12. {
13. va_list args;
14. va_start(args, methodID);
15. functions->CallStaticVoidMethodV(this, clazz, methodID, args);
16. va_end(args);
17. }
18.
19. ......
20.};
这个函数定义在文件dalvik/libnativehelper/include/nativehelper/jni.h中。
JNIEnv实际上是一个结构,它有一个成员变量functions,指向的是一个回调函数表。
这个回调函数表使用一个JNINativeInterface对象来描述。
JNIEnv结构体的成员函数CallStaticVoidMethod的实现很简单,它只是调用该回调函数表中的CallStaticVoidMethodV函数来执行参数clazz和methodID所描述的Java代码。
Step2. JNINativeInterface.CallStaticVoidMethodV
[cpp] viewplaincopy
1.struct JNINativeInterface {
2. ......
3.
4. void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);
5.
6. ......
7.};
这个函数定义在文件dalvik/libnativehelper/include/nativehelper/jni.h中。
JNINativeInterface是一个结构体,它的成员变量CallStaticVoidMethodV是一个函数指针。
从前面Dalvik虚拟机的启动过程分析一文可以知道,Dalvik虚拟机在内部为Zygote进程的主线程所创建的Java环境是用一个JNIEnvExt结构体来描述的,并且这个JNIEnvExt结构体会被强制转换成一个JNIEnv结构体返回给Zygote进程。
JNIEnvExt结构体定义在文件dalvik/vm/JniInternal.h中,如下所示:
[cpp] viewplaincopy
1.typedef struct JNIEnvExt {
2. const struct JNINativeInterface* funcTable; /* must be first */
3.
4. ......
5.} 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虚拟机的启动过程分析一文可以知道,Dalvik虚拟机在创建一个JNIEnvExt结构体的时候,会将它的成员变量funcTable指向全局变量gNativeInterface所描述的一个回调函数表。
gNativeInterface定义在文件dalvik/vm/Jni.c中,如下所示:
[cpp] viewplaincopy
1.static const struct JNINativeInterface gNativeInterface = {
2. ......
3.
4. CallStaticVoidMethodV,
5.
6. ......
7.};
在这个回调函数表中,名称为CallStaticVoidMethodV的函数指针指向的是一个同名函数CallStaticVoidMethodV。
函数CallStaticVoidMethodV同样是定义在文件dalvik/vm/Jni.c中,不过它是通过宏CALL_STATIC来定义的,如下所示:
[cpp] viewplaincopy
1.#define CALL_STATIC(_ctype, _jname, _retfail, _retok, _isref) \
2. ...... \
3. static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass jclazz, \
4. jmethodID methodID, va_list args) \
5. { \
6. UNUSED_PARAMETER(jclazz); \
7. JNI_ENTER(); \
8. JValue result; \
9. dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);\
10. if (_isref && !
dvmCheckException(_self)) \
11. result.l = addLocalReference(env, result.l); \
12. JNI_EXIT(); \
13. return _retok; \
14. }
15. ...... \
16.CALL_STATIC(void, Void, , , false);
通过上面的分析就可以知道,在JNIEnvExt结构体的成员变量funcTable所描述的回调函数表中,名称为CallStaticVoidMethodV的函数指针指向的是一个同名函数CallStaticVoidMethodV。
这就是说,我们通过JNIEnv结构体的成员变量functions所访问到的名称为CallStaticVoidMethodV函数指针实际指向的是函数CallStaticVoidMethodV。
Step3. CallStaticVoidMethodV
我们将上面的CALL_STATIC宏开之后,就可以得到函数CallStaticVoidMethodV的实现,如下所示:
[cpp] viewplaincopy
1.static _ctype CallStaticVoidMethodV(JNIEnv* env, jclass jclazz,
2. jmethodID methodID, va_list args)
3.{
4. UNUSED_PARAMETER(jclazz);
5. JNI_ENTER();
6. JValue result;
7. dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);
8. if (_isref && !
dvmCheckException(_self))
9. result.l = addLocalReference(env, result.l);
10. JNI_EXIT();
11. return _retok;
12.}
函数CallStaticVoidMethodV的实现很简单,它通过调用另外一个函数dvmCallMethodV来执行由参数jclazz和methodID所描述的Java代码,因此,接下来我们就继续分析函数dvmCallMethodV的实现。
Step4. dvmCallMethodV
[cpp] viewplaincopy
1.void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
2. bool fromJni, JValue* pResult, va_list args)
3.{
4. ......
5.
6. if (dvmIsNativeMethod(method)) {
7. TRACE_METHOD_ENTER(self, method);
8. /*
9. * Because we leave no space for local variables, "curFrame" points
10. * directly at the method arguments.
11. */
12. (*method->nativeFunc)(self->curFrame, pResult, method, self);
13. TRACE_METHOD_EXIT(self, method);
14. } else {
15. dvmInterpret(self, method, pResult);
16. }
17.
18. ......
19.}
这个函数定义在文件dalvik/vm/interp/Stack.c中。
函数dvmCallMethodV首先检查参数method描述的函数是否是一个JNI方法。
如果是的话,那么它所指向的一个Method对象的成员变量nativeFunc就指向该JNI方法的地址,因此就可以直接对它进行调用。
否则的话,就说明参数method描述的是一个Java函数,这时候就需要继续调用函数dvmInterpret来执行它的代码。
Step5. dvmInterpret
[cpp] viewplaincopy
1.void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
2.{
3. InterpState interpState;
4. ......
5.
6. /*
7. * Initialize working state.
8. *
9. * No need to initialize "retval".
10. */
11. interpState.method = method;
12. interpState.fp = (u4*) self->curFrame;
13. interpState.pc = method->insns;
14. ......
15.
16. typedef bool (*Interpreter)(Thread*, InterpState*);
17. Interpreter stdInterp;
18. if (gDvm.executionMode == kExecutionModeInterpFast)
19. stdInterp = dvmMterpStd;
20.#if defined(WITH_JIT)
21. else if (gDvm.executionMode == kExecutionModeJit)
22./* If profiling overhead can be kept low enough, we can use a profiling
23. * mterp fast for both Jit and "fast" modes. If overhead is too high,
24. * create a specialized profiling interpreter.
25. */
26. stdInterp = dvmMterpStd;
27.#endif
28. else
29. stdInterp = dvmInterpretStd;
30.
31. change = true;
32. while (change) {
33. switch (interpState.nextMode) {
34. case INTERP_STD:
35. LOGVV("threadid=%d:
interp STD\n", self->threadId);
36. change = (*stdInterp)(self, &interpState);
37. break;
38. case INTERP_DBG:
39. LOGVV("threadid=%d:
interp DBG\n", self->threadId);
40. change = dvmInterpretDbg(self, &interpState);
41. break;
42. default:
43. dvmAbort();
44. }
45. }
46.
47. *pResult = interpState.retval;
48.
49. ......
50.}
这个函数定义在文件dalvik/vm/interp/Interp.c中。
在前面Dalvik虚拟机的启动过程分析一文中提到,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虚拟机从一个平台移植到另外一个平台。
采取了模块化方法来生