一、启动镜像制作
上一讲当中我们总结了,Android系统运行所需要的三大部分:bootlader,kernel和ramdisk.那我们怎么才能获得它们呢?接下来我们来看看怎么制作bootlader,kernel和ramdisk.
Build bootloader
bootloder我们选用友善之臂提供的uboot_tiny4412
a) 安装好toolchain (arm-linux-gcc-4.5.1-v6-vfp-20120301.tgz)并设置好
环境变量PATH,保证可以正常使用。
b) 解压 uboot_tiny4412-20130729.tgz并进入相应的目录
tar xzf uboot_tiny4412-20130729.tgz
c) 配置 uboot并编译
cd uboot_tiny4412
make tiny4412_config
make
d) 编译用于生成bl2的工具
make -C sd_fuse
或者
cd sd_fuse; make
这样bootloader就制作好了。
Build kernel
a) 进入linux-3.5的解压目录:cp tiny4412_android_defconfig .config
b) make
对Android的支持做出的修改,见另一篇文档。
Build Android4.2.2
a) .setenv 中间有个空格,且必须执行这条
b) make
中间的具体错误及处理方法,见另一篇文档。
Build filesystem
大家都知道Android移植需要的三大部分:bootloader,kernel和ramdisk,前两个部分制作较为简单,我就不再赘述了。现在我来说说第三个部分:ramdisk,也就是根文件系统。要制作根文件系统当然离不开Android源码,那我先来分析Android源码编译完成后生成的三个比较重要的镜像文件:ramdisk.img,system.img,userdata.img,它们和我们要制作的根文件系统息息相关,因此我们需要知道它们都有什么用?拿来做什么?
a) emulator: emulator运行时会自动的加载这三个镜像文件,ramdisk.img是根文件系统,system.img包括了主要的包、库等文件,userdata.img包括了一些用户数据,在加载完这三个镜像文件后,会把system和userdata分别加载到ramdisk文件系统中的system和userdata目录下。
那既然这样,emulator仿真器已经可以运行了,是不是就意味着我们的根文件系统以及Android运行所需要的已经准备好了么?答案是:NO。
这三个镜像文件只是emulator运行时加载的,而Android运行所需要的并不是它们.为什么这么说呢?我们先来看看Android源码编译完成后,在<android_home>/out/target/product/generic目录下生成ramdisk.img,system.img和userdata.img的这三个镜像文件,首先查看这写镜像文件的格式:
file *.img
ramdisk.img: gzip compressed data, from Unix
system.img: VMS Alpha executable
userdata.img: VMS Alpha executable
userdata-qemu.img:VMS Alpha executable
从这里就能看出ramdisk.img映像文件是采用cpio打包,gzip压缩的,而其它几个镜像文件都是yaffs2文件系统。那这又跟我们Android移植有什么关联呢?当然有。我们再来看看友善之臂给我们提供的能让Android运行的这几个镜像又是什么格式:
fileramdisk-u.img
ramdisk-u.img: u-boot legacy uImage, ramdisk, Linux/ARM,RAMDisk Image (Not compressed), 675532 bytes, Wed Jun 3 12:13:14 2015, Load Address: 0x40800000,Entry Point: 0x40800000, Header CRC: 0x2660B05F, Data CRC: 0xD045562D
filesystem.img
system.img: data
从这里就可以看出来它们是不一样的,那又怎么能让Android正常运行(源码编译后生成的镜像文件)呢?方法自然有。
b) android:
接下来我们来讨论如何制作能让Android正常运行的镜像文件,首先,自然是我们的重点:ramdisk.img.我尝试了几种不同的方式去制作可用的镜像文件,即:
1. 将源码编译后生成的ramdisk.img制作成ramdisk-u.img
mkdir ram_dir
mv ramdisk.imgramdisk.img.gz
gunzipramdisk.img.gz
fileramdisk.img //cpio
ramdisk.img:ASCII cpio archive (SVR4 with no CRC)
1.1 cd ram_dir
将cpio解压
cpio -idmv<../ramdisk.img
将解压后的文件压缩重新打包为emulator运行需要的镜像文件:
find . | cpio-ov -H newc | gzip > ./ramdisk.img
fileramdisk.img
ramdisk.img:gzip compressed data, from Unix, last modified: Tue Jan 19 11:59:47 2016
1.2 将.gz格式的ramdisk.img.gz转化为安卓运行需要的文件系统ramdisk-u.img:----->将zImage转化为uImage,mkimage工具是u-boot格式uImage内核映像制作工具,uImage就是在zImage的前面加上64字节的头信息
mkimage -A arm-O linux -T ramdisk -C none -a 0x40800000 -n "ramdisk" -dramdisk.img.gz ramdisk-u.img
mkimage
-A ----> 用于指定CPU类型,比如ARM
-O ----> 用于指定操作系统,比如linux
-T----> 用于指定image类型,比如kernel
-C----> 指定压缩类型
-a----> 指定image的载入地址
-e----> 内核的入口地址,一般是:image的载入地址+0x40(信息头的大小)
-n----> image在头结构中的命名
-d----> 无头信息的image文件名
-x----> 设置执行位置
-T 指定映像类型,可以取下列值:
standalone,kernel,ramdisk,multi,firmware,script,filesystem
-C 指定映像压缩方式,可以取下列值:
none 不压缩
gzip 用gzip的压缩方式
bzips 用bzip2的压缩方式
-a 指定映像在内存中的加载地址,映像下载到内存中时,要按照用mkimage制作映像时,这个参数所指定的地址值来下载
-e 指定映像运行的入口点地址,这个地址就是-a参数指定的值加上0x40(因为前面有个mkimage添加的0x40个字节的头)
-n 指定映像名
-d 指定制作映像的源文件
2. 直接将root目录制作成ramdisk.img
(1).mkbootfsroot/ | minigzip > src_system/ramdisk.img
(2).mkimage -Aarm -O linux -T ramdisk -C none -a 0x40800000 -n "ramdisk" -dramdisk.img ramdisk-u.img
制作好了根文件系统,接下来就要准备system.img和userdata.img,也是两种方式:
1.源码编译后的system.img制作成system.img
因为这两种镜像文件是不同的格式,只需要进行格式转换就可以了。Android系统运行所需要的system.img是data格式,也就是ext4文件系统。源码编译完成后生成的system.img是yaffs2文件系统,所以首先需要对yaffs2进行解压,需要用到unyaffs,该软件可以从网上下载。
mkdir system_dir
mkdir src_system
cd system_dir
unyaffs ../system.img //解压源码生成的system.img文件
生成ext4--data文件
make_ext4fs -s -l 330000000 -a systemsrc_system/system.img system_dir
- 直接将system目录制作成system.img
make_ext4fs -s -l 330000000 -a systemsrc_system/system.img system_dir
但是注意:该过程之前需要把/vendor/friendly-arm/exynos4412目录下的busybox-bin.tgz和gapps-jb-20121212.tgz解压到system/目录下
- userdata.img制作和system.img一样,就不再赘述了。
至此,Android系统运行所需要的镜像已经准备好了。可以开始我们的Android系统移植了。
二、烧写Android
两种烧写方式:SD卡脱机烧写,fastboot
1.SD卡脱机烧写
按照友善之臂提供的烧写步骤进行即可。但需注意:友善之臂的superboot开启了三星推荐的TrustZone安全模式,注意内核必须也要启动 TrustZone 模式,才能配合此版本的 Superboot使用,否则将无法启动。
而烧写安卓到SD卡时uboot不支持trustzone,内核必须禁止trustzone后编译才能启动;如果是superboot,此日期(2014-03-17)前的应该能启动,此日期后的则必须是启用trustzone后编译的内核才能启动。
2014-03-17 更新说明:
================================
Superboot4412更新如下:
1)修正了eMMC大小显示不正确的问题;
2)实现了根据eMMC大小进行智能分区,以使Android下的Data分区得到更大的可用空间;
3)开启了三星推荐的TrustZone安全模式,注意内核必须也要启动 TrustZone 模式,才能配合此版本的 Superboot使用,否则将无法启动;
总结:用uboot必须禁止TrustZone安全模式,用superboot必须开启TrustZone安全模式
2.fastboot烧写
uboot界面输入fastboot,PC端输入以下命令进行烧写:
fastboot flash kernel zImage (烧写kernel)
fastboot -w (格式化userdata和cache)
fastboot flash ramdisk ramdisk-u.img (烧写ramdisk)
fastboot flash system system.img (烧写system)
这样,Android系统移植完成。EMMC方式启动Android可能会遇到问题,下面进行总结:
VFS: Cannot open root device"(null)" or unknown-block(0,0): error -6
Kernel panic - not syncing: VFS: Unable tomount root fs on unknown-block(0,0)
该错误是由于内核找不到根文件系统的分区位置。但是一直找不到解决方法,根文件系统无法挂载就不会执行安卓的第一个进程init,因此安卓不能启动。
最初uboot的bootcmd环境变量为:
bootcmd=movi read kernel 0 40008000;bootm40008000
但是内核启动时一直找不到根文件系统,通过设置bootargs参数的root=/dev/mmcblock0p1或者root=/dev/ram等等都不行都会出现类似上面的错误或者下面的错误(卡死在这):
Nosoundcards found.
[ 3.675000] Waiting for root device /dev/mmcblock0p1...
参考EMMC分区方案,又一无所获。后来参照资料更改bootcmd参数结果出现严重错误:找不到内核image
由此得出bootcmd参数应该比较重要。
参照别人的uboot的环境变量参数为下面值后,根文件系统正常挂载,安卓系统正常启动:
bootargs=console=ttySAC0,115200n8 androidboot.console=ttySAC0 uhost0=n ctp=2 skipcali=y vmalloc=512m lcd=S70init=/init root=/dev/mmcblock0p1 rootwait rw
bootcmd=movi read kernel 0 40008000;movi read rootfs 0 41000000 100000;bootm 40008000 41000000
但是还是有点疑问。
安卓系统启动流程:
uboot----->kernel------->ramdisk
(1).UBOOT启动后是根据bootargs参数来启动内核的么?会怎么选择?
bootargs会传给内核,优先使用bootargs
(2).bootcmd参数的作用以及它需要传递给内核还是...?
作为自动执行命令
(3).bootargs和bootcmd在安卓系统启动过程中担任什么角色?它们的功能以及设置好后的执行现象由此决定?
在安卓系统启动前期的uboot的启动和内核启动指定了参数.如果这两个参数设置错误,安卓不能正常启动。
查资料获取bootcmd的作用:
uboot提供两种工作模式:一是启动加载模式,一是下载模式。工作在启动加载模式时,uboot会自动执行bootcmd命令,如bootcmd=movi read kernel 0 40008000;bootm 40008000 uboot首先把内核镜像拷贝到内存地址0x40008000的地方,然后执行bootm0x40008000命令
bootm完成的功能有:
.CRC校验image head struct 的64字节的正确性
.根据镜像压缩类型,把kernel解压到指定的位置(0x40800000)
.调用函数do_bootm_linux(...),启动内核
而在启动内核的时候(do_bootm_linux时),会从参数中获取环境变量bootargs的值,然后再从image head struct中获取kernel的入口地址(0x40800000),最后会把commandline拷贝到指定的COMMAND_LINE地址,最后执行kernel的入口函数,把控制权交给kernel.
既然kernel加上了64K字节头信息,那加载地址和真正的入口地址应该不一样啊?为什么两个地址都设置成0X40800000,还是说UBOOT找到该地址时做了相应的处理?
mkimage时需要加上64K字节头信息,这样UBOOT才能识别这个映像是针对哪个CPU体系结构的,哪个OS的,哪种类型,加载内存中的哪个位置,入口点在内存的哪个位置以及映像名。
在UBOOT中每一个命令都对应一个其实现的函数,在启动linux过程中,主要是执行环境变量bootcmd和bootargs所定义的命令。因此,对这两个变量的定义很重要,如果定义不对就不能正确的加载内核和文件系统。尤其是必须确保文件系统所在分区。为什么找不到文件系统,因为没有告诉uboot--->bootcmd。那为什么要告诉uboot而不是直接告诉kernel呢?因为uboot的命令行参数会传递给内核。
文件系统和根文件系统:
根文件系统镜像直接烧写到EMMC中的第一个块的第一个分区,将根文件系统镜像从EMMC中读到指定地址0x41000000处,然后bootm解压缩到指定位置。
那文件系统是安卓的第一个进程init启动之后建立的么?
既然告诉了uboot那uboot是怎么告诉内核这个文件系统的位置呢?
bootm后调用do_bootm_linux么?
可以不可以不定义bootcmd中文件系统的位置而直接告诉内核让内核自己去找根文件系统?
bootm rootfs那条命令又是什么作用,是像内核那样启动根文件系统么?文件系统的位置和内核一样是固定的么?
bootcmd:是系统在上电自动执行时所执行的命令
bootcmd=movi read kernel 0 40008000;movi read rootfs 0 41000000 100000;bootm 40008000 41000000
这是两条命令:具体功能看上面bootm分析(NOTE:bootm会根据镜像压缩类型将文件解压到指定的地址).
至此,Android系统移植启动镜像的制作已经完成,Android可以正常启动。
未完待续。