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();
__commands_start = .;
KEEP (*(.commands))
__commands_end = .;
. = ALIGN();
__apps_start = .;
KEEP (*(.apps))
__apps_end = .;
. = ALIGN();
__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() { *(.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();
__bss_start = .;
.bss : { *(.bss .bss.*) } . = ALIGN();
_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 ;
}

在代码中,比较重要的是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) == ) {
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();

可以知道是,调用了所有放在*.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 = ;
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 - ;
mmc_blocksize = mmc_get_device_blocksize();
mmc_blocksize_mask = mmc_blocksize - ;
}
else
{
page_size = flash_page_size();
page_mask = page_size - ;
} 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 = ;
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 = ;
}
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 = ;
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 = ;
write_device_info(&device);
} else if(reboot_mode == DM_VERITY_KEYSCLEAR) {
if(send_delete_keys_to_tz())
ASSERT();
}
#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内核系统启动起来。

最新文章

  1. Windows下程序启动时出现0xc000007b错误的解决方案
  2. [BI项目记]-文档版本管理笔记
  3. diff输出格式解析
  4. ie下获取上传文件全路径
  5. centos 用dvd创建yum 仓库
  6. hdu City Game
  7. NOIP201305转圈游戏
  8. Codeforces Good Bye 2015 D. New Year and Ancient Prophecy 后缀数组 树状数组 dp
  9. 组装和拆分JSON
  10. cas 代理认证配置
  11. hash冲突随笔
  12. Winsock网络编程笔记(3)----基于UDP的server和client
  13. 设计模式复习小结一(Strategy Pattern/Observer Pattern/Decorator Patter/Factory Pattern)
  14. 装 ubuntu + win10 出现 grub rescue 并处理之
  15. Dubbo框架应用之(二)--服务治理
  16. ubuntu 18.04安装clojure工程的cli工具lein
  17. nginx 刷新显示404
  18. MyBatis(一)helloWorld程序
  19. jquery 添加与删除的规律 当要添加时候要定位到自己的父元素 当要删除时候 通过事件函数传入的this找到自己的父元素进行删除
  20. 【JBPM4】任务节点-任务分配swimlane

热门文章

  1. IT兄弟连 Java语法教程 Java语法基础 经典面试题
  2. Java Serializable:明明就一个空的接口嘛
  3. Mysql优化之Explain查询计划查看
  4. centos 8 docker-ce 安装
  5. Vue实现简单的列表金额计算效果(简易购物车)
  6. 表达式树练习实践:C#判断语句
  7. react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面
  8. 一文解读5G (转)
  9. 微信小程序之 catalog 切换
  10. Mysql类