易佳望 王斌 肖晖 胡海
摘要: 在移动应用开发中,为了将J2ME技术应用到ARM9嵌入式平台上,需要事先在该ARM9平台上成功移植KVM。但是,在将SUN公司的CLDC 1.1参考实现移植到ARM9平台的过程中,由于编译工具版本更新导致的编译工具与参考实现之间的版本不兼容问题,影响了KVM的成功移植。为此,研究了KVM的代码执行机制,并分析了SUN公司的CLDC 1.1参考实现的build过程。在此基础上,采用目前主流编译器来移植KVM,对编译生成KVM各个阶段产生的所有错误和警告进行了深入分析,并给出了消除那些影响移植结果的错误和警告的适当解决方法。所提出的解决方法不仅实现了参考实现源码的成功编译,而且提高了移植后KVM程序的健壮性。KVM移植测试的实验结果表明,KVM成功地被移植到了ARM9+Embedded Linux平台上。
关键词: 移动应用开发; J2ME; CLDC; KVM移植
中图分类号:TP399文献标志码:A 文章编号:1006-8228(2012)05-22-04
The research and implementation of KVM porting on ARM9 platforms
Yi Jiawang, Wang Bin, Xiao Hui, Hu Hai
(School of Computer & Communication Engineering, Changsha University of Science & Technology, Changsha, Hunan 410004, China)
Abstract: In mobile application development, we need to successfully port KVM to ARM9 embedded platforms before we use J2ME technology on ARM9 platforms. However, in the process of porting SUN's CLDC 1.1 reference implementation to ARM9 platforms, the version incompatibility problem between compiling tools and the reference implementation, which is brought by version updating of compiling tools, leads to failure in KVM porting. This paper studies the mechanism of executing KVM code, and analyses the building process of SUN's CLDC 1.1 reference implementation. Based on this work, the paper uses currently popular compiling tools to port KVM, analyses in depth all the errors and warnings produced in phases of building KVM, and gives the appropriate solutions to eliminate those errors and warnings which affect the porting result. Solutions proposed in the paper not only achieve the success of compiling reference implementation source code but also improve the robustness of ported KVM programs. The experimental results of KVM porting tests show that KVM is successfully ported to an 'ARM9 + Embedded Linux' platform.
Key words: Mobile application development; J2ME; CLDC; KVM porting
0 引言
目前移动手机上运行的操作系统种类繁多,包括Linux、Windows Mobile、Palm OS、Symbian OS、Google Android等。Java和J2ME技术克服了手机平台多样性造成的应用程序移植困难的问题,为移动应用程序的跨平台开发和运行提供了可能。
J2ME是Sun公司面向具有有限硬件资源的设备的Java版本,这些资源受限的设备包括PDA、手机、机顶盒和其它消费电子设备与嵌入式设备[1]。CLDC[2]和MIDP[3]已经成为用于移动手机应用开发的J2ME标准。
J2ME/CLDC技术的核心是Sun公司的KVM(Kilobyte Virtual Machine)[4]。KVM最初设计是用于资源受限的低端移动设备的JVM(Java virtual machine)。正是JVM的存在才使得Java具有“一次编译,处处运行”的特征[5,6],因此,KVM的移植便成为J2ME应用于移动开发的首要前提。
随着编译工具的不断更新,在编译生成KVM的过程中不可避免地出现了一些影响移植结果的新问题。对于这些新问题,一部分KVM移植者沿用较低版本的编译工具以回避版本不兼容问题,而其他移植者虽然处理了新版本编译器带来的错误但却忽视了其产生的大多数警告。这样做要么使得移植后的KVM与最新编译的J2ME应用仍可能产生版本不兼容问题,要么使得被忽略的那些警告可能导致程序bug从而降低了移植后KVM的程序健壮性或鲁棒性。
为了使移植后的KVM能够稳定健壮地运行最新编译的J2ME应用,本文将采用当前主流的编译工具对移植到ARM9平台上的KVM源码进行编译,同时对编译产生的所有错误和警告进行分析和研究,并给出消除这些错误和警告的适当解决方法。
1 KVM的代码执行机制
为了更好地理解KVM的移植过程,我们需要对KVM的代码执行机制作一些研究。
我们先来看一下JVM的代码执行机制,在此基础上才能更好地理解KVM的代码执行机制。
[Java源程序
(*.java)][Java字节码
(*.class)][Java API类库
(*.class)][Java虚拟机
(内含interpreter)][操作系统及电脑硬件] [Bytecode结构] [编译] [运行] [翻译成本地代码执行][加载及校验][加载及校验]
图1JVM的代码执行机制
在图1中,文本格式的Java源程序(文件后缀名为.java)由Java编译器编译为Java字节码文件(文件后缀名为.class),且每一个Java类对应一个.class文件。
在运行阶段,Java虚拟机先装入或加载指定的“.class”文件,进行必要的校验,然后找到指定的入口方法(例如main),由interpreter翻译运行“.class”文件中的字节码[7]。虚拟机在执行过程中根据需要(如创建新对象等)动态加载用户程序的其他“.class”文件或者系统类库的“.class”文件并校验和运行[8]。Java API类库(一个大型的现成软件组件(类)集合)是一些为用户程序运行提供支持或起辅助作用的“.class”格式的标准的Java类,它是Java语言的一个标准组成部分。加载过程通常都是被推迟到必要的时候才进行。
JVM中的interpreter一般采用解释的方式执行字节码。它按照程序执行的顺序逐条取出指令字节码,翻译成一段等效的本地代码序列来执行,这个解释执行过程一直重复到程序的最后一条指令执行完为止[7]。
下面我们再来看一下KVM的代码执行机制(如图2所示)。
[MyApp.Java][javac] [MyApp.class][preverifier] [预验证过的
MyApp.class][verifier] [interpreter][KVM] [下载到目标设备][预验证过的
核心API类库
(*.class)][用JCC处理得到与api类库对应的两个.c文件][JCC][prelink/prelode][指定CLDC类库][指定CLDC类库]
图2KVM的代码执行机制
我们知道,KVM需要运行在硬件资源受限的嵌入式设备上,因此有必要对虚拟机的功能进行简化,提高其运行效率,并减小其体积。为此,将虚拟机原本复杂而耗资源的大部分校验工作移出虚拟机,交由PC开发平台上一个工具软件preverify来进行预验证或预校验,从而减轻移动设备的负担,而在KVM中只留下一个简单的轻量级的校验器[6,9]。
另一方面,由于嵌入式设备硬件资源的限制和减小体积的需要,CLDC只包括了必要的Java核心类库。同时,出于安全性考虑,需要将PC开发平台上由preverify预验证过的CLDC类库“打包”存放到虚拟机中去。这将由JCC (JavaCodeCompact)工具软件来负责对预验证过的CLDC类库进行处理,并将其预连接或预加载(prelink/preload)到KVM中。这样做也可以避免动态加载这些类运行时的开销。
因此,我们在编译和预验证用于KVM的Java代码时,需要将-classpath选项指定为CLDC类库的路径,然后将预验证过的class文件下载到目标设备由KVM来执行。
2 CLDC参考实现
移植所用的KVM源码是来自SUN公司的CLDC 1.1参考实现。表1中描述了该CLDC 1.1参考实现源码包中的目录结构[4]。
表1CLDC 1.1参考实现的目录结构
[[子目录&说明&api&CLDC需要的核心Java类库源代码&bin&包含kvm等所有二进制执行文件和已编译好的核心Java类库的class文件&build&用于编译生成面向不同操作系统平台的KVM的makefile&doc&相关的说明文档&jam&KVM的可选组件JAM(Java Application Manager)的源码&kvm&面向不同平台的KVM的源码&tools&一些需要用到的工具软件的源码,如JavaCodeCompact,preverifier,KDWP Debug Proxy&]]
3 CLDC参考实现的build过程
KVM移植主要是采用合适的编译工具对CLDC 1.1参考实现中的源码进行编译,以生成运行在目标平台上的kvm可执行文件。
用于源码build的Makefile文件根据源码目录的层次结构相应地被组织成一个层次结构。主Makefile文件放在build目录中,由这里跳转去执行其它Makefile文件。在build目录中,根据目标平台上操作系统的不同类型分别对应有不同的主Makefile文件。
在主Makefile文件中可以确定要参与build的代码段,api部分是必选的,kdp和jcc都是可选的。因为要应用于嵌入式系统,故未选择kdp。对于JAM(Java Application Management),为简化起见没有选择该部分。所有的选择可以通过修改Makefile文件中的相关开关选择项值进行,也可在命令行中用参数覆盖。
以ARM+Linux为目标平台对CLDC参考实现进行build的操作很简单,只需进入build/linux目录,通过make命令执行那个主Makefile文件,就开始build过程了。下面的图3描述了其build过程。
[编译生成preverify工具
(x86平台可执行文件)][编译和预验证api classes,
在压缩得到class.zip文件
(预验证过的api类库)][编辑生成JCC工具(Java可执行程序),
用JCC处理class.zip得到两个.c文件,
nativeFunctionTableUnix.c
和ROMjavaUnix.c][将kvm的*.c文件
编译为*.o目标文件][将api类库对应的两个.c文件
编译为两个.o目标文件][将kvm的*.o文件和api类库对应的两个.o文件
连接在一起,生成kvm(ARM平台可执行文件)] [编译生成kvm,同时将api类库预连接到kvm中]
图3以ARM+Linux为目标平台的build过程
4 CLDC参考实现的源码编译
KVM移植所采用的编译环境为:Java编译器Javac 1.6.0_22,(Fedora9自带)X86平台Gcc 4.3.0编译器和交叉编译器Arm-Linux-Gcc 4.3.2。
首先,KVM的运行平台是ARM+Linux平台,所以需要在KVMVmUnixuild目录中将用于编译KVM的Makefile文件中的编译器设置为交叉编译器Arm-Linux-Gcc[10],即:
ifeq($(GCC),true)
CC=arm-linux-gcc
而preverify等工具软件是运行在X86平台上的,故无需改变其原采用的编译器。表2列出了执行make命令的过程中各个编译对象所采用的编译器。
表2各个编译对象所采用的编译器
[[编译对象&运行平台&采用编译器&Preverify Tool&X86&Gcc&Api Classes&NA&Javac&Jcc Tool&X86&Javac&KVM&Arm+Linux&Arm-Linux-Gcc&]]
其次,在最终对KVM源码进行编译之前,需要先对所需工具软件和API类库进行编译。因此,CLDC源码编译过程包括以下四个阶段:编译生成preverify工具,编译和预验证API类库,编译生成JCC工具,编译和连接生成KVM。
下面我们对CLDC源码编译过程中各阶段产生的errors和warnings进行分析,并给出适当的解决方法。
4.1 编译生成preverify工具
在使用X86 Gcc编译tools/preverifier目录下的C源文件时,编译产生的errors & warnings及其解决方法可以见表3。
表3编译生成preverify时的错误和警告及其解决方法
[[编译错误和警告&解决方法&warning 1:
内嵌函数exit的不兼容隐式声明&添加语句“#include
传递给iconv函数的参数2来自于不兼容指针类型&将第2个参数from另存为指向非const类型的指针,并改为传递该指针&]]
对于表3中给出的解决方法,说明如下。
⑴ warning 1:因为Gcc对函数exit的隐式声明和内置函数exit不兼容,所以需要在convert_md.c文件中添加语句“#include
⑵ warning 2:用于字符编码转换的iconv函数的声明为size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);
因为iconv函数会修改第2个参数inbuf和第4个参数outbuf指针所指向的地方,而源码中传递给iconv函数的第2个参数from是指向const char类型的,其值是不能改变的,所以有警告。
解决方法是将参数from另存为指向非const类型的指针并改为传递该指针,如下所示。
char*from2=(char*)from; //需要进行显示类型转换
ret=iconv(ic, &from2, &ileft, &to, &oleft);
4.2 编译和预验证api类库
在使用Javac编译api目录下的Java源文件时,编译产生的errors & warnings及其解决方法可以见表4。
表4编译api类库时的错误和警告及其解决方法
[[编译错误和警告&解决方法&warning:
不能映射为ASCII编码的字符&在api/Makefile文件中,对于javac命令添加“-encoding Cp1252”选项&error:
无法访问java.lang.StringBuilder类文件&在api/Makefile文件中,对于javac命令添加“-source 1.4”选项&]]
对于表4中给出的解决方法,说明如下。
⑴ warning:在将Java源文件编译为.class文件之前,JDK需要将源程序文本文件从原编码格式统一转换为Java内部默认的Unicode格式。
如果不指定Java源文件编码格式,JDK会以操作系统的默认编码格式作为转换前源文件的编码格式。这里编译环境所用Linux操作系统的默认编码格式是UTF-8(对于英文字母就是ASCII编码),因此该warning说明转换前源文件编码格式不是UTF-8。
在vi/vim中可用“:set fileencoding”命令查看源文件字符编码格式,结果为fileencoding=latin1,这表示源文件采用的是“Cp1252:Windows Latin-1”编码。所以,在api目录下的Makefile文件中,对于javac命令需要使用“-encoding Cp1252”选项指定源文件编码格式。
⑵ error:java.lang.StringBuilder类是JDK1.5之后出现的[11]。而核心api类库源文件的版本低于JDK1.6编译器的版本,所以要在javac命令中使用“-source 1.4”选项指定源码版本。
4.3 编译生成jcc工具
在使用javac编译tools/jcc目录下的Java源文件时,编译产生的errors & warnings及其解决方法可以见表5。
对于表5中给出的解决方法,说明如下。
⑴ warning 1:同表4中的warning。
⑵ error:根据代码可以判断这里的enum是用作标识符,所以要在javac命令中使用“-source 1.4”选项指定源码版本(之后enum处会产生false warning)。
⑶ warning 2:可以用Java SE 6 API中的类来替换这两个Sun专有API类,需要改写相关源代码。为了简化起见这里暂不处理。
表5编译生成jcc工具时的错误和警告及其解决方法
[[编译错误和警告&解决方法&warning 1:
不能映射为ASCII编码的字符&在tools/jcc/Makefile文件中,对于javac命令添加“-encoding Cp1252”选项&error:
JDK1.5之后,'enum'是关键词,不能被用作标识符&在tools/jcc /Makefile文件中,对于javac命令添加“-source 1.4”选项&warning 2:
使用了Sun公司专有的API类,即sun.misc.Compare和sun.misc.Sort,未来版本中可能会被移除&目前版本可以支持,暂不处理。&]]
4.4 编译和连接生成kvm
在使用交叉编译器Arm-Linux-Gcc编译KVM目录下的C源文件时,编译产生的errors & warnings及其解决方法可以见表6。
表6编译生成kvm时的错误和警告及其解决方法
[[编译错误和警告&解决方法&由两个静态函数的预先定义的位置不当引起的一系列错误和警告&将这两个静态函数的预先定义移出所在函数体,并放在该函数体前面&warning 1:
整型常数对于long类型太大&在这些整型常数或整型常量代表的常数后面加后缀LL&warning 2:
有三个变量可能会在未初始化赋值的情况下被使用&不作处理,因为程序本身已经保证了正确性&warning 3:
定义了但没有使用的variable和label&不影响程序正确性,可以不处理&error:
_FPU_EXTENDED和
_FPU_DOUBLE没有被声明&选择不编译相关的两个语句,或者将这两个语句都注释掉&]]
对于表6中给出的解决方法,说明如下。
⑴ GCC 4.0以上版本不支持将一个静态函数的预先定义直接放到另一个函数的函数体内部。因此,需要将两个静态函数function 'Vfy_verifyMethod'和function 'Vfy_checkNewInstructions'的预先定义移出到所在函数体的前面。
⑵ warning 1:因为32位机器的int和long类型都是32位,这些报警的整型常数已超出32位表示的范围,所以它们是64位整型常数,需要在这些整型常数或整型常量代表的常数后面加后缀LL(long long类型,交叉编译器支持该类型)。
⑶ warning 2:报警的原因是,在变量的定义和使用之间存在switch或if-else等多分支语句,而且多分支语句中存在没有对变量赋值的分支或路径。这些变量分别是'typeKey'、'targetClassKey'和'thisClass'。经检查,程序中可以保证这些变量在有效使用前都已被初始化赋值过,故无需处理。
⑷ warning 3:略。
⑸ error:在kvm/VmUnix/h目录下的machine_md.h文件中,将编译开关选项PROCESSOR_ARCHITECTURE_X86的定义改为0,选择不编译函数function 'InitializeFloatingPoint'内的两个语句(也可以简单注释掉这两个语句)。因为该函数只是用来设置X86 CPU集成的FPU的精度模式,所以不编译这两个语句并不影响针对ARM处理器所生成的KVM对浮点的支持。
5 移植结果测试
移植测试采用的目标开发板平台为S3C2440+Embedded Linux 2.6.32.2。
按以上方法解决编译时产生的errors和warnings后,可以得到一个KVM可执行文件。在Linux终端中输入命令:file kvm,将显示该KVM是一个ARM平台的可执行文件,需要下载到ARM开发板上运行。
编写一个“Hello World!”Java源程序,编译和预验证时指定-classpath选项为CLDC类库。在Linux终端中输入如下命令:
[root@tom home]# javac -source 1.4 -classpath/home/
j2me_cldc/api/classes -d /home/examples Hello.java
[root@tom home]# preverify -classpath/home/j2me_cldc/api/
classes -d ./home/examples
这里要注意Javac命令要加选项-source 1.4,指定源码版本。
最后,将预验证过的Hello.class下载到开发板上KVM可执行文件所在的目录中,在开发板终端中执行命令:./kvm Hello,可以看到“Hello World!”的输出,如图4所示。这说明KVM移植成功(该KVM支持浮点运算)。
图4KVM移植结果测试
6 结束语
本文采用目前主流版本的编译器来移植KVM,对KVM源码编译各个阶段所产生的错误和警告进行了深入分析,并给出了消除它们的适当解决方法。这些解决方法一方面消除了主要由编译器版本更新导致的错误,实现了KVM的成功编译;另一方面消除了那些可能会导致KVM程序bug的警告,提高了移植后KVM程序的健壮性或鲁棒性。KVM移植结果的测试实验表明,按本文所述解决方法消除相关错误和警告后编译所得的KVM被成功地移植到了ARM9嵌入式平台上。
本文可以为以后新编译环境下成功移植KVM提供有益的参考。为了在ARM9平台上实现J2ME的应用,我们进一步的工作将是在成功移植功能完整的KVM的基础上移植MIDP 2.0。
参考文献:
[1] 冯东,罗蕾.MTK系统下的J2ME运行平台设计[J].单片机与嵌入式
系统应用,2009.4.
[2] Sun Microsystem,Inc.Connected Limited Device Configuration
(CLDC).http://java.sun.com/products/cldc/.
[3] Sun Microsystem, Inc. Mobile Information Device Profile(MIDP).
http://java.sun.com/products/midp/.
[4] Sun Microsystems, Inc. KVM Porting Guide, CLDC 1.1, J2ME,
March 2003.
[5] Sun Microsystems, Inc. The Java Virtual Machine Specification,
Second Edition,1999.
[6] 周显军,李众立,张俊然.基于S3C4510B芯片KVM虚拟机的移植和
测试[J].微计算机信息,2007.23(10-2).
[7] 马嘉,周明天,陈虹.一种基于ARM7的嵌入式Java虚拟机性能优化
技术研究[J].计算机应用研究,2007.24(5).
[8] Bill Venners.曹晓刚,蒋靖译.深入Java虚拟机[M].机械工业出版社,
2007.
[9] 叶磊,陈榕,赵岳松.KVM在基于构件的嵌入式操作系统上的移植和
研究[J].计算机应用研究,2005.9.
[10] 袁文菊,孙天泽,李梅.Java虚拟机向ARM平台的移植[J].微计算机
信息,2007.23(8-2).
[11] Sun Microsystem, Inc. Java Platform, Standard Edition 6 API
Specification,http://download.oracle.com/javase/6/docs/api/.