Dalvik虚拟机的运行过程分析.docx

上传人:飞****2 文档编号:78771446 上传时间:2023-03-19 格式:DOCX 页数:19 大小:97.10KB
返回 下载 相关 举报
Dalvik虚拟机的运行过程分析.docx_第1页
第1页 / 共19页
Dalvik虚拟机的运行过程分析.docx_第2页
第2页 / 共19页
点击查看更多>>
资源描述

《Dalvik虚拟机的运行过程分析.docx》由会员分享,可在线阅读,更多相关《Dalvik虚拟机的运行过程分析.docx(19页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。

1、从前面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类

2、的静态成员函数main作为Java代码的入口点。 接下来,我们就从JNIEnv类的成员函数CallStaticVoidMethod开始,分析Dalvik虚拟机的运行过程,如图1所示:图1 Dalvik虚拟机的运行过程 这个过程可以分为9个步骤,接下来我们就详细分析每一个步骤。 Step 1.JNIEnv.CallStaticVoidMethodcppview plaincopy1. struct_JNIEnv;2. .3. typedef_JNIEnvJNIEnv;4. .5. 6. struct_JNIEnv7. /*donotrenamethis;itdoesnotseemtobeenti

3、relyopaque*/8. conststructJNINativeInterface*functions;9. .10. 11. voidCallStaticVoidMethod(jclassclazz,jmethodIDmethodID,.)12. 13. va_listargs;14. va_start(args,methodID);15. functions-CallStaticVoidMethodV(this,clazz,methodID,args);16. va_end(args);17. 18. 19. .20. ; 这个函数定义在文件dalvik/libnativehelpe

4、r/include/nativehelper/jni.h中。 JNIEnv实际上是一个结构,它有一个成员变量functions,指向的是一个回调函数表。这个回调函数表使用一个JNINativeInterface对象来描述。JNIEnv结构体的成员函数CallStaticVoidMethod的实现很简单,它只是调用该回调函数表中的CallStaticVoidMethodV函数来执行参数clazz和methodID所描述的Java代码。 Step 2.JNINativeInterface.CallStaticVoidMethodVcppview plaincopy1. structJNINativ

5、eInterface2. .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结构体来描述的,并

6、且这个JNIEnvExt结构体会被强制转换成一个JNIEnv结构体返回给Zygote进程。 JNIEnvExt结构体定义在文件dalvik/vm/JniInternal.h中,如下所示:cppview plaincopy1. typedefstructJNIEnvExt2. conststructJNINativeInterface*funcTable;/*mustbefirst*/3. 4. .5. JNIEnvExt; 从这里就可以看出,虽然结构体JNIEnvExt和JNIEnv之间没有继承关系,但是它们的第一个成员变量的类型是一致的,也就是它们都是指向一个类型为JNINativeInte

7、rface的回调函数表,因此,Dalvik虚拟机可以将一个JNIEnvExt结构体强制转换成一个JNIEnv结构体返回给Zygote进程,这时候我们通过JNIEnv结构体来访问其成员变量functions所描述的回调函数表时,实际访问到的是对应的JNIEnvExt结构体的成员变量funcTable所描述的回调函数表。 为什么不直接让JNIEnvExt结构体从JNIEnv结构体继承下来呢?这样把一个JNIEnvExt结构体转换为一个JNIEnv结构体就是相当直观的。然而,Dalvik虚拟机的源代码并一定是要以C+语言的形式来编译的,它也可以以C语言的形式来编译的。由于C语言没有继承的概念,因此,

8、为了使得Dalvik虚拟机的源代码能同时兼容C+和C,这里就使用了一个Trick:只要两个结构体的内存布局相同,它们就可以相互转换访问。当然,这并不要求两个结构体的内存布局完全相同,但是至少开始部分要求是相同的。在这种情况下,将一个结构体强制转换成另外一个结构体之外,只要不去访问内存布局不一致的地方,就没有问题。在Android系统的Native代码中,我们可以常常看到这种Trick。 接下来,我们需要搞清楚的是JNIEnvExt结构体的成员变量funcTable指向的回调函数表是什么。同样是从前面Dalvik虚拟机的启动过程分析一文可以知道,Dalvik虚拟机在创建一个JNIEnvExt结构

9、体的时候,会将它的成员变量funcTable指向全局变量gNativeInterface所描述的一个回调函数表。 gNativeInterface定义在文件dalvik/vm/Jni.c中,如下所示:cppview plaincopy1. staticconststructJNINativeInterfacegNativeInterface=2. .3. 4. CallStaticVoidMethodV,5. 6. .7. ; 在这个回调函数表中,名称为CallStaticVoidMethodV的函数指针指向的是一个同名函数CallStaticVoidMethodV。 函数CallStatic

10、VoidMethodV同样是定义在文件dalvik/vm/Jni.c中,不过它是通过宏CALL_STATIC来定义的,如下所示:cppview plaincopy1. #defineCALL_STATIC(_ctype,_jname,_retfail,_retok,_isref)2. .3. static_ctypeCallStatic#_jname#MethodV(JNIEnv*env,jclassjclazz,4. jmethodIDmethodID,va_listargs)5. 6. UNUSED_PARAMETER(jclazz);7. JNI_ENTER();8. JValueres

11、ult;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所描述的回调函数表中,名称为CallStaticVoidMet

12、hodV的函数指针指向的是一个同名函数CallStaticVoidMethodV。这就是说,我们通过JNIEnv结构体的成员变量functions所访问到的名称为CallStaticVoidMethodV函数指针实际指向的是函数CallStaticVoidMethodV。 Step 3.CallStaticVoidMethodV 我们将上面的CALL_STATIC宏开之后,就可以得到函数CallStaticVoidMethodV的实现,如下所示:cppview plaincopy1. static_ctypeCallStaticVoidMethodV(JNIEnv*env,jclassjcla

13、zz,2. jmethodIDmethodID,va_listargs)3. 4. UNUSED_PARAMETER(jclazz);5. JNI_ENTER();6. JValueresult;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. 函数CallStaticVoidMe

14、thodV的实现很简单,它通过调用另外一个函数dvmCallMethodV来执行由参数jclazz和methodID所描述的Java代码,因此,接下来我们就继续分析函数dvmCallMethodV的实现。 Step 4.dvmCallMethodVcppview plaincopy1. voiddvmCallMethodV(Thread*self,constMethod*method,Object*obj,2. boolfromJni,JValue*pResult,va_listargs)3. 4. .5. 6. if(dvmIsNativeMethod(method)7. TRACE_MET

15、HOD_ENTER(self,method);8. /*9. *Becauseweleavenospaceforlocalvariables,curFramepoints10. *directlyatthemethodarguments.11. */12. (*method-nativeFunc)(self-curFrame,pResult,method,self);13. TRACE_METHOD_EXIT(self,method);14. else15. dvmInterpret(self,method,pResult);16. 17. 18. .19. 这个函数定义在文件dalvik/v

16、m/interp/Stack.c中。 函数dvmCallMethodV首先检查参数method描述的函数是否是一个JNI方法。如果是的话,那么它所指向的一个Method对象的成员变量nativeFunc就指向该JNI方法的地址,因此就可以直接对它进行调用。否则的话,就说明参数method描述的是一个Java函数,这时候就需要继续调用函数dvmInterpret来执行它的代码。 Step 5.dvmInterpretcppview plaincopy1. voiddvmInterpret(Thread*self,constMethod*method,JValue*pResult)2. 3. In

17、terpStateinterpState;4. .5. 6. /*7. *Initializeworkingstate.8. *9. *Noneedtoinitializeretval.10. */11. interpState.method=method;12. interpState.fp=(u4*)self-curFrame;13. interpState.pc=method-insns;14. .15. 16. typedefbool(*Interpreter)(Thread*,InterpState*);17. InterpreterstdInterp;18. if(gDvm.exe

18、cutionMode=kExecutionModeInterpFast)19. stdInterp=dvmMterpStd;20. #ifdefined(WITH_JIT)21. elseif(gDvm.executionMode=kExecutionModeJit)22. /*Ifprofilingoverheadcanbekeptlowenough,wecanuseaprofiling23. *mterpfastforbothJitandfastmodes.Ifoverheadistoohigh,24. *createaspecializedprofilinginterpreter.25.

19、 */26. stdInterp=dvmMterpStd;27. #endif28. else29. stdInterp=dvmInterpretStd;30. 31. change=true;32. while(change)33. switch(interpState.nextMode)34. caseINTERP_STD:35. LOGVV(threadid=%d:interpSTDn,self-threadId);36. change=(*stdInterp)(self,&interpState);37. break;38. caseINTERP_DBG:39. LOGVV(threa

20、did=%d:interpDBGn,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,它们分别使用kExecutionModeInterpPor

21、table、kExecutionModeInterpFast和kExecutionModeJit三个常量来表示。Dalvik虚拟机在启动的时候,会通过解析命令行参数获得所要执行的模式,并且记录在全局变量gDvm所指向的一个DvmGlobals结构体的成员变量executionMode中。 kExecutionModeInterpPortable表示Dalvik虚拟机以可移植模式来解释Java代码,也就是这种执行模式可以应用在任何一个平台上,例如,可以同时在arm和x86各个平台上执行。这时候使用函数dvmInterpretStd来作为Java代码的执行函数。 kExecutionModeInt

22、erpFast表示Dalvik虚拟机以快速模式来解释Java代码。以这种模式执行的Dalvik虚拟机是针对某一个特定的目标平台进行过优化的,因此,它可以更快速地对Java代码进行解释以及执行。这时候使用函数dvmMterpStd来作为Java代码的执行函数。 kExecutionModeJit表示Dalvik虚拟机支持JIT模式来执行Java代码,也就是先将Java代码动态编译成Native代码再执行。这时候使用函数dvmMterpStd来作为Java代码的执行函数。 我们可以将函数dvmInterpretStd和dvmMterpStd理解为Dalvik虚拟机的解释器入口点。很显然,解释器是虚

23、拟机的核心模块,它的性能关乎到整个虚拟机的性能。Dalvik虚拟机的解释器开始的时候都是以C语言来实现的,后来为了提到性能,就改成以汇编语言来实现。注意,无论Dalvik虚拟机的解释器是以C语言来实现,还是以汇编语言来实现,它们的源代码都是以一种模块化方法来自动生成的,并且能够根据某一个特定的平台进行优化。 所谓模块化代码生成方法,就是说将解释器的实现划分成若干个模块,每一个模块都对应有一系列的输入文件(本身也是源代码文件),最后通过工具(一个Python脚本)将这些输入文件组装起来形成一个C语言文件或者汇编语言文件。这个最终得到的C语言文件或者汇编语言文件就是Dalvik虚拟机的解释器的实现

24、文件。有了这种模块化代码生成方法之后,为某一个特定的平台生成优化过的解释器就是相当容易的:我们只需要为该平台的Dalvik虚拟机解释器的相关模块提供一个特殊版本的输入文件即可。也就是说,我们需要为每一个支持的平台提供一个配置文件,该配置文件描述了该平台的Dalvik虚拟机解释器的各个模块所要使用的输入文件。这种模块化代码生成方法不仅能避免手动编写解释器容易出错的问题,还能方便快速地将Dalvik虚拟机从一个平台移植到另外一个平台。 采取了模块化方法来生成Dalvik虚拟机解释器的源代码之后,Dalvik虚拟机解释器的源代码整体上就划分成两部分:第一部分相当于一个Wrapper,定义在dalvi

25、k/vm/interp目录中;第二部分对应于具体的实现,定义在dalvik/vm/mterp目录中。 在dalvik/vm/mterp目录中,又定义了一系列的输入文件,以及各个平台所使用的配置文件。有了这些输入文件和配置文件之后,就可以通过一个Python脚本来为每一个平台生成一个Dalvik虚拟机解释器的输出文件,并且保存在dalvik/vm/mterp/out目录中。由于针对各个平台生成的输出文件是一个汇编语言文件,即一个 *.S文件,为了方便理解它的逻辑,每一个汇编语言文件都对应有一个C语言文件。 虽然Dalvik虚拟机解释器针对每一个平台都有一个优化的版本,但是同时也会提供一个通用的版

26、本,也就是一个可移植版本。该可移植版本的解释器源文件只有C语言实现版本,定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中。在本文中,我们只考虑可移植版本的Dalvik虚拟机解释器的实现,也就是只考虑执行模式为kExecutionModeInterpPortable的Dalvik虚拟机的运行机制。从前面的分析可以知道,该可移植版本的Dalvik虚拟机解释器的入口函数为dvmInterpretStd。 函数dvmInterpretStd在执行之前,需要知道解释器的当前状态,也就是它所要执行的Java函数及其指令入口,以及当前要执行的线程的堆栈。这些信息都用一个

27、InterpState结构体来描述。其中,这个InterpState结构体的成员变量method描述的是要执行的Java函数,成员变量fp描述的是要执行的线程的当前堆栈帧,成员变量pc描述的是要执行的Java函数的入口点。 在函数dvmInterpret中,参数self描述的是当前用来执行Java代码的线程,而参数method描述的是要执行的Java函数。通过这两个参数我们就可以初始化上述的InterpState结构体。Dalvik虚拟机解释器除了可以在正常模式执行之外,还可以在调试模式执行,即决于上述初始化后得到的InterpState结构体的成员变量nextMode的值。 在本文中,我们只

28、考虑正常模式执行的Dalvik虚拟机解释器的实现,也就是我们只分析函数dvmInterpretStd的实现。函数dvmInterpretStd解释完成指定的Java函数之后,获得的返回值就保存在上述InterpState结构体的成员变量retval中。 Step 6.dvmInterpretStdcppview plaincopy1. #defineINTERP_FUNC_NAMEdvmInterpretStd2. .3. 4. boolINTERP_FUNC_NAME(Thread*self,InterpState*interpState)5. 6. .7. 8. DvmDex*method

29、ClassDex;/curMethod-clazz-pDvmDex9. JValueretval;10. 11. /*corestate*/12. constMethod*curMethod;/methodwereinterpreting13. constu2*pc;/programcounter14. u4*fp;/framepointer15. u2inst;/currentinstruction16. .17. 18. /*copystatein*/19. curMethod=interpState-method;20. pc=interpState-pc;21. fp=interpSt

30、ate-fp;22. retval=interpState-retval;/*onlyneedforkInterpEntryReturn?*/23. 24. methodClassDex=curMethod-clazz-pDvmDex;25. .26. 27. while(1)28. .29. 30. /*fetchthenext16bitsfromtheinstructionstream*/31. inst=FETCH(0);32. 33. switch(INST_INST(inst)34. .35. 36. HANDLE_OPCODE(OP_INVOKE_DIRECT/*vB,vD,vE,

31、vF,vG,vA,methCCCC*/)37. GOTO_invoke(invokeDirect,false);38. OP_END39. 40. .41. 42. HANDLE_OPCODE(OP_RETURN/*vAA*/)43. vsrc1=INST_AA(inst);44. .45. retval.i=GET_REGISTER(vsrc1);46. GOTO_returnFromMethod();47. OP_END48. 49. .50. 51. 52. 53. 54. .55. 56. /*exportstatechanges*/57. interpState-method=cur

32、Method;58. interpState-pc=pc;59. interpState-fp=fp;60. /*debugTrackedRefStartdoesntchange*/61. interpState-retval=retval;/*needfor_entryPoint=ret*/62. interpState-nextMode=63. (INTERP_TYPE=INTERP_STD)?INTERP_DBG:INTERP_STD;64. .65. 66. returntrue;67. 这个函数定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中。

33、函数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

34、/InterpC-portstd.c中,如下所示:cppview plaincopy1. /*2. *Get16bitsfromthespecifiedoffsetoftheprogramcounter.Wealways3. *wanttoload16bitsatatimefromtheinstructionstream-itsmore4. *efficientthan8andwonthavethealignmentproblemsthat32might.5. *6. *Assumesexistenceofconstu2*pc.7. */8. #defineFETCH(_offset)(pc(

35、_offset)9. 10. /*11. *Extractinstructionbytefrom16-bitfetch(_instisau2).12. */13. #defineINST_INST(_inst)(_inst)&0xff) 从这里我们就可以看出,pc实际上指向的就是当前要执行的Java函数的方法区,也就是一个指令流。这个指令流包含了很多指令,需要通过一个while循环来依次对它们进行解释,直到碰到一个return指令为止。这就是Dalvik虚拟机解释器的核心功能。例如,假设当前遇到的是一条OP_INVOKE_DIRECT指令,它表示要调用当前类的一个非静态非虚成员函数,这时候就会

36、通过宏GOTO_invoke跳到invokeDirect这个分支去。 宏HANDLE_OPCODE和GOTO_invoke定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:cppview plaincopy1. #defineHANDLE_OPCODE(_op)case_op:2. 3. #defineGOTO_invoke(_target,_methodCallRange)4. do5. methodCallRange=_methodCallRange;6. goto_target;7. while(false) 分支invokeDirect是通过另外一个宏GOTO_TARGET来定义的,在接下来的Step 7中我们再分析。 当遇到return指令时,例如,遇到OP_RETURN指令时,首先会通过宏INST_AA和G

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 教育专区 > 教案示例

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知淘文阁网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号© 2020-2023 www.taowenge.com 淘文阁