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,能看到自己的平台,这是最基本的一步。跟着,我们要了解有哪些模块涉及到架构相关的代码。经过一番摸索,终于知道了有哪些模块跟架构相关,这里给各位看官列出来:
-
bionic:仿生库,其实这个不用说大家也知道,bionic提供libc、libstdc++等函数库以及一些系统调用,里面很多头文件都由kernel导过来,所以必然有架构相关代码。要想android能够跑起来,bionic一定要移植成功。其实bionic的移植工作非常困难,公司是由一个有多年工具链开发经验的同事做的。
-
llvm:一个编译器的后端,前端是clang。涉及编译器相关知识,不太了解,clang是apple公司开发的一款编译器,貌似性能要比gcc好。
-
v8:JavaScript引擎,在浏览器中用到,分为解析执行和编译执行,解析执行时会产生汇编代码,所以要修改汇编代码生成器。
-
libbcc:RenderScript需要依赖于libbcc和llvm。
-
RenderScript:由于2和4的原因,所以RenderScript也涉及架构相关的代码。RenderScript是一个3D的画图库,功能类似于openGL,虽然没有openGL强大,但是其开发流程比较简单,而且拥有高性能的3D渲染效果。主要用在动态壁纸的实现等。
-
dalvik:android的java虚拟机,这家伙也是一大块啊,涉及很多编译原理。
可能还有些跟架构相关的模块没列出来,本人目前只记得这些了,欢迎补充!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(srcAddress);
285 if (swap) {
286 result = bswap_32(result);
287 }
288 return result;
289 }
|
284行是jint result = *cast(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系统已基本上运行起来!
到此,本文也应该到了收笔之处。感谢大家一直以来的关注和支持!
|