本篇文章是对初步学习Android系统lk启动流程的一个大致简介。方便掌握lk启动流程的大致框架,具体细节后续再进行更新
1. 前言
需要了解的文件类型:
1)编译LK的链接文件(.ld)
2)汇编文件(.s、.S文件)
3).mk文件(用来向编译系统描述源代码,并将源文件分组为模块。可用于预定义变量)
4)makefile文件(包含一些规则告诉make编译哪些文件以及怎样编译这些文件)
5)Kconfig文件(编译前的文件,其中主要作用是在内核配置的时候,作为配置选项)
6).config文件(文件是在进行内核配置的时候,经过配置后生成的内核编译参考文件)
文件之间的顺序关系:
编写Kconfig—>进行make menuconfig---->生成.config----->编写Makefile---->按照Makefile编译规则进行编译---->编译成功
1、lk加载完成后,linux kernel又是怎样加载的?里面又做了些什么操作?
2、关于boot、kernel、HAL等不同层级,程序是怎样被加载、不同层级如何分化完成上层应用的请求任务?
2. LK入口
确定LK的入口,必须要先知道编译LK的链接文件
相关的链接文件为:
bootable/bootloader/lk/arch/arm/system-onesegment.ld
从链接文件中,可以确定LK启动入口为_start函数,该函数的定义在汇编文件:
_start函数的定义在汇编文件:
bootable/bootloader/lk/arch/arm/ctr0.S
_start函数的主要功能是设置中断向量表、初始化bss段、初始化与处理器架构的相关寄存器、搭建C运行环境等;
3. kmain函数
当_start函数设置完成后,然后开始运行bl kmain代码,跳转到kmain函数处运行,进入的C语言的世界
kmain入口(ctr0.S文件,line.187行)
bl kmain /*跳到kmain函数执行*/b .
在_start函数的最后,将会调用kmain函数,该函数的定义在文件:
bootable/bootloader/lk/kernel/main.c
对于kmain函数实现的主要功能:
1)函数调用后,首先是对早期的thread线程系统进行初始化,
2)接下来则是调用arch_early_init()函数,对CPU处理器架构相关的早期初始化,例如关闭cache,使能mmu等功能,
3)然后开始调用与平台早期初始化的相关函数,对早期需要使用的外设进行初始化,例如中断控制器、debug串口等外设,
4)接下来,则是调用函数搭建出一个完整的thread线程系统,并对lk中的定时器进行初始化,
5)调用thread_create()函数创建出"bootstrap2"线程,并调用thread_resume()函数,让该线程在系统中工作,
6)最后,则是设置kmain线程为idle状态。
拓展思考(boot程序加载流程)
- 复位键,早期的硬件初始化;
- 对CPU处理器架构相关的早期初始化,调用与平台、外设早期初始化的相关函数(如:中断控制器、debug串口等外设)
- 调用函数,搭建出一个完整的thread线程系统,并对lk中的定时器进行初始化
- 调用thread_create()函数创建出"bootstrap2"线程,并调用thread_resume()函数,让该线程在系统中工作,
- 线程设置中断响应请求,完成请求后,设置kmain线程为idle状态,等待下一个中断请求任务。
4. bootstrap2线程分析
1)使用thread_create()函数创建出"bootstrap2"线程后,并使用thread_resume()启动该线程后,接下来将会运行bootstrap2()函数,该函数可以看成是lk启动的第二阶段,它将会继续完成外设的初始化和启动。
2)在kmain函数的最后阶段,在thread线程系统搭建完成后,将会运行下面的代码创建出bootstrap2线程:
thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
3)此时,将会跳转到bootstrap2函数继续运行,完成整个lk系统启动,bootstarp2函数的定义在文件:
bootable/bootloader/lk/kernel/main.c
对于bootstrap2函数实现的主要功能:
1)arch_init(); /*arch处理器架构第二阶段初始化*/
2)platform_init(); /*platform第二阶段初始化*/
3)target_init(); /*target第二阶段初始化,按键、分区表等*/
4)apps_init(); /*创建多个app线程并运行,aboot_init将加载Linux内核*/
在该函数中,比较重要的是target_init()函数和apps_init()函数,target_init()函数将针对不同的硬件平台进行一些外设初始化,例如,按键、emmc分区等;apps_init()函数则是将整个lk系统要启动的app全部进行启动运行,本质是使用thread_create()函数和thread_resume()函数,创建多个线程并在lk系统中调度线程,比较重要的是aboot_init线程(MTK平台是mt_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[];
static void start_app(const struct app_descriptor *app);
/* 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定义类似。如shell.c、mt_boot.c文件等文件,都导入了app.h,在文本的末尾都使用了APP_START、APP_END宏的定义。
(lk的app包含哪些?全部在app文件夹当中:aboot、mt_boot、shell、clocktests、pcitests、string_tests.c、tests.c等)
在aboot.c、mt_boot.c、shell.c、test.c文件中,都包含了{.init=aboot_init,}该行代码,因此aboot_init、mt_boot_init、shell_init这些在lk阶段都会启动(但从平台的串口日志加了注释标记后,看出aboot_init并没有执行,待后续再继续研究分析)
而在test.c文件中,除了包含{.init=aboot_init,}该行代码,还设置了标志位flags=0;shell.c文件中,包含{.init=shell_init, .entry=shell_entry,},因此,会在执行完shell_init后,再去执行shell_entry函数。
6. aboot_init函数分析
对于aboot_init()函数的定义在文件:
bootable/bootloader/lk/app/aboot/aboot.c
函数的内容如下所示:
void aboot_init(const struct app_descriptor *app)
{
unsigned reboot_mode = 0;
unsigned usb_init = 0;
unsigned sz = 0;
/* Setup page size information for nand/emmc reads */
if (target_is_emmc_boot()) /*判断目标板是否是emmc启动*/
{
page_size = 2048;
page_mask = page_size - 1;
}
else
{
page_size = flash_page_size(); /*读取对应存储介质的page和block大小*/
page_mask = page_size - 1;
}
if(target_use_signed_kernel())
{
read_device_info(&device); /*读取设备的信息*/
}
可以看出aboot_init主要工作如下:
1、确定page_size大小;
2、从devinfo分区获取devinfo信息;
3、根据条件判断进入不同模式,设置对应标志位boot_into_xxx;
4、进入fastboot模式,初始化fastboot命令等。