Android工程搭建与系统启动


Android工程集成一直是工作中的部分内容,这里简要记述一下Android集成中涉及Kernel、Android修改和配置的要点,在一个全新的板子上配置时需要注意一些什么内容等等。

本文主要技术Android工程集成中需要注意的一些要点


Android源码下载

Platform Source Code

Android AOSP源码下载
Android Version描述

Android源码版本号确认:

  • 编译的时候在终端中一开始就会打印出来:PLATFORM_VERSION:2.3.1
  • 直接去make文件中去看:build/core/version_defaults.mk,搜索该文件中的PLATFORM_VERSION值,然后与上面Android Version描述中对比

Kernel Source Code

Android Kernel源码下载

Ramdisk制作和打解包
① 制作独立ramdisk.img(不启动Android的)

  • 使用busybox制作ramdisk镜像:

    TODO...

  • 人工手动制作ramdisk进行:

    我们用busybox制作了一个miniramdisk可以独立使用,放到了coding代码托管:下载路径
    TODO...

② 打/解包ramdisk.img

  • 解包ramdisk: 得到ramdisk目录,里面包含root/下的所有文件
    mv ramdisk.img ramdisk.img.gz
    gunzip ramdisk.img.gz 
    mkdir ramdisk 
    cd ramdisk 
    cpio -i -F ../ramdisk.img 
  • 打包ramdisk: 将上面解包的ramdisk重新打包生成ramdisk.img
    cd ramdisk
    find . | cpio -o -H newc | gzip > ../ramdisk-new.img

Android工程配置

Platform Config

修改工程代码
① fstab分区表修改:
挂载/data和/cache分区需要通过device/${board}/fstab进行配置。
② system.prop属性配置:

  • ro.hardware:

    通过kernel的bootargs中参数androidboot.hardware=XXXX带入,在init.cpp中会对这个做解析,然后设置到ro.hardware这个属性中。

  • ro.product.hardware:

    通过在build/make/target/board/generic_arm64/system.prop中添加ro.product.hardware=xxxx来定义,将来这个文件的内容会被copy合并到system.img中的property文件中。
    ③ rc文件配置:
    ④ selinux配置:

Kernel Config

修改工程代码
① 串口
② defconfig
③ dts - 需要配置soc上各种东西core、memory、gic、uart、等等
特别需要跟android启动有关的:

* android firmware中配置system、vendor分区
* fb配置,包含它的reserved memory等
    * 这里如果没有屏幕只想通过log验证的话,kernel里面有一个vfb设备可以使用
    * 配置CONFIG_FB_VIRTUAL=y,同时bootargs中带字段video=vfb指定GPU去找vfb这个framebuffer

配置交叉编译工具链
以arm64的版本为例:
① 确认编译的版本是arm64的版本
② 将aarch64-linux-android-4.9工具链路径export到shell环境变量中

export PATH=/home/acean/.buildman-toolchains/aarch64-linux-android-4.9/bin/:$PATH

Android工程编译

Platform Compile

source build/envsetup.sh
lunch 2                     //以generic_arm64为例
make -jN

以上编译完毕之后,会生成如下几个镜像文件:

  • ramdisk.img
  • system.img
  • userdata.img
  • vendor.img
  • cache.img

Kernel Compile

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-android- hikey_defconfig            //以hikey板子为例
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-android-

以上编译完毕之后,会生成如下镜像文件:

  • Image
  • dtb

被Uboot加载的镜像的格式化

这里说“格式化”不知道是否合适,其实目的只是通过uboot提供的工具mkimage来指定镜像加载与运行地址,以便uboot运行后能自动加载或者通过bootm等命令手动加载这些image到内存中,然后跳转并运行。

格式化ramdisk

mkimage -A arm64 -O linux -T ramdisk -C none -a 88080000 -n ramdisk -d ramdisk.img ramdisk_a

格式化Image

mkimage -A arm64 -O linux -T kernel -C none -a 80080000 -e 80080000 -n linux-4.10 -d out/tmparm64/arch/arm64/boot/Image hm_uImage

镜像打包成image文件

这里主要指的是kernel镜像、ramdisk镜像、dtb镜像三者或者二者可以打包在一起成boot.img加载,加载到开发板后再进行解包。

Tips:
或者也可以像上面uboot操作临时单独去分别下载

将uImage与ramdisk一起打包

TODO...

将uImage与ramdisk与dtb一起打包

TODO...


下载镜像到开发板

磁盘分区

从磁盘启动android的话,磁盘需要要存在如下分区:

  • uboot (可选,有的开发板可能固化在rom中)
  • boot (必选)
  • ramdisk (可选,有的开发板可能与kernel打包的一起)
  • dt (可选,有的开发板可能与boot打包在一起)
  • system (必选)
  • vendor (必选)
  • usrdata (必选)
  • cache (必选)

对板子上通过SCSI/PCIE等接口临时外接的HDD/SSD磁盘进行分区和修改分区表:
① GPT分区表: ==> 磁盘分区挂载时走/kernel/block/partitions/efi.c驱动来添加分区
首先,使用diskginus对硬盘进行分区,将以上分区都划分出来,格式化成ext4格式的
其次,修改GPT分区表中PARTNAME(name),使用工具parted,命令格式:

sudo bash  //切换到root用户
parted -l  //查看所有分区表

parted /dev/sdb  //查看image所在的硬盘上分区细节

$$name 6 sda6  //将system区分修改成对应磁盘名字,这里名字对应dts中dev=/dev/block/system,这两边需要一致的,否则从init主动触发的uevent上报会因在sysfs中找不到分区名字而失败

Tips:
注意一般我们会做从/dev/block/system --到-->/dev/block/sda6这种映射
因此通常是是需要将GPT分区表中名字修改成从其它修改成system即可、而并不需要将system再修改成sda6
我这里这样修改,原因是我还没有找到system->sda6映射是在哪里做的,如果不这样修改,会在__mount时找不到对应dev设备

② MBR分区表: ==> 磁盘分区挂载时走/kernel/block/partitions/msdos.c驱动来添加分区
TODO...

对嵌入式板子上通过SDIO等接口固定内部挂接的Nand/eMMC/UFS等存储进行分区和创建分区表:
一般使用SOC厂家提供的特定下载工具(比如展讯的ResearchDownload)按照给定的xml分区表进行分区固化。

① GPT分区表:
TODO...

② MBR分区表:
一般嵌入式板子上面,跑Linux的话通常不使用MBR分区,直接用GPT分区格式。

通过Uboot临时下载Ramdisk镜像

这里并不是必须的,一般我们验证的时候,为了调试方便,可以从uboot下载ramdisk.img和uImage文件。
但是系统固定之后,我们会将ramdisk、uImage等全部放到磁盘上,系统启动时uboot会从磁盘去load这些镜像文件的。

uboot通过tftp下载镜像:
一般我们比较常用的是走网络下载方式,通过host侧建立tftpd server,在uboot侧建立tftp guest,两者通过接入同一个路由器来在同一网段IP地址下通信。

① 电脑PC侧(host)配置tftp server:

  • 在ubuntu上安装tftp
    apt-get install tftp-hpa tftpd-hpa xinetd
  • 配置ftfp server
    vim /etc/default/tftpd-hpa
    
    TFTP_USERNAME="tftp"
    TFTP_DIRECTORY="/tftpboot" ##指定tftp服务目录,将来image放入其中
    TFTP_ADDRESS="0.0.0.0:69"  ##指定IP和端口
    TFTP_OPTIONS="-l -c -s"    ##这里是选项,-c是可以上传文件的参数,-s是指定tftpd-hpa服务目录,上面已经指定 
  • 手动启动tftpd并验证
    sudo /etc/init.d/xinetd restart
    sudo /etc/init.d/tftpd-hpa restart
    ps aux | grep -E 'netd|tftpd'  ##查看一下是否已经有了这两个daemon进程
  • 本地验证上传下载
    cd /tftpboot       ##进入服务目录
    tftp localhost     ##链接本机
    tftp>get test.txt  ##test.txt 是之前在 /tftpboot 目录下新建的文件  
    tftp>put test1.txt ##test1.txt 是在 /home 目录下新建的文件  
    tftp>q             ##退出后,在/home目录下会有一个test.txt文件,在/tftpboot 目录下有test1.txt,表示tftp服务器安装成功!
  • 配置tftpd开机启动

    ps aux | grep -E 'netd|tftpd'  ##查看一下是否已经有了这两个daemon进程

② 开发板侧配置配置uboot环境变量:
在开发板侧,主要设置以下几个uboot中环境变量:

setenv gatewayip 192.168.1.1   ##网关,路由器ip
setenv ipaddr 192.168.1.111    ##开发板ip,需要在路由器配置一下静态ip
setenv serverip 192.168.1.101  ##主机ip,需要在路由器配置一下静态ip

③ 开始下载ramdisk镜像:
通过网口用tftp下载ramdisk镜像

ftfp ramdisk.img 0x????????

Tips:
ramdisk.img可能需要首先经过mkimage进行格式转化,否则bootm不认识
0x????????是uboot中规划的ramdisk放的地址

uboot通过usb下载镜像:
TODO...

uboot通过serial port下载镜像:
TODO ...

uboot通过hard disk下载镜像:
将image放到hard disk分区:
这里只需要直接将image镜像文件放到磁盘任意分区X即可,比如我们放到分区1,比如sda1上,那么使用下面命令下载镜像。

从hard disk分区下载image到开发板:例如ext4load scsi 12:1 0x90100000 hm_uImage

  • 下载命令:ext4load
  • 磁盘分区:scsi 12:1,可以通过scsi info查看编号(12是通过scsi info打印出来的18的十六进制表示,1表示第1个分区)
  • 板子目标地址:0x90100000
  • 镜像文件:hm_Image

通过Uboot临时下载Kernel镜像

基本过程与uboot下载ramdisk类似,这里就不再赘述了。。。


镜像启动调试

Uboot启动Kernel

Uboot阶段涉及对SOC芯片和部分外设初始化,一般最简单的情况至少需要初始化如下部分:

  • uart 串口调试用
  • DDR
  • eMMC/Nand/Hardisk 命令bootm从这些存储设备加载kernel镜像到内存中(或者也可以通过net网络下载)
  • 检测机器类型,machine-id
  • 标记kernel启动参数列表或者设备树地址,atag or dtb

它的初始化过程大概就是按照下图从CPU=>ARCH=>Machine=>Board的顺序,如下图:

autoboot自动加载启动uImage
TODO...

bootm命令手动加载启动uImage
TODO...

Kernel启动Android

Kernel的起始点
① 在ARM64上:
从uboot通过函数boot_jump_linux()调用armv8_switch_to_el2(),再跳转到image->ep这个地址开始,
② 在ARM32上:
从uboot通过函数boot_jump_linux()调用start_kernel(),直接跳转到image->ep这个地址开始

Tips:
需要注意的是,从uboot的代码中可以看出image文件中image->ep记录了kernel代码段起始地址

Kernel启动分阶段
我们可以通过Kernel启动中所完成的功能,简单地人为将它分成如下几个阶段:
硬件环境准备 - 汇编函数:_head - 在kernel/arch/arm64/kernel/head.S

  • 创建临时页表
  • 开启MMU

软件环境准备 - C函数:start_kernel() - 在kernel/init/main.c

  • 多种初始化,比如:lock、irq/exception、clock/timer、memory、dts、vfs、scheduler等等
  • 这里函数都是用__init标记放在init.section段中的,捡来会被释放掉

单线程变多线程 - C函数:rest_init() - 在kernel/init/main.c

  • 启动另外2个线程:kernel_init、kthreadd
  • 激活调度器
  • 自身变为idle线程

单核变多核 - C函数:kernel_init()/kernel_init_freeable() - 在kernel/init/main.c

  • 启动SMP多核
  • 激活SMP调度
  • 初始化外设驱动
  • 打开/dev/console
  • 创建用户态init进程

如下示意图,简短描述了各个阶段和完成的最核心的事情:

Android启动流程

几个关键点
Android启动流程中有几个关键点是必须成功的,这点上进程一旦启动失败,系统就会reboot重启:

① 设置default.prop中属性:

  • 这里属性很重要,会影响到android系统启动的,比如ro.hardware需要通过bootargs带参数androidboot.hardware=${board_name}来带入,然后是在init.rc中解析并赋值给ro.hardware的

② 创建/data/dalvik-cache目录:

  • 该目录/data/dalvik-cache是在init.rc中创建的,如果创建失败,则会导致zegote启动时创建/data/dalvik-cache/arm64失败,进而zygote启动失败
  • Debug:
    • 在怀疑并检查是否/data分区没有mount成功时,可以查看logcat中zygote启动失败的NativeCrash信息,进而可以手动mount /data分区、并手动mkdir创建dalvik-cache目录,然后logcat看zygote启动流程是否能进行下去
    • /data分区的创建在init.$(ro.hardware).rc中,可以通过在init中开启log去查看init.$(ro.hardware).rc是否确实被import了
    • 如果发现这个rc没有被import的话,一般应该是ro.hardware属性没有被设置,可以通过getprop查看,这ro.hardware是在init.cpp中从bootargs中的androidboot.hardware=xxxx解析出来的

③ 将Selinux置为permissive模式:

  • 需要置为permissive模式,或者内核defconfig中关闭selinux,否则可能会因为某些进程访问某些文件的权限未配置导致启动失败

④ 加载SurfaceFlinger

  • libEGL:libEGL.so库必须要有,并且版本匹配,否则surfaceflinger会启动失败
  • libGLES:libGLES.so/libGLESv1_CM.so/libGLESv2.so/libGLESv3.so库必须有

启动流程
TODO...


参考文档

Android开源工程网址
U-boot环境变量设置
U-boot通过tftp下载镜像文件
U-boot下netconsole使用方法
U-boot关于在开发板和电脑用网线直接连接的情况下如何ping通,nfs挂载
Linux Kernel编译过程
Android系统启动过程-uBoot+Kernel+Android
linux kernel_init

@2018-05-15 12:05
Comments
Write a Comment