Dalvik interpreter 笔记

Java 发表评论

        在任何的virtual matchine都有一个interpreter的架构,DVM也不例外. 到底DVM的interpreter在指令处理上是如何运作的? 以下就作一一的心得记录.顺便提一下, 从Android2.2之后google为了要提升app在DVM上的执行效率,所以在DVM增加了JITcompiler机制. 由于这又是另一个topic. 所以有关于JIT的运作就不在这作心得记录. 日后有时间在研究.

 

Dalvik 直译器分为两种, fast interpreter和portable interpreter.

        fast interpreter: 根据不同的平台透过组语优化后的直译器版本.

        portable interpreter: 透过all-in-one的c函数, 来建构不同平台的直译器版本.早期的Android所使用的interpreter版本.

        Dalvik的预设直译器为fast interpreter. 若要切换直译器版本, 可利用以下的命令作动态切换. “ adb shell “echo dalvik.vm.execution-mode = int:portable>> /data/local.prop” “. 下完命令再重开机, 就会改为用portableinterpreter.

        Dalvik直译器的进入点 dvmInterpret(\dalvik\vm\interp\Interp.cpp), 之后会经由gDvm.executionMode 状态来选择要使用哪一个interpreter版本.

dvmInterpret@ Interp.cpp
if (gDvm.executionMode == kExecutionModeInterpFast)
        stdInterp = dvmMterpStd;     //fast interpreter
#if defined(WITH_JIT)
    else if (gDvm.executionMode == kExecutionModeJit ||
             gDvm.executionMode == kExecutionModeNcgO0 ||
             gDvm.executionMode == kExecutionModeNcgO1)
        stdInterp = dvmMterpStd;
#endif
    else
        stdInterp = dvmInterpretPortable;   //portable interpreter

要建构DVMinterpreter, 需要先针对各个平台编辑interpreterConfiguration file, 再利用gen-mterp.py 来产生各平台的cpp 跟assembly source. (InterpC-.cpp,InterpAsm-.S) 称之为Platform-specific source. 其Platform-specificsource会产在”out”文件夹下.建构Interpreter

 

 

Platform-specificsource

1. 由指令集的架构来看, 指令跟指令的传送是由computedgoto和jump table一起完成.

        computed goto: 每一道指令在内存中的分配大小都是固定长度 (e.g.64

byte).

        jump table: 所有指令都是连续且在内存中分配大小是动态长度.

2. 由Cpp实作来看, 每一道指令被包装成Cpp程序代码. 之后由Gcc编译成机械码.

 

 

Configfile format

 

Command name

description

Required/option

handler-style <computed-goto|jump-table|all-c>

指定interpreter的型态.

computed-goto style: 每个handler所占的内存大小式固定大小, 其长度为
table-start-address + (opcode * handler-size)

jump-table style: 每个handler所占的内存大小是变动的, 其handler是一组Table数组的地址.

“all-c” style: for the portable interpreter

 

当handler-style指定为computed-goto|jump-table所建构出来的Platform-specific source会有asm跟cpp檔, 若指定为all-c, 则建构出来只有cpp檔.

required

handler-size <bytes>

指定每一个handler所占的内存大小的固定长度. 在jump-table 和 all-c 型态设定, 这个设定被忽略.

required for computed-goto type

import <filename>

指定需要的档案, filename为一个档案的目录路径加上文件名

required

asm-stub <filename>

档案里含有任何需要transfer control给用c实作的function handler的assembly “stub”. 此command不适用于all-c type.

Required for computed-goto|jump-table

asm-alt-stub <filename>

产生另一组entry points (for computed-goto interpreters)或是jump table (for jump-table interpreters).

Required for computed-goto|jump-table

op-start <directory>

Op code list的起始点. directory为要加载Opcode source 的默认目录

Required

op <opcode> <directory>

只能出现在” op-start ” 和 ” op-end ” command之间. 用来从指定目录下载所指定的opcode, e.g. “op OP_NOP armv5te” will load from “armv5te/OP_NOP.S”.

option

alt <opcode> <directory>

只能出现在” op-start ” 和 ” op-end ” command之间. 功能跟op command类似, 差别在于所指定的是opcode的table. e.g. “alt OP_NOP armv5te” will load from  “armv5te/ALT_OP_NOP.S”.

option

op-end

Op code list的起始点. 一旦发现到任何opcode没有辅合固定大小指令操作空间, 所有kNumPackedOpcodes的opcode都会被释放.

Required

 

InterpreterControl

Interpreter控制的核心机制是一个struct InterpBreak, 其定义在vm/Thread.h.在此数据结构中有一个主要的属性 subMode, 和两个辅助的属性. breakFlags & curHandlerTable.

                subMode: 用来描述debug/profile/special 操作.

        breakFlags & curHandlerTable:  用在降低subMode polling成本.

subMode

        记录着目前正在启动的特定操作模式的一个bitMask,只要是用来告知interpreter在直译指令时须要根据subModebitMask的变化来做相对应的处理.例如,当Traceviewprofiling一被启动, kSubModeMethodTrace bit会被设定.interpreter在直译指令时便会通知在每一个method entry和 return上的profilingsubsystem 详情请参考, Profile.cpp/Profile.h, Interp.cpp.Interpreter支持最简单机制的subMode操作就是在直译任何DalvikByteCode和处理任何需求之前去检查subMode
属性并作相对应的处理. 这个操作会在portableinterpreter看到. 详请可以参考stubdefs.cpp和 Platform-specific source 的InterpC-portable.cpp中的FINISHMacro.

 

breakFlags & curHandlerTable

        在处理Pre-instructionpolling subMode是相对耗费成本且subMode操作也是相当罕见. 针对一般的操作, 比较偏向避免去做检查subMode属性的动作除非所检查的subMode属性是很有效率的. 为了弥补这个缺点, 这时候curHandlerTable和breakFlags就登场了.

curHandlerTable:

     为了使Fast interpreter在从一个Dalvik byteCode转换到下一个的效率上胜过portable interpreter, 在这设计上用了computed-goto机制(for ARM), 其handler entrypoints就可以由dvmAsmInstructionStart+ (opcode * 64)得到, 而for X86是用了jump table机制, 其handler entrypoints是由一个Table arry中的index来指定.此table称之为jump
table.为了支持有效率的处理subMode, 对ARM来说支持了两组handler entry, 对x86来说支援两个jump table. 一组entry pointer(ARM), jump table(X86)作优化执行速度并且执行no inter-instruction检查,而另外一组entry pointer(ARM), jump table(X86)则是处理subMode检查跟测试.

     在一般的操作下(亦即subMode = 0), 专用缓存器 rIBASE (r8 for ARM, edx for x86) 持有mainHandlerTable. 假若需要切换到要求inter-instruction checking的subMode时, rIBASE需要改持有altHandlerTable. 若直接改动rIBASE的值, 有可能会因为在之后的分支rIBASE的值被改变而导致exception被丢出.正常的改法是修改InterpBreak结构中的curHandlerTable属性.

breakFlags:

      用来通知interpreter control mechanism使用的handler table是mainHandlerTable还是altHandlerTable. 假若breakFlags为非0值, curHandlerTable就会使用altHandlerTable. breakFlags所含的bitMask是用来告知dvmCheckBefore检查哪一个subMode.

以下是跟subMode有关的函数.

dvmEnableSubMode:  enable subMode

dvmDisableSubMode:  disable subMode

dvmCheckBefore:  subMode handling

 

Conclusion

        在建构Interpreter的步骤其实还蛮简单的, 有要三个步骤.

1. cd\dalvik\vm\mterp

2. 编辑interpreter Configuration file

3. ./rebuild.sh

这时候就会在mterp的文件夹中产生一个out文件夹(若未build过interpreter),其out的文件夹中就有interpreter的asm跟cpp程序代码了. 利用程序代码产生器gen-mterp.py来产生程序的程序设计就称为元程序设计模式.gen-mterp.py是用Python语法写的, 有兴趣的人可以自行研究. 所产生的interpreter的asm中用到的op code就是interpreterConfiguration file利用import command所指定的.s档案称之为Instruction
file. 这篇心得并没有贴太多程序代码来介绍, 只有描述一些重点, 要懂其流程架构还是要边研究程序代码, 这里把研究的路径档案整理一下, 方便日后查找.

interpreter Configurationfile

\dalvik\vm\mterp\config-*

Instruction file

\vm\mterp\ armv5te

\vm\mterp\ armv6

\vm\mterp\ armv6t2

\vm\mterp\ armv7-a

\vm\mterp\ arm-vfp

Platform-specific source

fastinterpreter

\dalvik\vm\mterp\out\InterpAsm-*.S

\dalvik\vm\mterp\out\InterpC-*.cpp

portableinterpreter

\dalvik\vm\mterp\out\InterpC-portable.cpp

Mterpentry

\dalvik\vm\mterp\Mterp.cpp

以上是基于android 4.2的程序代码去分析的, 由于android进版都会重构程序代码, 所以不代表以上的目录路径是用于各个版本.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

昵称 *