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内核系统启动起来。

Android系统之LK启动流程分析(一)的更多相关文章

  1. Android系统架构及启动流程

  2. lk启动流程详细分析

    转载请注明来源:cuixiaolei的技术博客 这篇文章是lk启动流程分析(以高通为例),将会详细介绍下面的内容: 1).正常开机引导流程 2).recovery引导流程 3).fastboot引导流 ...

  3. Cocos2d-x3.3RC0的Android编译Activity启动流程分析

    本文将从引擎源代码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,以下是具体分析. 1.引擎源代码Jni.部分Java层和C++层代码分析 watermark/2 ...

  4. Android5 Zygote 与 SystemServer 启动流程分析

    Android5 Zygote 与 SystemServer 启动流程分析 Android5 Zygote 与 SystemServer 启动流程分析 前言 zygote 进程 解析 zygoterc ...

  5. “无处不在” 的系统核心服务 —— ActivityManagerService 启动流程解析

    本文基于 Android 9.0 , 代码仓库地址 : android_9.0.0_r45 系列文章目录: Java 世界的盘古和女娲 -- Zygote Zygote 家的大儿子 -- System ...

  6. u-boot启动流程分析(2)_板级(board)部分

    转自:http://www.wowotech.net/u-boot/boot_flow_2.html 目录: 1. 前言 2. Generic Board 3. _main 4. global dat ...

  7. u-boot启动流程分析(1)_平台相关部分

    转自:http://www.wowotech.net/u-boot/boot_flow_1.html 1. 前言 本文将结合u-boot的“board—>machine—>arch—> ...

  8. Android 4.4 音量调节流程分析(二)

    之前在Android 4.4 音量调节流程分析(一)里已经有简单的分析音量控制的流程,今天想接着继续分析下音量大小计算的方法.对于任一播放文件而言其本身都有着固定大小的音量Volume_Max,而在A ...

  9. Android 7.1 屏幕旋转流程分析

    Android 7.1   屏幕旋转流程分析 一.概述 Android屏幕的旋转在framework主要涉及到三个类,结构如图 PhoneWindowManager:为屏幕的横竖屏转换的管理类. Wi ...

随机推荐

  1. 第一章 1.18 re模块

    方法使用 1. compile(正则表达式) - 编译创建正则表达式对象 re_obj = re.compile(r'\d{3}') re_obj.fullmatch('234') re.fullma ...

  2. PVANET: Deep but Lightweight Neural Networks for Real-time Object Detection

    URL: https://arxiv.org/abs/1608.08021 year: 2016 TL;DR PVANet 一个轻量级多物体目标检测架构, 遵循 "less channels ...

  3. java 类内部定义接口

    java类内部可以定义接口,作用可以看作是对类功能的进一步补充,类里面包含两部分:一部分是自己的固定的,一部分是可以变化的,而这可变的部分就编程了一个接口. 另一个作用是避免命名冲突. 示例 类Fru ...

  4. Pycharm新手使用教程(详解)

    Pycharm新手使用教程(详解) [注]: 如果想要下载Pycharm工具,直接去<开发工具>中进行下载. 简介 Jetbrains家族和Pycharm版本划分: pycharm是Jet ...

  5. wpf file embeded resource is readonly,Copy always will copy the file and its folder to the bin folder

    Wpf file embeded resource will compile the file into the assembly and it will be readonly and can no ...

  6. PHP面试题2019年百度工程师面试题及答案解析

    一.单选题(共10题,每题5分) 1.以下代码输出的结果是? ​​​ A.[0,1,2,3] B.[1,3,5,7,5] C.[1,2,3,4,5] D.[0,1,2,3,5] 参考答案:D 答案解析 ...

  7. 微信小程序动画之弹出菜单

    用微信小程序做了一个动画,效果如上图: 代码: js: Page({ data: { isPopping: false, animPlus: {}, animCollect: {}, animTran ...

  8. Kotlin介绍(非原创)

    文章大纲 一.Kotlin简介二.Kotlin相比Java优势三.Kotlin与Java混合使用四.参考文章   一.Kotlin简介 1. 什么是Kotlin 安卓和Java,前者是最受欢迎的移动开 ...

  9. 用python的time库写一个进度条

    运算符 算数运算 如a=10,b=20 +两个数相加 a+b=30 -两个数相减 a-b=-10 两个数相乘 a****b =200 /两个数相除b/a=2 %取模,并返回余数b%a=0 幂,a*** ...

  10. this license XXXXXX has been cancelled

    this license XXXXXX has been cancelled问题解决:首先修改hosts 文件 加入0.0.0.0 account.jetbrains.comhosts 目录 wind ...