Android系统之LK启动流程分析(一)
1、前言
LK是Little Kernel的缩写,在Qualcomm平台的Android系统中普遍采用LK作为bootloader,它是一个开源项目,LK是整个系统的引导部分,所以不是独立存在的,但是目前LK只支持arm和x86架构,LK显著的特点是实现了一个简单的线程机制(thread),并和Qualcomm的处理器深度定制和使用。
LK的代码架构如下所示:
app ----> 应用相关代码 arch ----> 处理器架构体系 dev ----> 和设备相关代码 include ----> 相关头文件 kernel ----> lk系统实现相关代码 lib ----> 相关库 make ----> Makefile文件 platform ----> 和平台相关驱动代码 projects ----> Makefile文件 scripts ----> jtag脚本文件 target ----> 和目标相关的驱动代码
2、LK入口确定
在Qualcomm平台上,编译lk的命令为:
$ make aboot
编译完成后,会生成文件emmc_appsboot.mbn的镜像文件,对于mbn格式文件,为Qualcomm包含了特定运营商定制的一套efs、nv的集成包文件,大致格式类似于elf文件格式,要确定LK的入口,必须要先知道编译LK的链接文件,相关的链接文件为:
bootable/bootloader/lk/arch/arm/system-onesegment.ld
链接文件内容如下所示:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS { . = %MEMBASE%; /* text/read-only data */ .text.boot : { *(.text.boot) } .text : { *(.text .text.* .glue_7* .gnu.linkonce.t.*) } =0x9090 .interp : { *(.interp) } .hash : { *(.hash) } .dynsym : { *(.dynsym) } .dynstr : { *(.dynstr) } .rel.text : { *(.rel.text) *(.rel.gnu.linkonce.t*) } .rela.text : { *(.rela.text) *(.rela.gnu.linkonce.t*) } .rel.data : { *(.rel.data) *(.rel.gnu.linkonce.d*) } .rela.data : { *(.rela.data) *(.rela.gnu.linkonce.d*) } .rel.rodata : { *(.rel.rodata) *(.rel.gnu.linkonce.r*) } .rela.rodata : { *(.rela.rodata) *(.rela.gnu.linkonce.r*) } .rel.got : { *(.rel.got) } .rela.got : { *(.rela.got) } .rel.ctors : { *(.rel.ctors) } .rela.ctors : { *(.rela.ctors) } .rel.dtors : { *(.rel.dtors) } .rela.dtors : { *(.rela.dtors) } .rel.init : { *(.rel.init) } .rela.init : { *(.rela.init) } .rel.fini : { *(.rel.fini) } .rela.fini : { *(.rela.fini) } .rel.bss : { *(.rel.bss) } .rela.bss : { *(.rela.bss) } .rel.plt : { *(.rel.plt) } .rela.plt : { *(.rela.plt) } .init : { *(.init) } =0x9090 .plt : { *(.plt) } .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) . = ALIGN(4); __commands_start = .; KEEP (*(.commands)) __commands_end = .; . = ALIGN(4); __apps_start = .; KEEP (*(.apps)) __apps_end = .; . = ALIGN(4); __rodata_end = . ; } /* writable data */ __data_start_rom = .; /* in one segment binaries, the rom data address is on top of the ram data address */ __data_start = .; .data : SUBALIGN(4) { *(.data .data.* .gnu.linkonce.d.*) } __ctor_list = .; .ctors : { *(.ctors) } __ctor_end = .; __dtor_list = .; .dtors : { *(.dtors) } __dtor_end = .; .got : { *(.got.plt) *(.got) } .dynamic : { *(.dynamic) } __data_end = .; /* unintialized data (in same segment as writable data) */ . = ALIGN(4); __bss_start = .; .bss : { *(.bss .bss.*) } . = ALIGN(4); _end = .; . = %MEMBASE% + %MEMSIZE%; _end_of_ram = .; /* Strip unnecessary stuff */ /DISCARD/ : { *(.comment .note .eh_frame) } }
从链接文件中,可以确定LK启动入口为_start函数,该函数的定义在汇编文件:
bootable/bootloader/lk/arch/arm/ctr0.S
该文件的部分代码如下:
.section ".text.boot" .globl _start _start: b reset b arm_undefined b arm_syscall b arm_prefetch_abort b arm_data_abort b arm_reserved b arm_irq b arm_fiq reset: .... .... .... bl kmain /* 跳到kmain函数执行 */ b . ....
_start函数的主要功能是设置中断向量表、初始化bss段、初始化与处理器架构的相关寄存器、搭建C运行环境等,然后开始运行bl kmain代码,跳转到kmain函数处运行,进入的C语言的世界。
3、kmain函数分析
在_start函数的最后,将会调用kmain函数,接下来,对kmain函数的流程进行分析,该函数的定义在文件:
bootable/bootloader/lk/kernel/main.c
函数的定义如下所示:
void kmain(void) { // get us into some sort of thread context thread_init_early(); /* thread系统早期初始化 */ // early arch stuff arch_early_init(); /* arch架构相关早期初始化,使能mmu等 */ // do any super early platform initialization platform_early_init(); /* msm平台的早期初始化(board、时钟和中断控制器初始化等) */ // do any super early target initialization target_early_init(); /* target早期初始化(主要是debug串口的初始化) */ dprintf(INFO, "welcome to lk\n\n"); bs_set_timestamp(BS_BL_START); // deal with any static constructors dprintf(SPEW, "calling constructors\n"); call_constructors(); // bring up the kernel heap dprintf(SPEW, "initializing heap\n"); heap_init(); /* kernel heap初始化 */ __stack_chk_guard_setup(); // initialize the threading system dprintf(SPEW, "initializing threads\n"); thread_init(); /* thread系统初始化 */ // initialize the dpc system dprintf(SPEW, "initializing dpc\n"); dpc_init(); /* dpc系统相关初始化 */ // initialize kernel timers dprintf(SPEW, "initializing timers\n"); timer_init(); /* kernel timer初始化 */ #if (!ENABLE_NANDWRITE) // create a thread to complete system initialization dprintf(SPEW, "creating bootstrap completion thread\n"); /* 创建bootstrap2线程完成system初始化 */ thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE)); // enable interrupts exit_critical_section(); /* 使能中断 */ // become the idle thread thread_become_idle(); /* 将当前线程设置为idle状态 */ #else bootstrap_nandwrite(); #endif }
对于kmain函数实现的主要功能,在代码中已经注释得很清楚了,函数调用后,首先是对早期的thread线程系统进行初始化,接下来则是调用arch_early_init()函数,对CPU处理器架构相关的早期初始化,例如关闭cache,使能mmu等功能,然后开始调用与平台早期初始化的相关函数,对早期需要使用的外设进行初始化,例如中断控制器、debug串口等外设,接下来,则是调用函数搭建出一个完整的thread线程系统,并对lk中的定时器进行初始化,调用thread_create()函数创建出"bootstrap2"线程,并调用thread_resume()函数,让该线程在系统中工作,最后,则是设置kmain线程为idle状态。
对kmain函数调用流程整理如下:
thread_init_early(); /* thread早期初始化 */ arch_early_init(); /* arch架构早期初始化 */ platform_early_init(); /* msm平台的早期初始化(board、时钟和中断控制器初始化等) */ target_early_init(); /* target早期初始化(主要是debug串口的初始化) */ bs_set_timestamp(BS_BL_START); call_constructors(); heap_init(); /* kernel heap初始化 */ __stack_chk_guard_setup(); thread_init(); /* thread线程系统初始化 */ dpc_init(); /* dpc系统初始 */ timer_init(); /* kernel timer初始化 */ thread_create(); /* 创建bootstrap2线程 */ thread_resume(); /* 运行bootstrap2线程 */ exit_critical_section(); /* 使能中断 */ thread_become_idle(); /* 将当前线程设置为idle状态 */
使用thread_create()函数创建出"bootstrap2"线程后,并使用thread_resume()启动该线程后,接下来将会运行bootstrap2()函数,该函数可以看成是lk启动的第二阶段,它将会继续完成外设的初始化和启动。
4、bootstrap2线程分析
在kmain函数的最后阶段,在thread线程系统搭建完成后,将会运行下面的代码创建出bootstrap2线程:
thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
此时,将会跳转到bootstrap2函数继续运行,完成整个lk系统启动,bootstarp2函数的定义在文件:
bootable/bootloader/lk/kernel/main.c
该函数的定义,如下所示:
/* lk启动的第二阶段(bootstrap2) */ static int bootstrap2(void *arg) { dprintf(SPEW, "top of bootstrap2()\n"); arch_init(); /* arch处理器架构第二阶段初始化 */ // XXX put this somewhere else #if WITH_LIB_BIO bio_init(); #endif #if WITH_LIB_FS fs_init(); #endif // initialize the rest of the platform dprintf(SPEW, "initializing platform\n"); platform_init(); /* platform第二阶段初始化(msm8909只是简单输出debug信息) */ // initialize the target dprintf(SPEW, "initializing target\n"); target_init(); /* target第二阶段初始化,按键、分区表等 */ dprintf(SPEW, "calling apps_init()\n"); apps_init(); /* 创建多个app线程并运行,aboot_init将加载Linux内核 */ return 0; }
在代码中,比较重要的是target_init()函数和apps_init()函数,target_init()函数将针对不同的硬件平台进行一些外设初始化,例如,按键、emmc分区等,apps_init()函数则是将整个lk系统要启动的app全部进行启动运行,本质是使用thread_create()函数和thread_resume()函数,创建多个线程并在lk系统中调度线程,比较重要的是aboot_init线程,它将会启动Linux内核。
5、apps_init函数分析
apps_init()函数的主要功能是将lk系统中的app线程进行创建和调度,其中比较重要的aboot_init线程,它用于启动Linux内核,apps_init函数的定义在文件:
bootable/bootloader/lk/app/app.c
该函数的定义如下所示:
extern const struct app_descriptor __apps_start; extern const struct app_descriptor __apps_end; /* one time setup */ void apps_init(void) { const struct app_descriptor *app; /* call all the init routines */ for (app = &__apps_start; app != &__apps_end; app++) { /* 遍历所有apps */ if (app->init) /* 判断app_descriptor结构的init函数是否存在 */ app->init(app); /* 如果存在,则调用init函数 */ } /* start any that want to start on boot */ for (app = &__apps_start; app != &__apps_end; app++) { if (app->entry && (app->flags & APP_FLAG_DONT_START_ON_BOOT) == 0) { start_app(app); /* 启动所有要在lk阶段启动的app */ } } }
从代码中知道,apps_init函数使用了两个for循环,调用了位于__apps_start与__apps_end之间的函数,对于__apps_start和__apps_end需要去相应的ld链接文件中去寻找,在上面提到的system-onesegment.ld文件中有:
__apps_start = .; KEEP (*(.apps)) __apps_end = .; . = ALIGN(4);
可以知道是,调用了所有放在*.apps段中的函数了,在下面的文件中有和*.apps段的相关宏:
bootable/bootloader/lk/include/app.h
宏APP_START和struct app_descriptor结构体定义如下:
/* each app needs to define one of these to define its startup conditions */ struct app_descriptor { const char *name; app_init init; app_entry entry; unsigned int flags; }; #define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname, #define APP_END };
因此,可以知道,每个app都有一个app_descriptor结构体进行描述,这些结构体的定义都在.apps段中,接下来,继续搜索使用APP_START宏添加的结构体和函数有什么:
在文件:
bootable/bootloader/lk/app/aboot/aboot.c
使用了APP_START宏的定义,如下:
APP_START(aboot) .init = aboot_init, APP_END
这就是aboot这个app的定义,aboot_init函数就是要启动的线程,该线程用来启动Linux内核,非常重要,其它的app定义类似,就不全都讲解了。
6、aboot_init函数分析
对于aboot_init()函数的定义在文件:
bootable/bootloader/lk/app/aboot/aboot.c
函数的内容如下所示:
void aboot_init(const struct app_descriptor *app) { unsigned reboot_mode = 0; bool boot_into_fastboot = false; /* Setup page size information for nv storage */ if (target_is_emmc_boot()) /* 判断目标板是否是emmc启动 */ { page_size = mmc_page_size(); /* 读取对应存储介质的page和block大小*/ page_mask = page_size - 1; mmc_blocksize = mmc_get_device_blocksize(); mmc_blocksize_mask = mmc_blocksize - 1; } else { page_size = flash_page_size(); page_mask = page_size - 1; } ASSERT((MEMBASE + MEMSIZE) > MEMBASE); read_device_info(&device); /* 读取设备的信息 */ read_allow_oem_unlock(&device); /* oem解锁 */ /* Display splash screen if enabled */ /* 初始化LCD接口并显示log */ #if DISPLAY_SPLASH_SCREEN dprintf(INFO, "Display Init: Start\n"); target_display_init(device.display_panel); dprintf(INFO, "Display Init: Done\n"); #endif target_serialno((unsigned char *) sn_buf); dprintf(SPEW,"serial number: %s\n", sn_buf); memset(display_panel_buf, '\0', MAX_PANEL_BUF_SIZE); /* * Check power off reason if user force reset, * if yes phone will do normal boot. */ if (is_user_force_reset()) goto normal_boot; /* Check if we should do something other than booting up */ if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN)) /* 根据按键进入到不同的启动模式 */ { dprintf(ALWAYS,"dload mode key sequence detected\n"); if (set_download_mode(EMERGENCY_DLOAD)) { dprintf(CRITICAL, "dload mode not supported by target\n"); } else { reboot_device(DLOAD); dprintf(CRITICAL,"Failed to reboot into dload mode\n"); } boot_into_fastboot = true; } if (!boot_into_fastboot) { if (keys_get_state(KEY_HOME) || keys_get_state(KEY_BACK)) boot_into_recovery = 1; if (!boot_into_recovery && (keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN))) boot_into_fastboot = true; } #if NO_KEYPAD_DRIVER if (fastboot_trigger()) boot_into_fastboot = true; #endif #if USE_PON_REBOOT_REG reboot_mode = check_hard_reboot_mode(); #else reboot_mode = check_reboot_mode(); #endif if (reboot_mode == RECOVERY_MODE) { boot_into_recovery = 1; } else if(reboot_mode == FASTBOOT_MODE) { boot_into_fastboot = true; } else if(reboot_mode == ALARM_BOOT) { boot_reason_alarm = true; } #if VERIFIED_BOOT #if !VBOOT_MOTA else if(reboot_mode == DM_VERITY_ENFORCING) { device.verity_mode = 1; write_device_info(&device); } #if ENABLE_VB_ATTEST else if (reboot_mode == DM_VERITY_EIO) #else else if (reboot_mode == DM_VERITY_LOGGING) #endif { device.verity_mode = 0; write_device_info(&device); } else if(reboot_mode == DM_VERITY_KEYSCLEAR) { if(send_delete_keys_to_tz()) ASSERT(0); } #endif #endif normal_boot: if (!boot_into_fastboot) { if (target_is_emmc_boot()) { if(emmc_recovery_init()) dprintf(ALWAYS,"error in emmc_recovery_init\n"); if(target_use_signed_kernel()) { if((device.is_unlocked) || (device.is_tampered)) { #ifdef TZ_TAMPER_FUSE set_tamper_fuse_cmd(); #endif #if USE_PCOM_SECBOOT set_tamper_flag(device.is_tampered); #endif } } boot_linux_from_mmc(); /* 从emmc读取linux内核镜像并启动 */ } else { recovery_init(); #if USE_PCOM_SECBOOT if((device.is_unlocked) || (device.is_tampered)) set_tamper_flag(device.is_tampered); #endif boot_linux_from_flash(); } dprintf(CRITICAL, "ERROR: Could not do normal boot. Reverting " "to fastboot mode.\n"); } /* We are here means regular boot did not happen. Start fastboot. */ /* register aboot specific fastboot commands */ aboot_fastboot_register_commands(); /* dump partition table for debug info */ partition_dump(); /* initialize and start fastboot */ fastboot_init(target_get_scratch_address(), target_get_max_flash_size()); #if FBCON_DISPLAY_MSG display_fastboot_menu(); #endif }
aboot_init()函数被调用后,首先是判断目标板是从emmc还是nand flash启动,判断完存储介质后,读取相应的页面和块大小,读取设备的信息,然后调用target_display_init()函数将LCD接口进行初始化,并在屏幕上显示出log图片,接下来,就是判断启动模式,对于emmc存储介质,则会调用boot_linux_from_mmc()函数,从emmc介质中读取Linux内核镜像,并启动Linux系统,aboot_init()函数最主要的功能就是要启动Linux内核,在这,只是简单阐述启动流程,需要了解更详细的内容,可以深入源码分析。
7、小结
本篇文章简单介绍了Android系统中LK启动流程,LK是一个轻量级的线程系统,是一个Bootloader,其最主要的目的就是将Linux内核镜像从emmc或nand flash中加载入RAM中,然后将Linux内核系统启动起来。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?