Android跨平台移植经验之谈
原创: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 00000000at 00000007v0 00060000v1 555b9c80
I/DEBUG(43):a0 555ba070a1 b8500001a2 321071cda3 00000001
I/DEBUG(43):t0 31e83ed0t1 2e69b308t2 2d634a70t3 2e68cf52
I/DEBUG(43):t4 00000000t5 ffffffect6 00000001t7 00000001
I/DEBUG(43):s0 00000000s1 555b9c18s2 00000008s3 00000018
I/DEBUG(43):s4 00000000s5 00000000s6 31e83e90s7 00000000
I/DEBUG(43):t8 fffffffft9 2fdbceb0k0 00000000k1 00000000
I/DEBUG(43):gp 2baa17d0sp 31f9fb50s8 0000004cra 2b9993b4
I/DEBUG(43):hi 00554909lo 00000000 bva 2ab9d3c0 epc 2fdbceb4
I/DEBUG(43):
I/DEBUG(43): backtrace:
I/DEBUG(43):#00pc 0003ceb4/system/lib/libjavacore.so
I/DEBUG(43):#01pc 000293b0/system/lib/libdvm.so (dvmPlatformInvoke+340)
I/DEBUG(43):
I/DEBUG(43): stack:
I/DEBUG(43):31f9fb10003fffff
I/DEBUG(43):31f9fb140000b97a
I/DEBUG(43):31f9fb180000c680
I/DEBUG(43):31f9fb1c2bef11c0/dev/ashmem/dalvik-heap (deleted)
I/DEBUG(43):31f9fb2000001204
I/DEBUG(43):31f9fb2400000000
I/DEBUG(43):31f9fb2800000000
I/DEBUG(43):31f9fb2c00000000
I/DEBUG(43):31f9fb3000000000
I/DEBUG(43):31f9fb3400000000
I/DEBUG(43):31f9fb3800000000
I/DEBUG(43):31f9fb3c00000000
I/DEBUG(43):31f9fb4000000001
I/DEBUG(43):31f9fb442ba9c1c8/system/lib/libdvm.so (gDvm+896)
I/DEBUG(43):31f9fb4800000001
I/DEBUG(43):31f9fb4c00000000
I/DEBUG(43):#0031f9fb502b9e4948/system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+488)
I/DEBUG(43):................
I/DEBUG(43):#0131f9fb502b9e4948/system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+488)
I/DEBUG(43):31f9fb54555b9c18[heap]
I/DEBUG(43):31f9fb582fdbceb0/system/lib/libjavacore.so
I/DEBUG(43):31f9fb5c2d547f00/dev/ashmem/dalvik-LinearAlloc (deleted)
I/DEBUG(43):31f9fb60555b9c18[heap]
I/DEBUG(43):31f9fb64555b9c08[heap]
I/DEBUG(43):31f9fb6800000000
...
从中可以看到”epc 2fdbceb4“这条信息,表示发生错误的pc指向2fdbceb4,这是谁的空间呢,再看
I/DEBUG(43): backtrace:
I/DEBUG(43):#00pc 0003ceb4/system/lib/libjavacore.so
I/DEBUG(43):#01pc 000293b0/system/lib/libdvm.so (dvmPlatformInvoke+340)
这3行信息,pc是从#01跳到#00的,而#00的pc偏移量刚好是epc的最后几位,所以libjavacore.so就是发生错误的动态库。用objdump反汇编libjavacore.so,查看偏移量指向的指令是
3ceb4:60660000ld.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) {
284jint result = *cast
285if (swap) {
286result = bswap_32(result);
287}
288return result;
289 }
284行是jint result = *cast
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系统已基本上运行起来!
【Android跨平台移植经验之谈】到此,本文也应该到了收笔之处。感谢大家一直以来的关注和支持!
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 带有Hilt的Android上的依赖注入
- android|android studio中ndk的使用
- Android事件传递源码分析
- RxJava|RxJava 在Android项目中的使用(一)
- Android7.0|Android7.0 第三方应用无法访问私有库
- 深入理解|深入理解 Android 9.0 Crash 机制(二)
- android防止连续点击的简单实现(kotlin)
- Android|Android install 多个设备时指定设备