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");

bs_set_timestamp(BS_BL_START);//deal with any static constructors

dprintf(SPEW, "calling constructors");

call_constructors();//bring up the kernel heap

dprintf(SPEW, "initializing heap");

heap_init();/*kernel heap初始化*/__stack_chk_guard_setup();//initialize the threading system

dprintf(SPEW, "initializing threads");

thread_init();/*thread系统初始化*/

//initialize the dpc system

dprintf(SPEW, "initializing dpc");

dpc_init();/*dpc系统相关初始化*/

//initialize kernel timers

dprintf(SPEW, "initializing timers");

timer_init();/*kernel timer初始化*/

#if (!ENABLE_NANDWRITE)

//create a thread to complete system initialization

dprintf(SPEW, "creating bootstrap completion thread"); /*创建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状态*/

#elsebootstrap_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)*/staticint bootstrap2(void *arg)

{

dprintf(SPEW,"top of bootstrap2()");

arch_init();/*arch处理器架构第二阶段初始化*/

//XXX put this somewhere else

#if WITH_LIB_BIObio_init();#endif

#if WITH_LIB_FSfs_init();#endif

//initialize the rest of the platform

dprintf(SPEW, "initializing platform");

platform_init();/*platform第二阶段初始化(msm8909只是简单输出debug信息)*/

//initialize the target

dprintf(SPEW, "initializing target");

target_init();/*target第二阶段初始化,按键、分区表等*/dprintf(SPEW,"calling apps_init()");

apps_init();/*创建多个app线程并运行,aboot_init将加载Linux内核*/return0;

}

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

constchar *name;

app_init init;

app_entry entry;

unsignedintflags;

};#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_SCREENdprintf(INFO,"Display Init: Start");

target_display_init(device.display_panel);

dprintf(INFO,"Display Init: Done");#endiftarget_serialno((unsignedchar *) sn_buf);

dprintf(SPEW,"serial number: %s", sn_buf);

memset(display_panel_buf,'

android启动流程之lk,Android系统之LK启动流程分析(一)相关推荐

  1. (连载)Android系统源码分析--Android系统启动流程之Linux内核

    > **这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 [github连载地址](https://github.com/foxleezh/AOSP/issues/3 ...

  2. 我的Android进阶修炼:安卓启动流程之init(1)

    文章目录 我的Android进阶修炼:安卓启动流程之init(1) 一.前言 二.init进程简介 1.文件位置 2.主要功能 三.init进程源码分析 3.1 main() 源码注解 3.1.1 参 ...

  3. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | 查找 DexFile 对应的C代码 | dalvik_system_DexFile.cpp 分析 )

    文章目录 前言 一.查找 DexFile 对应的 C++ 代码 1.根据 Native 文件命名惯例查找 C++ 代码 2.根据方法名查找 二.dalvik_system_DexFile.cpp 源码 ...

  4. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexFile loadDexFile 函数 | 构造函数 | openDexFile 函数 )

    文章目录 前言 一.DexFile.loadDexFile 函数分析 二.DexFile 构造函数分析 三.DexFile.openDexFile 函数分析 前言 上一篇博客 [Android 逆向] ...

  5. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 )

    文章目录 前言 一.RawDexFile.cpp 中 dvmRawDexFileOpen() 方法分析 前言 上一篇博客 [Android 逆向]整体加固脱壳 ( DexClassLoader 加载 ...

  6. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 中根据 File 加载 DexFile | loadDexFile 分析 )

    文章目录 前言 一.根据 File 加载 DexFile 二.DexPathList.loadDexFile 函数分析 前言 上一篇博客 [Android 逆向]整体加固脱壳 ( DexClassLo ...

  7. 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 构造函数分析 | makeDexElements 函数分析 )

    文章目录 前言 一.DexPathList 构造函数分析 二.DexPathList.makeDexElements 函数分析 三.Element 类分析 前言 上一篇博客 [Android 逆向]整 ...

  8. android启动流程之preloader--->lk

    关于异常的基本知识 什么是异常 对于AArch64而言,exception是指cpu的某些异常状态或者一些系统的事件(可能来自外部,也可能来自内部),这些状态或者事件可以导致cpu去执行一些预先设定的 ...

  9. Android筑基——Activity的启动过程之同进程在一个Activity中启动另一个Activity(基于api21)

    目录 1. 前言 2. 正文 2.1 Activity类的startActivity()方法 2.2 Instrumentation类的execStartActivity()方法 2.3 Activi ...

最新文章

  1. 使用 java 的 displaytag1.2 分页组件使用步骤
  2. 天津Uber优步司机奖励政策(1月4日~1月10日)
  3. 【Groovy】MOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 实现 GroovyInterceptable 接口 | 重写 invokeMethod 方法 )
  4. matlab 图像处理 新浪 应变,[转载]Matlab图像处理小结
  5. 开源力量 Linux内核源码深度解析与开发实战
  6. 动软代码生成器-模板修改,模型修改
  7. 反向传播算法带动了业界使用两层神经网络研究的热潮
  8. php给img标签加alt属性
  9. matlab分频.m,分频器m是什么意思 音响分频器m. TW那个代表高音那个代表是低音?...
  10. 通用Excel数据导入功能模板
  11. phpStrom2016.3激活教程
  12. AndroidStudio配置LitePal时Failed to Resolve
  13. StringBuffer之间的比较、String和StringBuffer的比较
  14. 操作系统——段式存储管理
  15. 程序员如何快乐学习?
  16. 计算机网络ppt课件教学视频教程,计算机网络高级教程ppt课件
  17. 鲸交所抢滩PoS 25亿美元市场,正式上线鲸矿池
  18. XY51S03B蓝牙5.1芯片OBU方案
  19. java查看kafka数据量_Java kafka监控 topic的数据量count情况,每个topic的offset,
  20. MT4跟单系统的运行环境、模式与原理分析

热门文章

  1. 即学即用的 30 段 Python 非常实用的代码
  2. 打印从1到最大的n位数
  3. Linux之复制、移动文件及目录命令
  4. 简洁版本 STP/RSTP/MSTP的区别以及各自的特点
  5. 创业基础(第四章: 创业风险及识别与管理) 来自高校:全国大学生创新创业实践联盟 分类:创新创业 学习规则:按序学习
  6. 电路交换和报文交换和分组交换的概念和区别
  7. 使用Python分析姿态估计数据集COCO的教程
  8. Hexo 个人博客 SEO 优化(3):改造你的博客,提升搜索引擎排名
  9. php通过http请求发送数组
  10. qt获取combobox的值_Qt官方示例嵌套甜甜圈