LK是(L)ittle (K)ernel的缩写,是一个功能及其强大的bootloader开源项目,但现在只支持arm和x86平台。
LK的一个显著的特点就是它实现了一个简单的线程机制(thread),和对高通处理器的深度定制和使用。因此高通平台android普遍采用LK作为其bootloader。但是,LK只是整个系统的引导部分。
1,lk的代码链接方式以及第一行代码的位置
本文以高通平台为例,编译lk的命令是make aboot,编译后生成一个emmc_appsboot.mbn image文件。mbn格式是高通包含了特定运营商定制的一套efs,nv的集成包文件。大致格式可以认为和elf相似。
确定bootloader/lk第一行执行的代码
整个系统的启动顺序是PBL加载运行SBL1,SBL1加载运行LK,LK加载运行kernel,kernel启动android。
关于这部分更详细的讲解可以参考linux驱动由浅入深系列:PBL-SBL1-(bootloader)LK-Android启动过程详解之一(高通MSM8953启动实例)
其中我们知道LK的image是 emmc_appsboot.mbn,它被PBL加载进内存后,其代码段第一条指令就是emmc_appsboot.mbn在编译链接时有ld链接脚本确定的。
查看lk相应的链接脚本发现
在 bootable\bootloader\lk\arch\arm目录下有 system-onesegment.ld、system-twosegment.ld两个链接脚本,这就是lk第一阶段和第二阶段的两个链接脚本。
整个lk第一行代码的位置由第一阶段链接脚本确定,查看system-onesegment.ld内容如下:
我们在bootable\bootloader\lk\arch\arm\crt0.S中
声明symbol是全局可见的。标号_start是GNU链接器用来指定第一个要执行指令所必须的,同样的是全局可见的(并且只能出现在一个模块中)。
2,lk代码的简要分析
LK 代码结构
+app // 应用相关
+arch // arm 体系
+dev // 设备相关
+include // 头文件
+kernel // lk系统相关
+platform // 相关驱动
+projiect // makefile文件
+scripts // Jtag 脚本
+target // 具体板子相关
bl kmain跳转到c函数kmain,之后进入c语言世界。
2,kmain函数
kmain函数是被汇编调用的c函数,从下面代码第一行注释中也可以看到
3,bootstrap2线程
bootstrap2线程的函数体也在
4,lk上的app
apps_init函数实现如下
__apps_start段我们需要去ld文件中找了,在system-onesegment.ld中我们找到如下定义:
在app.h中我们找到了*.apps段的一个宏
bootloader/lk/app/aboot/aboot.c中
aboot.c中的aboot_init是较为重要的一个函数,其他暂时忽略了。
LK的一个显著的特点就是它实现了一个简单的线程机制(thread),和对高通处理器的深度定制和使用。因此高通平台android普遍采用LK作为其bootloader。但是,LK只是整个系统的引导部分。
1,lk的代码链接方式以及第一行代码的位置
本文以高通平台为例,编译lk的命令是make aboot,编译后生成一个emmc_appsboot.mbn image文件。mbn格式是高通包含了特定运营商定制的一套efs,nv的集成包文件。大致格式可以认为和elf相似。
确定bootloader/lk第一行执行的代码
整个系统的启动顺序是PBL加载运行SBL1,SBL1加载运行LK,LK加载运行kernel,kernel启动android。
关于这部分更详细的讲解可以参考linux驱动由浅入深系列:PBL-SBL1-(bootloader)LK-Android启动过程详解之一(高通MSM8953启动实例)
其中我们知道LK的image是 emmc_appsboot.mbn,它被PBL加载进内存后,其代码段第一条指令就是emmc_appsboot.mbn在编译链接时有ld链接脚本确定的。
查看lk相应的链接脚本发现
在 bootable\bootloader\lk\arch\arm目录下有 system-onesegment.ld、system-twosegment.ld两个链接脚本,这就是lk第一阶段和第二阶段的两个链接脚本。
整个lk第一行代码的位置由第一阶段链接脚本确定,查看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) }
}
链接脚本的一个主要目的是描述输入文件中的各个段(数据段,代码段,堆,栈,bss)如何被映射到输出文件中,并控制输出文件的内存排布。其中用ENTRY定义了入口位置为_start。
我们在bootable\bootloader\lk\arch\arm\crt0.S中
.globl _start
_start:
b reset
reset:
#ifdef ENABLE_TRUSTZONE
/*Add reference to TZ symbol so linker includes it in final image */
ldr r7, =_binary_tzbsp_tzbsp_bin_start
#endif
/* do some cpu setup */
#if ARM_WITH_CP15
/* Read SCTLR */
mrc p15, 0, r0, c1, c0, 0
/* XXX this is currently for arm926, revist with armv6 cores */
/* new thumb behavior, low exception vectors, i/d cache disable, mmu disabled */
bic r0, r0, #(1<<15| 1<<13 | 1<<12)
bic r0, r0, #(1<<2 | 1<<0)
/* disable alignment faults */
bic r0, r0, #(1<<1)
/* Enable CP15 barriers by default */
#ifdef ARM_CORE_V8
orr r0, r0, #(1<<5)
#endif
/* Write SCTLR */
mcr p15, 0, r0, c1, c0, 0
#ifdef ENABLE_TRUSTZONE
/*nkazi: not needed ? Setting VBAR to location of new vector table : 0x80000 */
ldr r0, =0x00080000
mcr p15, 0, r0, c12, c0, 0
#endif
#endif
bl kmain ////////////////跳转到c函数kmain,离开汇编世界
其中.global _start定义了_start这个全局符号。.global 使得连接程序(ld)能够识别 symbl
声明symbol是全局可见的。标号_start是GNU链接器用来指定第一个要执行指令所必须的,同样的是全局可见的(并且只能出现在一个模块中)。
2,lk代码的简要分析
LK 代码结构
+app // 应用相关
+arch // arm 体系
+dev // 设备相关
+include // 头文件
+kernel // lk系统相关
+platform // 相关驱动
+projiect // makefile文件
+scripts // Jtag 脚本
+target // 具体板子相关
1,整个lk image的第一行代码就是crt0.S文件中的_start标号所在的行。
从上面的代码片段可知_start指示的是b reset,即先执行开机reset,初始时cpu等芯片相关逻辑,最后
2,kmain函数
kmain函数是被汇编调用的c函数,从下面代码第一行注释中也可以看到
bootable/bootloader/lk/kernel/main.c
/* called from crt0.S */
void kmain(void) __NO_RETURN __EXTERNALLY_VISIBLE;
void kmain(void)
{
thread_t *thr;
// get us into some sort of thread context
thread_init_early();////////////lk中简单线程相关结构体初始化
// early arch stuff
arch_early_init();//////////////arm平台相关初始化,关闭cache、是能mmu等
// do any super early platform initialization
platform_early_init();///////////硬件板子相关初始化,初始化串口等
// do any super early target initialization
target_early_init();
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();///////////////////堆栈初始化
__stack_chk_guard_setup();
// initialize the threading system
dprintf(SPEW, "initializing threads\n");
thread_init();/////////////////线程相关初始化
// initialize the dpc system
dprintf(SPEW, "initializing dpc\n");
dpc_init();
// initialize kernel timers
dprintf(SPEW, "initializing timers\n");
timer_init();/////////////////timer相关初始化
#if (!ENABLE_NANDWRITE)
// create a thread to complete system initialization
dprintf(SPEW, "creating bootstrap completion thread\n");
thr = thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);//////////启动bootstrap2线程
if (!thr)
{
panic("failed to create thread bootstrap2\n");
}
thread_resume(thr);
// enable interrupts
exit_critical_section();
// become the idle thread
thread_become_idle();
#else
bootstrap_nandwrite();
#endif
}
kmain中完成了必要的初始化后,调用thread_create启动了bootstrap2线程
3,bootstrap2线程
bootstrap2线程的函数体也在
bootable/bootloader/lk/kernel/main.c
static int bootstrap2(void *arg)
{
dprintf(SPEW, "top of bootstrap2()\n");
arch_init();
// 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();//////////////时钟clk相关处理
// initialize the target
dprintf(SPEW, "initializing target\n");
target_init();//////////////分区表相关处理
dprintf(SPEW, "calling apps_init()\n");
apps_init();///////////////启动lk中的app了
return 0;
}
bootstrap2线程中处理了clk和分区表配置后,启动了lk这个小型内核上的应用程序apps。
4,lk上的app
apps_init函数实现如下
void apps_init(void)
{
const struct app_descriptor *app;
/* call all the init routines */
for (app = &__apps_start; app != &__apps_end; app++) {
if (app->init)
app->init(app);
}
/* 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);
}
}
}
其中调用所有位于__apps_start与__apps_end之间的函数。
__apps_start段我们需要去ld文件中找了,在system-onesegment.ld中我们找到如下定义:
__apps_start = .;
KEEP (*(.apps))
__apps_end = .;
可见是要所有放在了*.apps段中的函数了
在app.h中我们找到了*.apps段的一个宏
#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = { .name = #appname,
#define APP_END };
搜索发现使用APP_START添加了__SECTION段名的函数有这些:
bootloader/lk/app/aboot/aboot.c中
APP_START(aboot)
.init = aboot_init,
APP_END
bootloader/lk/app/aboot/shell.c中
APP_START(shell)
.init = shell_init,
.entry = shell_entry,
APP_END
5,aboot
aboot.c中的aboot_init是较为重要的一个函数,其他暂时忽略了。
bootloader/lk/app/aboot/aboot.c
void aboot_init(const struct app_descriptor *app)
{
unsigned reboot_mode = 0;
int boot_err_type = 0;
int boot_slot = INVALID;
if (target_is_emmc_boot())
{
page_size = mmc_page_size();/////////////////设置NAND、EMMC读取的页大小
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;
}
read_allow_oem_unlock(&device);/////////////////OEM锁
#if ENABLE_WBC
/* Wait if the display shutdown is in progress */
while(pm_app_display_shutdown_in_prgs());
if (!pm_appsbl_display_init_done())
target_display_init(device.display_panel);
else
display_image_on_screen();//////////////lk中显示开机logo
#else
target_display_init(device.display_panel);
#endif
/* Check if we should do something other than booting up */
if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN))////////判断开机时按键状态进入不同模式
{
#if 1
boot_into_ffbm = true;
strcpy(ffbm_mode_string, "ffbm-00");
#else
dprintf(ALWAYS,"dload mode key sequence detected\n");
reboot_device(EMERGENCY_DLOAD);
dprintf(CRITICAL,"Failed to reboot into dload mode\n");
boot_into_fastboot = true;
#endif
}
if (!boot_into_fastboot && !boot_into_ffbm)
{
if (keys_get_state(KEY_HOME) || keys_get_state(KEY_VOLUMEUP))
{
if (target_pause_for_battery_charge())
reboot_device(EMERGENCY_DLOAD);
else
boot_into_recovery = 1;
}
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 = 1;
}
else if(reboot_mode == FASTBOOT_MODE)
{
boot_into_fastboot = true;
}
else if(reboot_mode == ALARM_BOOT)
{
boot_reason_alarm = true;
}
#if VERIFIED_BOOT
else if (VB_V2 == target_get_vb_version())
{
if (reboot_mode == DM_VERITY_ENFORCING)
{
device.verity_mode = 1;
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 = 0;
write_device_info(&device);
}
else if (reboot_mode == DM_VERITY_KEYSCLEAR)
{
if(send_delete_keys_to_tz())
ASSERT(0);
}
}
#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
}
}
retry_boot:
/* Trying to boot active partition */
if (partition_multislot_is_supported())
{
boot_slot = partition_find_boot_slot();
partition_mark_active_slot(boot_slot);
if (boot_slot == INVALID)
goto fastboot;////////////////////进入fastboot
}
boot_err_type = boot_linux_from_mmc();
switch (boot_err_type)
{
case ERR_INVALID_PAGE_SIZE:
case ERR_DT_PARSE:
case ERR_ABOOT_ADDR_OVERLAP:
if(partition_multislot_is_supported())
goto retry_boot;
else
break;
case ERR_INVALID_BOOT_MAGIC:
default:
break;
/* going to fastboot menu */
}
}
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();//////////////////////////正常启动linux内核
}
dprintf(CRITICAL, "ERROR: Could not do normal boot. Reverting "
"to fastboot mode.\n");
}
fastboot:
/* 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
}
至此lk启动过程简要分析结束!!