Android Dalvik 虚拟机移植指导
Dalvik 移植指导
Dalvik虚拟机可以在很多平台上运行,这些平台的操作系统至少是一个运行着GNUC编译器的类似于UNIX的平台(Linux,BSD,Mac OS X)。本文档指导读者如何把Dalvik虚拟机移植到一个Linux平台上,本文档假定我们要移植的平台和目前Android平台在代码架构上有一定的相似性,可以进行移植。
核心库的移植
核心库的源代码主要在Dalvik/libcore和dalvik/vm/native这两个文件夹中。核心库的源代码是用C语言和C++写成的,因此在Linux环境下不需要更改。核心库的代码很多都是来自Apache Harmony项目,但是也有一些是来自OpenSSL、zlb和ICU等项目,因此,为了虚拟机的运行,这些项目需要被移植到新平台上。
JNI Call Bridge 的移植
DVM的运行库绝大部分都是用portable C编写的,其中的一个例外是JNI call bridge。简单来说,它的作用是把一系列的整型值转变成各种类型的函数参数,并且调用函数。这个调用过程必须符合C函数调用的约定。
为了简化移植,JNI Call Bridge在新平台上通常会使用开源的FFI库(我觉得,这种库,大概类似于java一样,具有通用性吧)。但是,FFI运行不够快,也没有对平台做专门优化,所以,移植JNI Call Bridge首先应该重新写一个FFI库。
JNI Call Bridge代码在dalvik/vm/arch/*这个位置上,同时,基于FFI的版本(我觉得大概是在移植的时候的通用版本)在“generic”目录下。每一种架构都有两个源文件,其中一个是定义了JNI Call Bridge函数,函数如下:
void dvmPlatformInvoke(void* pEnv, ClassObject* clazz, int argInfo, int argc, const u4* argv, const char* signature, void* func, JValue* pReturn)
这个函数会调用如下C/C++函数:(我想应该是JNI函数)
return_type func(JNIEnv* pEnv, Object* this [, args])
或者
return_type func(JNIEnv* pEnv, ClassObject* clazz [, args]) (适用于静态方法)
dvmPlatformInvoke是把argv所指向的值转化为符合C类型调用的值,而后调用我上面指出的函数,再把得到的返回值放入JValue* pReturn所指向的地方。该函数可能使用方法签名来决定如何处理函数中的相关值。至于方法签名,它是一个短小的DEX签名,用一个字符对应一个返回值和一个参数。
而另外一个源文件(前面提过有两个源文件)则定义了一个32位的“hint”。当相应的方法类型被加载时,hint值就会被计算出来,作为“arginfo”参数进入dvmPlatformInvoke函数,hint可以用来使dvmPlatformInvoke停止例如扫面函数的返回值、总体参数的大小,以及整形参数64字节条件测试的限制等的ASCII方法签名。(我想hint应该是决定函数是否对返回值或者参数进行方法签名扫描)
解释器的移植
Dalvik虚拟机运行库含有两个解释器,分别是标以“移动型”和“快速型”。
移动型主大体上就是一个C函数,在任何装有GCC的系统上都应该可以被编译出来。(如果你的机器没装有GCC,那么你应该停用“threaded”模块,因为这个模块依赖于GCC的goto语句的目录来执行的。不清楚的话可以查找THREADED_INTERP的定义。
快速型使用手工汇编导致的碎片(??这里不懂)。如果目前系统里没有解释器可用,那么系统就会从C stubs中生成一个解释器,这个解释器运行速度比移动型慢了很多,说它是快速解释器,实在是名不符实。
快速型在系统里是被默认使能的,如果源代码不支持快速型。那么该如何默认使能移动型呢?这可以通过dalvik.vm.execution-mode system来实现。例如。你打入如下一行:
adb shell "echo dalvik.vm.execution-mode = int:portable >> /data/local.prop"
然后重启,这样android应用层框架启动时就自动会使能移动型解释器了。
Mterp 解释器架构
如果用汇编语言重写解释器的话,解释器的效能应该会有一个明显的提高再加上相应平台专用架构的优化,dalvik可以用一个指令一次执行完毕(??这里也不懂)
实现解释器最简单的方法究竟是用一个大型的开关语句。每条指令执行完毕后,解释器就跳到循环的顶部,根据条件跳到合适的位置(我想是类似于goto语句,执行完毕后回到顶部)。
使用线程化执行可以对这种方法进行改进。在每条指令执行的末端就包含下一条指令的取值和分派(我想是类似于流水线架构吧)。这样会使解释器变得有些庞大,但是就不用再跳回到开关语句的顶部了,而后者代价不菲。
而Mterp解释器更加先进一些,用一个计算过的“goto”,来代替goto语句的(跳转)表,避免从跳转表中查找符合的地址,后者每次执行都需要再从内存中取的指令。Mterp的操作码都是固定值,Mterp中的每个处理程序都有64个字节的空间,当然这无法满足所有程序的需要,那些无法满足的,可以通过子程或者干脆在基本空间(64个字节)之外再添加代码。Dalvik会自动处理其中的一些程序,但是,直到VM执行的时候dalvik才会检测是否有程序溢出(超过64个字节)。对于每条处理程序64个字节空间的决定看起来多少有些任意选择的意味,但是却证明对于ARM和X86架构都很不错。
在开发的进程中每个处理程序最好同时混有C语言好和汇编语言,而且可以在解决难问题的时候随意在这两种语言中切换进行。Mterp就是这样的,在如果你看一下dalvik/vm/mterp/out 目录,你就会发现C编译器和汇编编译器总是在同时执行。
解释器的代码在dalvik/vm/mterp文件夹里,如果你没有,那么现在你应该好好读读alvik/vm/mterp/README.txt这个文档了。