android启动流程之lk,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");
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启动流程分析(一)相关推荐
- (连载)Android系统源码分析--Android系统启动流程之Linux内核
> **这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 [github连载地址](https://github.com/foxleezh/AOSP/issues/3 ...
- 我的Android进阶修炼:安卓启动流程之init(1)
文章目录 我的Android进阶修炼:安卓启动流程之init(1) 一.前言 二.init进程简介 1.文件位置 2.主要功能 三.init进程源码分析 3.1 main() 源码注解 3.1.1 参 ...
- 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | 查找 DexFile 对应的C代码 | dalvik_system_DexFile.cpp 分析 )
文章目录 前言 一.查找 DexFile 对应的 C++ 代码 1.根据 Native 文件命名惯例查找 C++ 代码 2.根据方法名查找 二.dalvik_system_DexFile.cpp 源码 ...
- 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexFile loadDexFile 函数 | 构造函数 | openDexFile 函数 )
文章目录 前言 一.DexFile.loadDexFile 函数分析 二.DexFile 构造函数分析 三.DexFile.openDexFile 函数分析 前言 上一篇博客 [Android 逆向] ...
- 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | RawDexFile.cpp 分析 | dvmRawDexFileOpen函数读取 DEX 文件 )
文章目录 前言 一.RawDexFile.cpp 中 dvmRawDexFileOpen() 方法分析 前言 上一篇博客 [Android 逆向]整体加固脱壳 ( DexClassLoader 加载 ...
- 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 中根据 File 加载 DexFile | loadDexFile 分析 )
文章目录 前言 一.根据 File 加载 DexFile 二.DexPathList.loadDexFile 函数分析 前言 上一篇博客 [Android 逆向]整体加固脱壳 ( DexClassLo ...
- 【Android 逆向】整体加固脱壳 ( DexClassLoader 加载 dex 流程分析 | DexPathList 构造函数分析 | makeDexElements 函数分析 )
文章目录 前言 一.DexPathList 构造函数分析 二.DexPathList.makeDexElements 函数分析 三.Element 类分析 前言 上一篇博客 [Android 逆向]整 ...
- android启动流程之preloader--->lk
关于异常的基本知识 什么是异常 对于AArch64而言,exception是指cpu的某些异常状态或者一些系统的事件(可能来自外部,也可能来自内部),这些状态或者事件可以导致cpu去执行一些预先设定的 ...
- Android筑基——Activity的启动过程之同进程在一个Activity中启动另一个Activity(基于api21)
目录 1. 前言 2. 正文 2.1 Activity类的startActivity()方法 2.2 Instrumentation类的execStartActivity()方法 2.3 Activi ...
最新文章
- 使用 java 的 displaytag1.2 分页组件使用步骤
- 天津Uber优步司机奖励政策(1月4日~1月10日)
- 【Groovy】MOP 元对象协议与元编程 ( 使用 Groovy 元编程进行函数拦截 | 实现 GroovyInterceptable 接口 | 重写 invokeMethod 方法 )
- matlab 图像处理 新浪 应变,[转载]Matlab图像处理小结
- 开源力量 Linux内核源码深度解析与开发实战
- 动软代码生成器-模板修改,模型修改
- 反向传播算法带动了业界使用两层神经网络研究的热潮
- php给img标签加alt属性
- matlab分频.m,分频器m是什么意思 音响分频器m. TW那个代表高音那个代表是低音?...
- 通用Excel数据导入功能模板
- phpStrom2016.3激活教程
- AndroidStudio配置LitePal时Failed to Resolve
- StringBuffer之间的比较、String和StringBuffer的比较
- 操作系统——段式存储管理
- 程序员如何快乐学习?
- 计算机网络ppt课件教学视频教程,计算机网络高级教程ppt课件
- 鲸交所抢滩PoS 25亿美元市场,正式上线鲸矿池
- XY51S03B蓝牙5.1芯片OBU方案
- java查看kafka数据量_Java kafka监控 topic的数据量count情况,每个topic的offset,
- MT4跟单系统的运行环境、模式与原理分析