Android跨平台移植经验之谈

来源:互联网 发布:出名的网络作家 编辑:程序博客网 时间:2024/06/04 18:57

 原创:M_r_D

转载请注明出处!

Part 1

    元旦休息,闲来无事,又暂无睡意,写点东西捣鼓捣鼓吧!学的东西多了,就怕忘记以前的知识,所以还是记下来比较好。正所谓,好记性不如烂笔头!目前做的一个项目是移植android4.2,所以刚好可以把移植的经验跟大家分享分享,共同进步。尽管界面还没启动起来,但相信到那一天应该不远了。

    标题自称为跨平台移植,那么究竟怎样跨平台了呢?出于公司利益的考虑,这里只透露一点点吧!我们现在所用的cpu(面向嵌入式)是公司自主研发的,市场上暂时还没有,其指令架构不同于市场上任何的cpu架构,例如arm、mips等。由于google谷歌官方不支持我们的cpu架构,而我们又想跑android系统,所以就需要把它移植到我们的平台上。架构不同,必然要使用不同的编译器,公司也自己开发了一个编译器。

    android使用的kernel也是linux,所以在移植android之前,必须先把linux kernel移植好,这部份工作由kernel团队的成员完成,我很少参与,这里大概说个情况吧。首先就是要选择kernel的版本,在google官方,JellyBean(android 4.x版本的代号)搭配的kernel是3.0.x版本的,所以android4.2至少应该选择一个3.0或以上的kernel。我们选择的是3.4,为何要选择如此高版本的kernel?因为android与kernel的版本更新太快了,我们不想google官方出一个新版本,我们就要重新移植一次kernel,所以就选择版本比较高的kernel,用来兼容未来的android版本。版本定下来后,还要考虑用哪里的linux源代码,因为目前支持android的kernel有多种多样,这些都是被各大芯片厂商或者手机厂商修改过的,所以要选择最合适自己架构的kernel。kernel源码下载下来后,把里面架构相关的代码修改成自己平台的代码,其实主要是汇编级的代码。修改完毕后,做一个根文件系统,让kernel能启动到shell。最后就是开发各种驱动,不断地支持android的移植工作。

    对于android这边的工作,首先下载4.2的源码,用git管理,源码里面有多个分支,我们一般不会跟着master分支走,因为master分支每天都有更新,很难维护。一般checkout到某个分支上,例如android-4.2_r1。android4.2里面支持3种架构:arm、mips、x86,它们的代码都混杂在一起,编译系统是如何区分它们的?是通过android自己的Makefile区分的,在一些目录下面的Android.mk文件中,可以看到里面有语句判断是哪个架构的;还有,在一些头文件(xx.h)里面也能看到#ifdef或者#ifndef等,这些架构信息都是在编译时通过读取环境变量得到的,编译系统会根据不同的环境变量来编译需要的代码。所以,我们需要修改android的编译脚本,把自己的架构添加进去,具体修改的地方有build/和device/目录下的编译脚本等。修改后的效果是,在编译前,执行source build/envsetup.sh,然后lunch,能看到自己的平台,这是最基本的一步。跟着,我们要了解有哪些模块涉及到架构相关的代码。经过一番摸索,终于知道了有哪些模块跟架构相关,这里给各位看官列出来:

1、bionic:仿生库,其实这个不用说大家也知道,bionic提供libc、libstdc++等函数库以及一些系统调用,里面很多头文件都由kernel导过来,所以必然有架构相关代码。要想android能够跑起来,bionic一定要移植成功。其实bionic的移植工作非常困难,公司是由一个有多年工具链开发经验的同事完成的。

2、llvm:一个编译器的后端,前端是clang。clang是apple公司开发的一款编译器,貌似性能要比gcc好。llvm也会在GPU那一块用到。

3、v8:JavaScript引擎,在浏览器中用到,分为解析执行和编译执行,解析执行时会产生汇编代码,所以要修改汇编代码生成器。

4、libbcc:RenderScript需要依赖于libbcc和llvm。

5、RenderScript:由于2和4的原因,所以RenderScript也涉及架构相关的代码。RenderScript是一个3D的画图库,功能类似于openGL,虽然没有openGL强大,但是其开发流程比较简单,而且拥有高性能的3D渲染效果。主要用在动态壁纸的实现等。

6、dalvik:android的java虚拟机,这家伙也是一大块啊,涉及很多编译原理。它支持3种模式:portable,fast,jit。

可能还有些跟架构相关的模块没列出来,本人目前只记得这些了,欢迎补充!android的启动不需要依赖于上面所有的模块,所以我们的计划是先把必须的模块移植好,先让系统起来。在上面的模块中,我们只需要移植好bionic和dalvik就能把系统运行起来。

    必要的架构相关的模块移植好后,下一步要做的是编译一个android的最小系统,并把镜像做出来。编译的话,只能编到哪里出错就解决哪里的问题了。如果人手足够的话,可以把最小系统的模块分工好,让其他人员一个模块一个模块地扫过去,把不能编译的都修复好。修复的内容主要是,缺少架构的定义,缺少头文件,缺少某些变量和宏的定义等。这些一般都是修改一些xx.mk文件添加架构支持,或者在bionic中导入所需要的头文件,定义变量、宏等。这样分工,能加快进展速度。

    最小系统编译完成后,就要做image,烧到SD卡或者nanflash中启动,什么样的根文件系统格式配什么样的镜像,大家要弄清楚。

    启动的第一步当然是启动init进程,接着是把必要的服务运行起来,直到shell能用。这时,就能调试servicemanager和surfaceflinger等服务了,幸运的话,一般起来都不会有太大的问题。surfaceflinger可以用bootanimation验证它的功能,如果看到android的文字logo,就证明surfaceflinger能工作。如果shell都不能启动的话,可以跑一下在android源码中system/extra/tests下面的测试程序,主要是测试kernel是否为启动android作了足够的准备。在jni层的服务都能启动后,接下来就是调试zygote了,zygote里面开始进入java世界,调试zygote是最难的一关,总会遇到各种segmentfault,很恐怖的说。。。。。

    目前的进展只到这里,等到系统的界面出来后再跟大家接着分享吧!!


=============================================================================

Part 2

    经过一翻折腾后,终于看到界面了,尽管很难看!呵呵!导致这一问题的原因是RenderScript没移植好,另外,还有部分的服务没启动起来。接下来是把其它服务和模块逐一地移植上去,启动界面的过程中遇到挺多问题的,有空再跟大家分享!先上图!左边紫色那块的数字是时间9:xx。


问题:

启动界面遇到一个最重要的错误是产生了不对齐的地址访问,这个不对齐的地址访问会导致segment fault(段错误),打印的log如下:

page_fault() #2: sending SIGSEGV to ActivityManager for invalid read access from
31f90000 (epc == 2fdbceb4, ra == 2b9993b4)
F/libc    (  272): Failed while talking to debuggerd: Bad file number
I/DEBUG   (   43): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   (   43): Build fingerprint: 'Android/mini_duck/duck:4.2/JOP40C/eng.zengdaquan.20130104.113607:eng/test-keys'
I/DEBUG   (   43): Revision: '0'
I/DEBUG   (   43): pid: 272, tid: 287, name: ActivityManager  >>> system_server <<<
I/DEBUG   (   43): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 31f90000
I/DEBUG   (   43):  zr 00000000  at 00000007  v0 00060000  v1 555b9c80
I/DEBUG   (   43):  a0 555ba070  a1 b8500001  a2 321071cd  a3 00000001
I/DEBUG   (   43):  t0 31e83ed0  t1 2e69b308  t2 2d634a70  t3 2e68cf52
I/DEBUG   (   43):  t4 00000000  t5 ffffffec  t6 00000001  t7 00000001
I/DEBUG   (   43):  s0 00000000  s1 555b9c18  s2 00000008  s3 00000018
I/DEBUG   (   43):  s4 00000000  s5 00000000  s6 31e83e90  s7 00000000
I/DEBUG   (   43):  t8 ffffffff  t9 2fdbceb0  k0 00000000  k1 00000000
I/DEBUG   (   43):  gp 2baa17d0  sp 31f9fb50  s8 0000004c  ra 2b9993b4
I/DEBUG   (   43):  hi 00554909  lo 00000000 bva 2ab9d3c0 epc 2fdbceb4
I/DEBUG   (   43):
I/DEBUG   (   43): backtrace:
I/DEBUG   (   43):     #00  pc 0003ceb4  /system/lib/libjavacore.so
I/DEBUG   (   43):     #01  pc 000293b0  /system/lib/libdvm.so (dvmPlatformInvoke+340)
I/DEBUG   (   43):
I/DEBUG   (   43): stack:
I/DEBUG   (   43):          31f9fb10  003fffff
I/DEBUG   (   43):          31f9fb14  0000b97a
I/DEBUG   (   43):          31f9fb18  0000c680
I/DEBUG   (   43):          31f9fb1c  2bef11c0  /dev/ashmem/dalvik-heap (deleted)
I/DEBUG   (   43):          31f9fb20  00001204
I/DEBUG   (   43):          31f9fb24  00000000
I/DEBUG   (   43):          31f9fb28  00000000
I/DEBUG   (   43):          31f9fb2c  00000000
I/DEBUG   (   43):          31f9fb30  00000000
I/DEBUG   (   43):          31f9fb34  00000000
I/DEBUG   (   43):          31f9fb38  00000000
I/DEBUG   (   43):          31f9fb3c  00000000
I/DEBUG   (   43):          31f9fb40  00000001
I/DEBUG   (   43):          31f9fb44  2ba9c1c8  /system/lib/libdvm.so (gDvm+896)
I/DEBUG   (   43):          31f9fb48  00000001
I/DEBUG   (   43):          31f9fb4c  00000000
I/DEBUG   (   43):     #00  31f9fb50  2b9e4948  /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+488)
I/DEBUG   (   43):          ........  ........
I/DEBUG   (   43):     #01  31f9fb50  2b9e4948  /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+488)
I/DEBUG   (   43):          31f9fb54  555b9c18  [heap]
I/DEBUG   (   43):          31f9fb58  2fdbceb0  /system/lib/libjavacore.so
I/DEBUG   (   43):          31f9fb5c  2d547f00  /dev/ashmem/dalvik-LinearAlloc (deleted)
I/DEBUG   (   43):          31f9fb60  555b9c18  [heap]
I/DEBUG   (   43):          31f9fb64  555b9c08  [heap]
I/DEBUG   (   43):          31f9fb68  00000000
...


从中可以看到”epc 2fdbceb4“这条信息,表示发生错误的pc指向2fdbceb4,这是谁的空间呢,再看

I/DEBUG   (   43): backtrace:
I/DEBUG   (   43):     #00  pc 0003ceb4  /system/lib/libjavacore.so
I/DEBUG   (   43):     #01  pc 000293b0  /system/lib/libdvm.so (dvmPlatformInvoke+340)

这3行信息,pc是从#01跳到#00的,而#00的pc偏移量刚好是epc的最后几位,所以libjavacore.so就是发生错误的动态库。用objdump反汇编libjavacore.so,查看偏移量指向的指令是

3ceb4:   60660000    ld.w    $3,0($6)

这条指令的意思是读取6号寄存器偏移量为0的内容,放到3号寄存器,我们可以从上面打印的log中看到6号寄存器的内容是321071cd,16进制的,最后一位d表示13,是基地址,我现在用的cpu不支持不对齐的访问,所以就产生了segment fault。

如何处理这个错误!?

首先是调查哪句代码产生了不对齐的访问,用命令“addr2line -e libjavacore.so 3ceb4”可以跟踪到具体哪行代码(addr2line的使用请参考其它资料,这里不再说明)。查看到的代码是libcore/luni/src/main/native/libcore_io_Memory.cpp:284

283 static jint Memory_peekInt(JNIEnv*, jclass, jint srcAddress, jboolean swap) {
284     jint result = *cast<const jint*>(srcAddress);
285     if (swap) {
286         result = bswap_32(result);
287     }
288     return result;
289 }

284行是jint result = *cast<const jint*>(srcAddress),上面说到读取6号寄存器时产生了错误,6号寄存器是a2,也就是函数的第3个参数,这里srcAddress刚好是第3个参数,所以符合产生错误的逻辑。既然直接读取srcAddress的值会产生不对齐的访问,那么我们就换另外一种方式去读取。有那些方式可以避免不对齐的访问呢?可以用memcpy,它读取内容的方式是一个字节一个字节的读取的,不理会地址是否对齐。所以用它来间接读取srcAddress就能解决这一问题,代码修改后如下:

static jint Memory_peekInt(JNIEnv*, jclass, jint srcAddress, jboolean swap) {
     jint result;
     memcpy((void*)&result, (void*)(srcAddress),sizeof(jint));
     if (swap) {
         result = bswap_32(result);
     }
     return result;
  }

其实,这只是一个暂时的解决办法,不对齐的操作应该还是在kernel里面处理的,现在只能等到kernel团队的人有空的时候再做修改。

=============================================================================

Part 3

修了一个bug之后,能显示成这样子了,没有显示背景主要是因为wallpaper service没启动好。Go ahead~


===========================================================================

Part 4

架构相关的模块移植好之后,剩下的都是一些很零散的问题,例如android跟kernel的磨合,驱动的debug,android自身的各种服务和它们之间的机制问题等!只能一个一个地去修bug了!目前android系统已基本上运行起来!

到此,本文也应该到了收笔之处。感谢大家一直以来的关注和支持!