前言
android的启动流程,在高通平台主要分为BIOS->bootLoader -> kernel -> Android 四个大步骤。除此之外,还有启动Recovery模式和fastboot模式等启动路径,一般来说类似于下面
在Android N/O开始,Android引入了system-as-root(SAR)概念。当时还没有A/B分区结构,没有动态分区,仍然是传统的分区结构。而SAR是将传统的分区结构基础上,在ROM打包阶段,将rootfs不再位于boot.img的ramdisk中,而是与system合并放在的system.img中,这样系统启动时如果启动Android系统(而非recovery),kernel将直接挂载system.img作为rootfs。
对于Android Q,必须使用SAR分区布局,默认强制在打包时将rootfs与system合并。而SAR有两种
- legacy system-as-root(LSAR):上述非动态分区的方案,在Android Q以前使用(主要是Android P)。
- two-stage-init(2SI):Android Q上新开发的动态分区的方案,支持非动态分区与动态分区两种。
- 如果ROM配置为非动态分区,则kernel启动后,在ramdisk中存在first stage init执行程序,运行后,它将system.img挂载至/system,再通过switch root将/system改为/,从而ramdisk完成了它的使命,结束了生命周期。如果ROM配置为动态分区,则kernel启动后,挂载first_stage_ramdisk执行其中的first stage init,挂载super分区中的用户态system逻辑分区,再通过switch root将/system改为/,并结束ramdisk生命周期。随后,运行/init -> /system/bin/init程序,即second stage init,启动Android系统
1.BootLoader
1.1定义
-
BootLoader是在操作系统运行之前运行的一段程序,它可以将系统的软硬件环境带到一个合适的状态,为运行操作系统做好准备,这样描述是比较抽象的,但是它的任务确实不多,终极目标就是把操作系统拉起来运行。
-
Recovery模式是另一个小型的类Android(Linux)系统,它独立于Android系统,主要作用是用来清除用户数据(在某些系统中被称作“恢复出厂设置”)、升级Android系统(OTA)等。请注意,如果系统采用的A/B分区结构,则Recovery不再具备升级Android系统的能力。
-
fastboot模式是bootloader中的一个模块,它没有启动Linux Kernel,主要作用是通过USB连接,刷写Android镜像、控制A/B slots状态(如果采用A/B分区结构)等。
1.2 BootLoader的类型
- x86:x86的工作站和服务器上一般使用LILO和GRUB
- ARM:最早由为ARM720处理器开发板所做的固件,又有armboot,StrongARM平台的blob,还有- - S3C2410处理器开发板上的vivi等。现在armboot已并入U-Boot,所以U-Boot也支持ARM/XSALE平台。U-Boot已经成为ARM平台事实上的标准Bootloader
- PowerPC:最早使用于ppcboot,不过现在大多数直接使用U-boot。
- MIPS:最早都是MIPS开发商自己写的bootloader,不过现在U-boot也支持MIPS架构。
- M68K:Redboot能够支持m68k系列的系统。
android系统大多运行在arm 架构上,所以我们使用的BootLoader是Uboot
1.3 主要工作
1.3.1
Bootloader=Boot + loader
Boot的目的: 最终目的:跳到C语言中;为了C语言运行程序会进行一系列的初始化,系统一上电后如何通过一系列的设置让软件程序员进入C语言/更高级语言环境的开发,这个过程就是boot的主要目的。
Loader的目的: 主要目的是开始执行应用逻辑,比如点灯:需要灯的接口开发;串口输入输出:需要串口编程;加载linux的内核:flash的编程、网卡的编程、内核启动前的初始化部分。根据不同的应用会有不同的变化。 第一阶段:硬件初始化,SVC模式,关闭中断,关闭看门狗,初始化栈,进入C代码 第二阶段:CPU/board/中断初始化;初始化内存与flash;将kernel从flash中拷贝到内存中;执行bootm,启动内核
1.3.2 Flash是什么?
Flash 存储器(FLASH EEPROM)又称闪存,快闪。它是EEPROM的一种。它结合了ROM和RAM的长处。不仅具备电子可擦除可编辑(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据。它于EEPROM的最大区别是,FLASH按扇区(block)操作,而EEPROM按照字节操作。FLASH的电路结构较简单,同样容量占芯片面积较小,成本自然比EEPROM低,因此适合用于做程序存储器。 参考:Flash,RAM, ROM的不同
1.3.3 ramdisk 是什么?
Ram Disk 就是将内存中的一块区域作为物理磁盘来使用的一种技术。 对于用户来说,可以把RAM disk与通常的硬盘分区(如/dev/hda1)同等对待来使用, RAM disk不适合作为长期保存文件的介质,掉电后Ramdisk的内容会随内存内容的消失而消失。 RAM disk的其中一个优势是它的读写速度高,内存盘的存取速度要远快于目前的物理硬盘,可以被用作需要高速读写的文件,在整个启动过程中主要作为一个文件系统使用。
1.3.4 bootm命令执行有三个步骤:
- 解压Linux内核映像并将其加载到RAM中
- 将ramdisk(随机文件读取系统)映像加载到RAM中
- 将控制权交给内核,并向内核传递ramdisk在RAM中的位置和大小等信息
2 详细启动流程
2.1Bios启动
从按下电源键开始,此时硬件电路会产生一个确定的复位时序,保证CPU是最后一个被复位的器件,为什么CPU要最后被复位呢?因为,如果CPU第一个被复位,则当CPU复位后开始运行时,其他硬件内部的寄存器状态可能还没有准备好,比如磁盘或者内存,那么久可能出现外围硬件初始化错误。当正确完成复位后,CPU开始执行第一条指令,该指令所在的内存你地址是固定的,这由CPU的制造者指定。不同的CPU可能会从不同的地址获取指令,但这个地址必须是固定的,这个固定地址所保存的程序往往被称为"引导程序(BootLoader)",因为其作用是装载真正的用户程序。
2.2 Bootloader
BootLoader代码是芯片复位后,进入操作系统之前执行的一段代码。主要用于完成由硬件启动到操作系统启动的过渡,从而为操作系统提供基本的运行环境。 BootLoder主要的启动流程可以概括为:PBL阶段、SBL阶段、LK阶段。之后会加载并启动kernel
2.2.1启动流程
- AP侧CPU上电。在芯片内部ROM的PBL首先运行,PBL会从boot device(eMMC)中加载并验证SBL1到TCM中。这里的TCM可以理解为CPU的二级缓存。既然PBL能够从boot device(eMMC)中加载SBL1,那PBL应该是初始化过boot device的。
- SBL1初始化DDR,并从boot device中加载并且校验如下镜像: QSEE或者TZ镜像、QHEE镜像、RPM_FW、镜像、APPSBL等。
- SBL1加载并验证完上述镜像后,即将执行权转移到QSEE中,QSEE将设置并初始化一个安全的执行环境。
- QSEE通知RPM去执行RPM_FW相关代码。
- QSEE将执行权转移到APPSBL中,APPSBL也就是LK。
- LK加载HLOS的kernel。 sbl1入口: sbl1.s
此部分代码路径在:boot_images/core/boot/secboot3/hw/msm89xx/sbl1/sbl1.s,此文件引导处理器,主要有实现如下操作: 部分源码:
IMPORT |Image$$SBL1_SVC_STACK$$ZI$$Limit|
IMPORT |Image$$SBL1_UND_STACK$$ZI$$Limit|
IMPORT |Image$$SBL1_ABT_STACK$$ZI$$Limit|
IMPORT boot_undefined_instruction_c_handler
IMPORT boot_swi_c_handler
IMPORT boot_prefetch_abort_c_handler
IMPORT boot_data_abort_c_handler
IMPORT boot_reserved_c_handler
IMPORT boot_irq_c_handler
IMPORT boot_fiq_c_handler
IMPORT boot_nested_exception_c_handler
IMPORT sbl1_main_ctl #主要关注此函数
IMPORT boot_crash_dump_regs_ptr
...
sbl1_main_ctl函数
路径:...\sbl1\sbl1_mc.c
/* Calculate the SBL start time for use during boot logger initialization. */
sbl_start_time = CALCULATE_TIMESTAMP(HWIO_IN(TIMETICK_CLK));
boot_clock_debug_init();
/* Enter debug mode if debug cookie is set */
sbl1_debug_mode_enter();
/* Initialize the stack protection canary */
boot_init_stack_chk_canary();
/* Initialize boot shared imem */
boot_shared_imem_init(&bl_shared_data);
/*初始化RAM*/
boot_ram_init(&sbl1_ram_init_data);
/*初始化log系统,即串口驱动*/
sbl1_boot_logger_init(&boot_log_data, pbl_shared);
/*检索PBL传递过来的数据*/
sbl1_retrieve_shared_info_from_pbl(pbl_shared);
/* Initialize the QSEE interface */
sbl1_init_sbl_qsee_interface(&bl_shared_data,&sbl_verified_info);
/* Initialize SBL memory map. Initializing early because drivers could be located in RPM Code RAM. */
sbl1_populate_initial_mem_map(&bl_shared_data);
/*初始化DAL*/
boot_DALSYS_InitMod(NULL);
/*配置PMIC芯片,以便我们能通过PS_HOLD复位*/
sbl1_hw_init();
/*执行sbl1的目标依赖进程*/
boot_config_process_bl(&bl_shared_data, SBL1_IMG, sbl1_config_table);
- 设置硬件,继续boot进程。
- 初始化ddr。
- 加载Trust_Zone操作系统。
- 加载RPM固件。
- 加载APPSBL然后继续boot进程。
2.2内核启动
#bootable
-bootloader/LK
-app #功能实现,如 adb 命令
-arch #CPU架构
-dev #设备驱动
-include #头文件
-kernel #主文件,main.c
-lib #库文件
-platform #平台文件,如:msm8916
-projiect #mk文件
-make #mk文件
-scripts #脚本文件
-target #目标设备文件
AndroidBoot.mk
makefie
-recovery #由lk启动,主要用来更新主系统(即我们平时使用的Android系统)
-diskinstaller #打包镜像
路径:#/lk_mtk/blob/master/bootable/bootloader/lk/kernel/main.c
void kmain(void)
{ //获得当前的时间戳
boot_time = get_timer(0);
//打印
zzytest_printf("kmain begin, boot_time=%d\n", boot_time);
// 使得我们能够进入线程的上下文
thread_init_early();
// ARM 体系初始化
arch_early_init();
// 执行boot_loader日志初始化
bldr_log_init(BOOT_DEBUG_LOG_BASE_PA, BOOT_DEBUG_LOG_SIZE);
// 平台相关,初始化不同平台不同的初始化
platform_early_init();
zzytest_printf("zzytest, printf log end\n");
dprintf(CRITICAL, "zzytest, after platform_early_init\n");
// lk系统相关堆栈初始化
heap_init();
//线程初始化
thread_init();
// 初始化lk系统的控制器
dpc_init();
// 初始化定时器
timer_init();
// 创建一个线程来执行bootstrap2,用于boot工作(重点)
thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
// 启用中断
exit_critical_section();
// 当前线程变成守护线程
dprintf(CRITICAL, "zzytest, thread_become_idle\n");
thread_become_idle();
}
以上与 boot 启动初始化相关函数是 arch_early_init()、 platform_early_init() 、bootstrap2,这些是启动的重点,我们下面慢慢来看。
2.1 arch_early_init()
thread_early_init(){
//1.关闭cache
arch_disable_cache(UCACHE);
//2.设置向量基地址(中断相关)
set_vector_base(MEMBASE)
//3.初始化MMU
arm_mmu_init();
//4.初始化MMU映射__平台相关
platform_init_mmu_mappings();
//5.开启cache
arch_enable_cache(UCACHE)
//6.使能 cp10 和 cp11
__asm__ volatile("mrc p15, 0, %0, c1, c0, 2" : "=r" (val));
val |= (3<<22)|(3<<20);
__asm__ volatile("mcr p15, 0, %0, c1, c0, 2" :: "r" (val));
// 7.设置使能 fpexc 位 (中断相关)
__asm__ volatile("mrc p10, 7, %0, c8, c0, 0" : "=r" (val));
val |= (1<<30);
__asm__ volatile("mcr p10, 7, %0, c8, c0, 0" :: "r" (val));
//8.使能循环计数寄存器
__asm__ volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (en));
en &= ~(1<<3); /*循环计算每个周期*/
en |= 1;
__asm__ volatile("mcr p15, 0, %0, c9, c12, 0" :: "r" (en));
//9.使能循环计数器
en = (1<<31);
__asm__ volatile("mcr p15, 0, %0, c9, c12, 1" :: "r" (en));
2.2 platform_early_init()
platform_early_init(){
//1.初始化中断
platform_init_interrupts();
//2.初始化定时器
platform_init_timer();
}
2.3 bootstrap2
bootstrap2在kmain()的末尾以线程方式开启,主要分为3部分 platform_init()、target_init()、apps_init()
- platfrom_init 中主要是函数acpu_clock_init(),对系统时钟进行设置,超频,初始化平台的其余部分
- target_init 针对硬件平台进行设置,主要对arm9和arm11的分区表进行整合,初始化Flash 和读取flash信息
- app_init是关键,对LK中所谓的app初始化并运行起来,而aboot_init就将在这里开始被运行,android Linux内核的加载工作就在aboot_init中完成,主要分为4步。
2.3.1 aBoot_init
1.设置NAND/EMMC 读取页面大小
if (target_is_emmc_boot()){
page_size = 2048;
page_mask = page_size - 1;
}else{
page_size = flash_page_size();
page_mask = page_size - 1;
}
2.选择开机模式
读取按键信息,判断是正常开机,还是进入 fastboot ,还是进入recovery 模式
3.从 nand 中加载 内核
boot_linux_from_flash();
partition_dump();
sz = target_get_max_flash_size();
fastboot_init(target_get_scratch_address(), sz);
udc_start(); // 开始 USB 协议
boot_linux_from_flash主要是内核的加载过程,我们的boot.img包含了:kernel头,kernel, ramdisk,second stage(可以没有)
3.1 读取boot 头部 3.2 读取 内核
memcmp(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE)
n = (hdr->kernel_size + (FLASH_PAGE_SIZE - 1)) & (~(FLASH_PAGE_SIZE - 1));
flash_read(p, offset, (void*) hdr->kernel_addr, n)
offset += n;
3.3 读取 ramdisk
n = (hdr->ramdisk_size + (FLASH_PAGE_SIZE - 1)) & (~(FLASH_PAGE_SIZE - 1));
flash_read(p, offset, (void*) hdr->ramdisk_addr, n)
offset += n;
4.启动内核
boot_linux();//在boot_linux 中entry(0,machtype,tags);从kernel加载在内核中的地址开始运行了。
到这里Linux kenrel的启动过程就完成了
参考内容