• 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状态 */
    #else
        bootstrap_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) */
    static int bootstrap2(void *arg)
    {
        dprintf(SPEW, "top of bootstrap2()
    ");
    
        arch_init(); /* arch处理器架构第二阶段初始化 */
    
        // 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
    ");
        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内核 */
    
        return 0;
    }

    在代码中,比较重要的是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 {
        const char *name;
        app_init  init;
        app_entry entry;
        unsigned int flags;
    };
    
    #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_SCREEN
        dprintf(INFO, "Display Init: Start
    ");
        target_display_init(device.display_panel);
        dprintf(INFO, "Display Init: Done
    ");
    #endif
    
        target_serialno((unsigned char *) sn_buf);
        dprintf(SPEW,"serial number: %s
    ", sn_buf);
        memset(display_panel_buf, '', MAX_PANEL_BUF_SIZE);
    
        /*
         * Check power off reason if user force reset,
         * if yes phone will do normal boot.
         */
        if (is_user_force_reset())
            goto normal_boot;
    
        /* Check if we should do something other than booting up */
        if (keys_get_state(KEY_VOLUMEUP) && keys_get_state(KEY_VOLUMEDOWN)) /* 根据按键进入到不同的启动模式 */
        {
            dprintf(ALWAYS,"dload mode key sequence detected
    ");
            if (set_download_mode(EMERGENCY_DLOAD))
            {
                dprintf(CRITICAL, "dload mode not supported by target
    ");
            }
            else
            {
                reboot_device(DLOAD);
                dprintf(CRITICAL,"Failed to reboot into dload mode
    ");
            }
            boot_into_fastboot = true;
        }
        if (!boot_into_fastboot)
        {
            if (keys_get_state(KEY_HOME) || keys_get_state(KEY_BACK))
                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
    #if !VBOOT_MOTA
            else 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
    #endif
    
    normal_boot:
        if (!boot_into_fastboot)
        {
            if (target_is_emmc_boot())
            {
                if(emmc_recovery_init())
                    dprintf(ALWAYS,"error in emmc_recovery_init
    ");
                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
                    }
                }
                boot_linux_from_mmc();    /* 从emmc读取linux内核镜像并启动 */
            }
            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();
            }
            dprintf(CRITICAL, "ERROR: Could not do normal boot. Reverting "
                "to fastboot mode.
    ");
        }
    
        /* 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
    }

    aboot_init()函数被调用后,首先是判断目标板是从emmc还是nand flash启动,判断完存储介质后,读取相应的页面和块大小,读取设备的信息,然后调用target_display_init()函数将LCD接口进行初始化,并在屏幕上显示出log图片,接下来,就是判断启动模式,对于emmc存储介质,则会调用boot_linux_from_mmc()函数,从emmc介质中读取Linux内核镜像,并启动Linux系统,aboot_init()函数最主要的功能就是要启动Linux内核,在这,只是简单阐述启动流程,需要了解更详细的内容,可以深入源码分析。

    7、小结

     本篇文章简单介绍了Android系统中LK启动流程,LK是一个轻量级的线程系统,是一个Bootloader,其最主要的目的就是将Linux内核镜像从emmc或nand flash中加载入RAM中,然后将Linux内核系统启动起来。

  • 相关阅读:
    记某b/s项目维护思路及心得
    Csla.Net架构学习总结
    ado.net entity framework使用odp.net(ODAC for .net)连接oracle11g体验
    向北走,还是向南走。也谈创业...
    星级豪华酒店如何提高利润回报率
    .net compact framework2.0 Wince智能设备开发项目经验分享 .net拖空间之进阶篇
    Cipherlab CPT9300手持扫描枪开发体验
    LogMiner日志分析工具的使用
    微软首度承认Linux威胁Windows
    有关SYSDATE与DBLINK的问题
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12012978.html
Copyright © 2020-2023  润新知