文章大纲
序言
Android系统的启动指Android设备断电死机后,再长按电源通电Android设备开机的过程,涉及到软硬件的上电和启动,而Android是基于Linux内核的操作系统,所以会涉及到一些Linux内核的知识,接出来系列文章将逐一介绍和总结这一流程,系列文章链接如下:
源码基于android9.0,因为知识水平有限,可能有些理解和标示在学术上有所误差和不够严谨linux文本编辑器,仅供参考。
一、上电开机,启动BootLoader
从硬件层面上说,开机就是给系统上电,此时硬件电路会形成一个确定的复位时序,确保CPU是最后一个被复位的元件(由于如果CPU被复位后并开始运行时,但其他硬件(I/O设备、内存等)内部的寄存器状态可能还没有打算好,就有可能造成硬件初始化错误)。当所有部件完成复位后,CPU开始执行第一条指令,该指令所在的显存地址是由CPU厂商指定且固定的(不同的CPU可能会从不同的地址获取指令,但该地址必须是固定的),该地址对应的程序就是名为Bootloader。Bootloader是嵌入式系统在上电后在操作系统内核运行之前执行的第一段代码用于
因而整个嵌入式系统内核的加载和启动就完全由BootLoader来完成。
在嵌入式系统中,一般并没有像BIOS那样的固件程序(有的嵌入式CPU也会内嵌一段短小的启动程序)
在多数基于ARM的实际硬件系统,会从串口NANDFlash芯片中的0x00000000地址处装载程序,对于一些大型嵌入式系统而言,该地址中的程序就是最终要执行的用户程,而对于Android而言,该地址中的程序还不是Android程序,而是一个名为U-Boot(或fastboot)用于初始化硬件设备的程序(U-Boot的源码是通过GCC和Makefile组织编译的)
U-Boot(或fastboot)可以看成是Android设备的Bootloader,当U-Boot(或fastboot)被装载后便开始运行,它通常会先检查用户是否按下个别非常按钮,这种非常键盘是uboot在编译时预先被约定好的,用于步入调试模式。假如用户没有按这种非常的按钮,则uboot会从NANDFlash中装载Linux内核,装载的地址是在编译uboot时预先约定好的。
BootLoader的启动通常是直接由汇编发起,再调用到对应的C代码。
二、加载Linux内核并初始化
Linux系统内核启动主要分为三个阶段:
1、内核(kernel)自解压
当嵌入式设备从uboot跳转到kernel的时侯arm linux 内核启动流程,首先运行的是kernel的自解压程序,Linux内核在编译后是以压缩文件方式存在(路径是kernel/out/arch/arm/boot/zImage),根out/arch/arm/boot/compressed/vmlinux.lds文件组织储存,假如要更改代码段和数据段位置也须要更改该链接脚本。内核压缩和解压缩代码都在目录kernel/arch/boot/compressed,编译完成后将形成head.o、misc.o、piggy.gzip.o、vmlinux、decompress.o这几个可重定位文件。
BootLoader完成系统的引导之后并将Linux内核加载以后,调用do_bootm_linux()函数跳转到kernel的真实位置(主要是直接通过汇编和C实现)。若kernel没被压缩,就可以启动了;但kernel被压缩过,就要先解压arm linux 内核启动流程,之后再启动。
2、内核初始化和完善页表
当内核解压完成后通过汇编代码检测处理器类型和机器码类型调用相应的初始化函数,再完善页表,最后跳转到start_kernel()函数开始内核的初始化工作。
检测处理器是汇编子函数__lookup_processor_type中完成的(在文件head-commom.S实现)。__lookup_processor_type调用结束返回原程序时,会将返回结果保存到寄存器中。其中r5寄存器返回一个描述处理器的结构体地址,并对r5进行判定,假如r5的值为0说明不支持这些处理器,将步入_error_p。r8保存了页表的标志位,r9保存了处理器的ID号wps for linux,r10保存了与处理相关的structproc_info_list
检测机器码类型是汇编函数__lookup_machine_type中完成,该函数返回时会将返回结构保存在r5、r6和r7三个寄存器中,其中r5寄存器返回一个拿来描述机器的机构体地址,并对r5进行判定,假如r5为0,则说明不支持这些机器,将步入__error_a。r6保存了I/O的页表偏斜地址。当测量处理器类型和机器码类型结束后,将调用__create_page_tables子函数来构建页表,它所要做的工作就是将RAM地址开始的1M空间化学地址映射到0xCxC0000000开始的虚拟地址处。
3、执行start_kernel函数创建init进程
Linux内核启动的是通过汇编调用start_kernel函数(坐落init/main.c)开始的,start_kernel是所有Linux平台步入系统内核初始化后的入口函数,大致的功能逻辑包含:
简而言之,主要完成剩余与硬件平台的相关初始化工作。
4、挂载文件系统
在进行一些系列的与内核相关的初始后,调用init进程并等待用户进程的执行之前,Linux内核启动的最后一个阶段就是挂载根文件系统,便于安装适当的内核模块来驱动个别硬件设备或启动个别功能和
启动储存于文件系统中的init服务,便于让init服务接手后续的启动工作。起码要挂载以下目录:
Linxu内核启动的最后一个动作,就是从根文件系统上找出并执行init服务(不同的目录都有对应的init服务)。
找到init服务后,Linux会让init服务负责后续初始化系统使用环境的工作,init启动后,就代表系统早已顺利地启动了Linux内核。启动init服务时,init服务会读取/etc/inittab文件,依照/etc/inittab中的设置数据进行初始化系统环境工作。/ect/inittabl定义init服务在Linux启动过程中必须执行以下几个脚本:
/etc/rc.d/rc
/etc/rc.d/rc.local
在构建虚拟控制台,init会在若干个虚拟控制台北执行/bin/login,便于用户可以从虚拟控制台登陆Linux。Linux默认在前6个虚拟控制台,也就tty1~tty6,执行/bin/login登陆程序。当所有的初始化工作结束后,cpu-idle()函数会被调用来使用系统处于闲置(idle)状态并等待用户程序的执行。至此,整个Linux内核启动完毕。
Android设备启动要依次经过BootLoader引导、LinuxKernel加载和初始化和初始化Android系统服务(就会有相应的启动动漫对应),未完待续…