LK(little kernel)第一行代码链接位置分析及lk启动过程

来源:互联网 发布:linux查看svn当前分支 编辑:程序博客网 时间:2024/06/13 00:07
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内容如下:
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:bresetreset:#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 */mrcp15, 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 */bicr0, r0, #(1<<15| 1<<13 | 1<<12)bicr0, r0, #(1<<2 | 1<<0)/* disable alignment faults */bicr0, r0, #(1<<1)/* Enable CP15 barriers by default */#ifdef ARM_CORE_V8orrr0, r0, #(1<<5)#endif        /* Write SCTLR */mcrp15, 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#endifblkmain ////////////////跳转到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等芯片相关逻辑,最后

bl        kmain跳转到c函数kmain,之后进入c语言世界。
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 contextthread_init_early();////////////lk中简单线程相关结构体初始化// early arch stuffarch_early_init();//////////////arm平台相关初始化,关闭cache、是能mmu等// do any super early platform initializationplatform_early_init();///////////硬件板子相关初始化,初始化串口等// do any super early target initializationtarget_early_init();dprintf(INFO, "welcome to lk\n\n");bs_set_timestamp(BS_BL_START);// deal with any static constructorsdprintf(SPEW, "calling constructors\n");call_constructors();// bring up the kernel heapdprintf(SPEW, "initializing heap\n");heap_init();///////////////////堆栈初始化__stack_chk_guard_setup();// initialize the threading systemdprintf(SPEW, "initializing threads\n");thread_init();/////////////////线程相关初始化// initialize the dpc systemdprintf(SPEW, "initializing dpc\n");dpc_init();// initialize kernel timersdprintf(SPEW, "initializing timers\n");timer_init();/////////////////timer相关初始化#if (!ENABLE_NANDWRITE)// create a thread to complete system initializationdprintf(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 interruptsexit_critical_section();// become the idle threadthread_become_idle();#else        bootstrap_nandwrite();#endif}
kmain中完成了必要的初始化后,调用thread_create启动了bootstrap2线程
3,bootstrap2线程
bootstrap2线程的函数体也在
bootable/bootloader/lk/kernel/main.cstatic int bootstrap2(void *arg){dprintf(SPEW, "top of bootstrap2()\n");arch_init();// XXX put this somewhere else#if WITH_LIB_BIObio_init();#endif#if WITH_LIB_FSfs_init();#endif// initialize the rest of the platformdprintf(SPEW, "initializing platform\n");platform_init();//////////////时钟clk相关处理// initialize the targetdprintf(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_ENDbootloader/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.cvoid 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);elsedisplay_image_on_screen();//////////////lk中显示开机logo#elsetarget_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 1boot_into_ffbm = true;strcpy(ffbm_mode_string, "ffbm-00");#elsedprintf(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);elseboot_into_recovery = 1;}if (!boot_into_recovery &&(keys_get_state(KEY_BACK) || keys_get_state(KEY_VOLUMEDOWN)))boot_into_fastboot = true;}#if NO_KEYPAD_DRIVERif (fastboot_trigger())boot_into_fastboot = true;#endif#if USE_PON_REBOOT_REGreboot_mode = check_hard_reboot_mode();#elsereboot_mode = check_reboot_mode();#endifif (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_BOOTelse if (VB_V2 == target_get_vb_version()){if (reboot_mode == DM_VERITY_ENFORCING){device.verity_mode = 1;write_device_info(&device);}#if ENABLE_VB_ATTESTelse if (reboot_mode == DM_VERITY_EIO)#elseelse 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);}}#endifnormal_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_FUSEset_tamper_fuse_cmd();#endif#if USE_PCOM_SECBOOTset_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;elsebreak;case ERR_INVALID_BOOT_MAGIC:default:break;/* going to fastboot menu */}}else{recovery_init();#if USE_PCOM_SECBOOTif((device.is_unlocked) || (device.is_tampered))set_tamper_flag(device.is_tampered);#endifboot_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_MSGdisplay_fastboot_menu();#endif}
至此lk启动过程简要分析结束!!
原创粉丝点击