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. Linux shell脚本编程及系统启动实践

    1.编写脚本,接受二个位置参数,magedu和/www,判断系统是否有magedu,如果没有则自动创建magedu用户,并自动设置家目录为/www [root@test qiuhom]#cat che ...

  2. javaAPI操作Hbase

    package chapter04; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configu ...

  3. shell 命令 tar -zxvf 解压 tar -zcvf 压缩

    tar -zxvf 解压 tar -zcvf 压缩

  4. java war包 路径--解决war包中文件路径问题

    https://blog.csdn.net/u013409283/article/details/51480948 转自:http://free-chenwei.iteye.com/blog/1507 ...

  5. RabbitMQ的安装与使用(Centos7,linux版本)

    1.主流的消息中间件简单介绍哦. 1).ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线,并且它一个完全支持jms(java message service)规范的消息中间件.其丰 ...

  6. RabbitMQ Node.js 示例

    RabbitQM 处理和管理消息队列的中间人(broker).可简单理解为邮局,你在程序中写好消息,指定好收件人,剩下的事件就是 RabbitMQ 的工作了,它会保证收件人正确收到邮件. 任何发送邮件 ...

  7. 从《华为的冬天》到AI的冬天 | 甲子光年

    知难不难,惶者生存. 作者 | DougLong 编辑 | 火柴Q.甲小姐 *本文为甲子光年专栏作家DougLong独家稿件.作者为AI从业者.Gary Marcus<Rebooting AI& ...

  8. Xcode 7.3 解决自定义类无法自动联想

    正在苦拼的码友们,最近是不是觉得在写代码的时候很是头疼,甚至连个最基本的系统自带的语法啊.单词啊等等都不能联想了,之前习惯了的码友们,这个时候肯定会觉得是不是自己写错了,然后也往下翻了一大篇,还是找不 ...

  9. 一语点醒技术人:你不是 Google(转载)

    转载链接:https://www.infoq.cn/article/2017/06/U-no-Google 在为问题寻找解决方案时要先充分了解问题本身,而不是一味地盲目崇拜那些巨头公司.Ozan On ...

  10. oracle自定义存储过程:删除表(无论表是否存在)和检测表是否存在

    oracle删除表,如果表不存在,就报错,在跑大型脚本(脚本长且耗时的时候)比较麻烦,一般希望的是点开始然后脚本运行到结束,不可能一直盯着屏幕等弹出提示手工点掉,mysql就很好有drop table ...