最新帖子 精华区 社区服务 会员列表 统计排行
主题 : android启动流程分析-从cpu上电到android Lancher应用程序启动完成
admin 离线
级别: 管理员
UID: 1
精华: 1
发帖: 993
金币: 526 个
银元: 488 个
铜钱: 7833 个
技术分: 601 个
在线时间: 736(时)
注册时间: 2010-04-21
最后登录: 2018-01-16
楼主  发表于: 2016-01-02   

android启动流程分析-从cpu上电到android Lancher应用程序启动完成

管理提醒: 本帖被 admin 从 阶段1讨论区 移动到本区(2016-11-20)
这1篇我们看下从cpu上电启动uboot到启动android Lancher应用程序完成的流程,我们不是为了搞清楚启动流程的每个细节,而是从整体上了解整个软件系统大致是怎么运行的。
前面我们有介绍过4412的启动流程,cpu上电后会执行IROM-》BL1->BL2. BL2就是我们的裸机程序,而uboot就是个big裸机程序,主要用来引导linux系统,烧写系统img。
我们先看看uboot的启动流程:
uboot的前1024byte被制作成BL2,从链接脚本可以看出是从arch/arm/cpu/armv7/start.S开始执行,
最开始是个异常向量表,第1条指令是b reset, 把cpu设成SVC32模式,然后跳到cpu_init_crit执行
.globl _start
_start: b    reset
    ldr    pc, _undefined_instruction
    ldr    pc, _software_interrupt
    ldr    pc, _prefetch_abort
    ldr    pc, _data_abort
    ldr    pc, _not_used
    ldr    pc, _irq
    ldr    pc, _fiq

reset:
    /*
     * set the cpu to SVC32 mode
     */
    mrs    r0, cpsr
    bic    r0, r0, #0x1f
    orr    r0, r0, #0xd3
    msr    cpsr,r0
    /* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_crit
#endif
cpu_init_crit:

    bl cache_init
   
    /*
     * Invalidate L1 I/D
     */
    mov    r0, #0            @ set up for MCR
    mcr    p15, 0, r0, c8, c7, 0    @ invalidate TLBs
    mcr    p15, 0, r0, c7, c5, 0    @ invalidate icache

    /*
     * disable MMU stuff and caches
     */
    mrc    p15, 0, r0, c1, c0, 0
    bic    r0, r0, #0x00002000    @ clear bits 13 (--V-)
    bic    r0, r0, #0x00000007    @ clear bits 2:0 (-CAM)
    orr    r0, r0, #0x00000002    @ set bit 1 (--A-) Align
    orr    r0, r0, #0x00000800    @ set bit 12 (Z---) BTB
    mcr    p15, 0, r0, c1, c0, 0

    /*
     * Jump to board specific initialization...
     * The Mask ROM will have already initialized
     * basic memory. Go here to bump up clock rate and handle
     * wake up conditions.
     */
    mov    ip, lr            @ persevere link reg across call
    bl    lowlevel_init        @ go setup pll,mux,memory
    mov    lr, ip            @ restore link
    mov    pc, lr            @ back to my caller


cpu_init_crit里主要是清TLB(页面缓存)、关MMU及Cache等,然后跳到lowlevel_init
lowlevel_init:
    /* init system clock */
    bl    system_clock_init
    /* Memory initialize */
    bl    mem_ctrl_asm_init
    /* init uart for debug */
    bl    uart_asm_init
    /* init system clock */
    bl    system_clock_init

    /* Memory initialize */
    bl    mem_ctrl_asm_init

1:

    bl    tzpc_init

    b    load_uboot

after_copy:

#endif

#ifdef CONFIG_ENABLE_MMU
    bl    enable_mmu
#endif

    ldr    r0, _board_init_f
    mov    pc, r0

load_uboot:
    ldr    r0, =INF_REG_BASE
    ldr    r1, [r0, #INF_REG3_OFFSET]
    cmp    r1, #BOOT_NAND
    beq    nand_boot
    cmp    r1, #BOOT_ONENAND
    beq    onenand_boot
    cmp    r1, #BOOT_MMCSD
    beq    mmcsd_boot
    cmp    r1, #BOOT_EMMC
    beq    emmc_boot
    cmp    r1, #BOOT_EMMC_4_4
    beq    emmc_boot_4_4
    cmp    r1, #BOOT_NOR
    beq    nor_boot
    cmp    r1, #BOOT_SEC_DEV
    beq    mmcsd_boot
lowlevel_init里面就是时钟初始化,uart初始化,enable_mmu等,然后调load_uboot,根据启动方式把uboot的所有代码从sd/emmc里加载到内存后继续执行。
后面就跳到board_init_f执行,board_init_f位于Board.c (uboot_tiny4412\arch\arm\lib),主要是gd_t数据结构空间分配回调一组初始化函数,对gd_t数据结构进行初始化,relocate_code(U-Boot重定义代码,即自搬移)。
然后跳到board_init_r执行,
void board_init_r(gd_t *id, ulong dest_addr)
{
    board_init();    /* Setup chipselects */

#ifdef CONFIG_GENERIC_MMC
    mmc_initialize(bd);
#endif

    /* initialize environment */
    env_relocate();

    /* IP Address */
    gd->bd->bi_ip_addr = getenv_IPaddr("ipaddr");

    stdio_init();    /* get the devices list going. */

    jumptable_init();

#if defined(CONFIG_API)
    /* Initialize API */
    api_init();
#endif

    //console_init_r();    /* fully init console as a device */

     /* set up exceptions */
    interrupt_init();
    /* enable exceptions */
    enable_interrupts();

#ifdef BOARD_LATE_INIT
    board_late_init();
#endif

    /* main_loop() can return to retry autoboot, if so just run it again. */
    for (;;) {
        main_loop();
    }

    /* NOTREACHED - no way out of command loop except booting */
}
board_init_r里就是一些初始化,board_init,api_init,中断使能等,然后跳到main_loop函数,等待命令或自动加载内核。
在uboot启动过程按任意键就会停止等待命令,如果不按就会加载内核执行,加载内核是通过bootcmd命令实现的。
bootcmd命令通过调Cmd_bootm.c里的do_bootm(uboot_tiny4412\common)函数,然后调do_bootm_linux启动linux内核。
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
    kernel_entry = (void (*)(int, int, uint))images->ep;

    setup_start_tag (bd);
    setup_serial_tag (&params);
    setup_revision_tag (&params);
    setup_memory_tags (bd);
    setup_commandline_tag (bd, commandline);
    setup_end_tag(bd);

    kernel_entry(0, machid, bd->bi_boot_params);
    /* does not return */
    return 1;
}
do_bootm_linux里就是设置各种tag作为参数传给kernel, 把函数指针kernel_entry指向内核运行地址,调kernel_entry跳到linux内核去执行,uboot的任务就完成了。

linux kernel编译后的文件是zImage, kernel_entry指向的内核运行地址位于arch/arm/boot/compressed/head.S里,head.S主要就是解压zImage,把linux内核代码
在内存中进行重定位,然后跳到arch/arm/kernel/head.S里去执行,这个才是内核真正启动的地方。
    __HEAD
ENTRY(stext)
    setmode    PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
                        @ and irqs disabled
    mrc    p15, 0, r9, c0, c0        @ get processor id
    bl    __lookup_processor_type        @ r5=procinfo r9=cpuid
    movs    r10, r5                @ invalid processor (r5=0)?
 THUMB( it    eq )        @ force fixup-able long branch encoding
    beq    __error_p            @ yes, error 'p'

#ifndef CONFIG_XIP_KERNEL
    adr    r3, 2f
    ldmia    r3, {r4, r8}
    sub    r4, r3, r4            @ (PHYS_OFFSET - PAGE_OFFSET)
    add    r8, r8, r4            @ PHYS_OFFSET
#else
    ldr    r8, =PLAT_PHYS_OFFSET
#endif

    /*
     * r1 = machine no, r2 = atags or dtb,
     * r8 = phys_offset, r9 = cpuid, r10 = procinfo
     */
    bl    __vet_atags
#ifdef CONFIG_SMP_ON_UP
    bl    __fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT
    bl    __fixup_pv_table
#endif
    bl    __create_page_tables

    /*
     * The following calls CPU specific code in a position independent
     * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
     * xxx_proc_info structure selected by __lookup_processor_type
     * above.  On return, the CPU will be ready for the MMU to be
     * turned on, and r0 will hold the CPU control register value.
     */
    ldr    r13, =__mmap_switched        @ address to jump to after
                        @ mmu has been enabled
    adr    lr, BSYM(1f)            @ return (PIC) address
    mov    r8, r4                @ set TTBR1 to swapper_pg_dir
 ARM(    add    pc, r10, #PROCINFO_INITFUNC    )
 THUMB(    add    r12, r10, #PROCINFO_INITFUNC    )
 THUMB(    mov    pc, r12                )
1:    b    __enable_mmu
ENDPROC(stext)
    .ltorg
#ifndef CONFIG_XIP_KERNEL
2:    .long    .
    .long    PAGE_OFFSET
#endif

__mmap_switched:
    adr    r3, __mmap_switched_data

    ldmia    r3!, {r4, r5, r6, r7}
    cmp    r4, r5                @ Copy data segment if needed
1:    cmpne    r5, r6
    ldrne    fp, [r4], #4
    strne    fp, [r5], #4
    bne    1b

    mov    fp, #0                @ Clear BSS (and zero fp)
1:    cmp    r6, r7
    strcc    fp, [r6],#4
    bcc    1b

 ARM(    ldmia    r3, {r4, r5, r6, r7, sp})
 THUMB(    ldmia    r3, {r4, r5, r6, r7}    )
 THUMB(    ldr    sp, [r3, #16]        )
    str    r9, [r4]            @ Save processor ID
    str    r1, [r5]            @ Save machine type
    str    r2, [r6]            @ Save atags pointer
    bic    r4, r0, #CR_A            @ Clear 'A' bit
    stmia    r7, {r0, r4}            @ Save control register values
    b    start_kernel
ENDPROC(__mmap_switched)

head.S里主要就是检查处理器类型,检查机器类型,创建页表,打开cache和MMU,然后跳到c函数start_kernel去执行,start_kernel是一个非常重要的初始化函数和引导函数,在start_kernel函数里做了大量工作,
函数解释如下:
asmlinkage void __init start_kernel(void)
{
    char * command_line;
    extern const struct kernel_param __start___param[], __stop___param[];

    smp_setup_processor_id();

    /*
     * Need to run as early as possible, to initialize the
     * lockdep hash:
     */
    lockdep_init();
    debug_objects_early_init();

    /*
     * Set up the the initial canary ASAP:
     */
    boot_init_stack_canary();

    cgroup_init_early();

    local_irq_disable();
    early_boot_irqs_disabled = true;

/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
    tick_init();
    boot_cpu_init();
    page_address_init();
    printk(KERN_NOTICE "%s", linux_banner); //打印内核的一些信息,版本,作者,编译器版本,日期等信息。
    setup_arch(&command_line); //设置与体系结构相关的环境
    mm_init_owner(&init_mm, &init_task);
    mm_init_cpumask(&init_mm);
    setup_command_line(command_line);
    setup_nr_cpu_ids();
    setup_per_cpu_areas();
    smp_prepare_boot_cpu();    /* arch-specific boot-cpu hooks */

    build_all_zonelists(NULL);
    page_alloc_init();

    printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
    parse_early_param();
    parse_args("Booting kernel", static_command_line, __start___param,
           __stop___param - __start___param,
           &unknown_bootoption);
    /*
     * These use large bootmem allocations and must precede
     * kmem_cache_init()
     */
    setup_log_buf(0);
    pidhash_init();
    vfs_caches_init_early();
    sort_main_extable();
    trap_init();
    mm_init();

    /*
     * Set up the scheduler prior starting any interrupts (such as the
     * timer interrupt). Full topology setup happens at smp_init()
     * time - but meanwhile we still have a functioning scheduler.
     */
    sched_init();  //初始化每个处理器的可运行队列,设置系统初始化进程即0号进程。
    /*
     * Disable preemption - early bootup scheduling is extremely
     * fragile until we cpu_idle() for the first time.
     */
    preempt_disable();
    if (!irqs_disabled()) {
        printk(KERN_WARNING "start_kernel(): bug: interrupts were "
                "enabled *very* early, fixing it\n");
        local_irq_disable();
    }
    idr_init_cache();
    perf_event_init();
    rcu_init();
    radix_tree_init();
    /* init some links before init_ISA_irqs() */
    early_irq_init();
    init_IRQ(); //初始化系统中所有的中断描述结构数组
    prio_tree_init();
    init_timers();
    hrtimers_init();
    softirq_init(); //内核的软中断机制初始化函数。
    timekeeping_init();
    time_init();
    profile_init();
    call_function_init();
    if (!irqs_disabled())
        printk(KERN_CRIT "start_kernel(): bug: interrupts were "
                 "enabled early\n");
    early_boot_irqs_disabled = false;
    local_irq_enable();

    kmem_cache_init_late();

    /*
     * HACK ALERT! This is early. We're enabling the console before
     * we've done PCI setups etc, and console_init() must be aware of
     * this. But we do want output early, in case something goes wrong.
     */
    console_init();//初始化系统的控制台结构,该函数执行后调用printk函数将log_buf中所有符合打印级别的系统信息打印到控制台上。
    if (panic_later)
        panic(panic_later, panic_param);

    lockdep_info();

    /*
     * Need to run this when irqs are enabled, because it wants
     * to self-test [hard/soft]-irqs on/off lock inversion bugs
     * too:
     */
    locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD
    if (initrd_start && !initrd_below_start_ok &&
        page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
        printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
            "disabling it.\n",
            page_to_pfn(virt_to_page((void *)initrd_start)),
            min_low_pfn);
        initrd_start = 0;
    }
#endif
    page_cgroup_init();
    enable_debug_pagealloc();
    debug_objects_mem_init();
    kmemleak_init();
    setup_per_cpu_pageset();
    numa_policy_init();
    if (late_time_init)
        late_time_init();
    sched_clock_init();
    calibrate_delay();
    pidmap_init();
    anon_vma_init();
#ifdef CONFIG_X86
    if (efi_enabled)
        efi_enter_virtual_mode();
#endif
    thread_info_cache_init();
    cred_init();
    fork_init(totalram_pages);
    proc_caches_init();
    buffer_init();
    key_init();
    security_init();
    dbg_late_init();
    vfs_caches_init(totalram_pages);
    signals_init();
    /* rootfs populating might need page-writeback */
    page_writeback_init();
#ifdef CONFIG_PROC_FS
    proc_root_init();
#endif
    cgroup_init();
    cpuset_init();
    taskstats_init_early();
    delayacct_init();

    check_bugs();

    acpi_early_init(); /* before LAPIC and SMP init */
    sfi_init_late();

    ftrace_init();

    /* Do the rest non-__init'ed, we're now alive */
    rest_init();
}
static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);

    /*
     * The boot idle thread must execute schedule()
     * at least once to get things moving:
     */
    init_idle_bootup_task(current);
    preempt_enable_no_resched();
    schedule();
    preempt_disable();

    /* Call into cpu_idle with preempt disabled */
    cpu_idle();
}
start_kernel里调用一系列的初始化函数后最终调用rest_init函数, rest_init里创建2个内核线程kernel_init和kthreadd,分别叫做1号内核线程和2号内核线程,因为创建它们的父进程叫0号启动进程。
创建完2个线程后内核就自己调用cpu_idle()函数隐退了。然后看内核线程kernel_init的执行,kernel_init里主要会挂载根文件系统,然后执行下面的init_post函数。
static noinline int init_post(void)
{
    /* need to finish all async __init code before freeing the memory */
    async_synchronize_full();
    free_initmem();
    mark_rodata_ro();
    system_state = SYSTEM_RUNNING;
    numa_default_policy();


    current->signal->flags |= SIGNAL_UNKILLABLE;

    if (ramdisk_execute_command) {
        run_init_process(ramdisk_execute_command);
        printk(KERN_WARNING "Failed to execute %s\n",
                ramdisk_execute_command);
    }

    /*
     * We try each of these until one succeeds.
     *
     * The Bourne shell can be used instead of init if we are
     * trying to recover a really broken machine.
     */
    if (execute_command) {
        run_init_process(execute_command);
        printk(KERN_WARNING "Failed to execute %s.  Attempting "
                    "defaults...\n", execute_command);
    }
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

    panic("No init found.  Try passing init= option to kernel. "
          "See Linux Documentation/init.txt for guidance.");
}
这里执行的init程序需要我们在u-boot传给kernel的cmdline中使用init=/init来告知kernel,或者kernel启动代码中直接写死。
init进程是linux起来之后启动的第一个用户进程,位于ramdisk根文件系统里,android系统也就是在这个进程的基础上启动的。进程号是1。
android系统编译出来后主要有3个文件ramdisk.img,system.img,userdata.img。内核启动过程中会挂载ramdisk里的内容作为根文件系统,然后执行
ramdisk里的init进程。system和userdata分别加载到ramdisk文件系统中的system和data目录下.
init进程位于system/core/init目录下,主要是解析init.rc文件,根据文件里的配置启动各种服务进程。
每个服务都启动一个进程,父进程都是1号进程。主要有下面一些服务进程。
console: start a shell,code path: system/bin/sh,其源码中包含常用的shell命令,如ls,cd等。
adbd: start adb daemon,通常带有disabled的选项,表明需要按名字启动,code path:system/bin/adb。
servicemanager:这个服务管理着系统内所有binder services。code path: frameworks/base/cmds/servicemanager。
Vold: android 的udev,code path: system/vold。
Netd: start ntd daemon, code path: system/netd。
Debuggerd: start debug system, code path: system/core/debuggerd。
zygote: 孵化进程,这是一个非常重要的服务,所有的应用程序进程都是基于这个进程创建的。start Android  Java Runtime  and start systemserver。code path:frameworks/base/cmds/app_process。
Installd: start install package daemon, code path:frameworks/base/cmds/installd。

android的启动流程是以zygote进程为基础继续往下执行的,我们看看zygote的启动流程。在int.rc我们可以看到有注册下面的service.
就是启动/system/bin/app_process进程的main函数作为zygote进程,代码位于frameworks/base/cmds/app_process。
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd


int main(int argc, char* const argv[])
{
   
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments
    // ignore argv[0]

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}
main函数里主要是调runtime.start("com.android.internal.os.ZygoteInit启动一个java虚拟机实例,然后调ZygoteInit.java里的main函数
public static void main(String argv[]) {
        try {
            registerZygoteSocket(socketName);
          

            if (startSystemServer) {
                startSystemServer(abiList, socketName);
            }

            Log.i(TAG, "Accepting command socket connections");
            runSelectLoop(abiList);
    }
main函数里主要就是registerZygoteSocket,注册zygote这个进程作为服务端的socket,后面的应用程序进程都是通过socket跟zygote通信,由zygote创建的,这些进程的父
进程ID都是zygote的id.注册后就调runSelectLoop循环等待socket消息过来。
startSystemServer函数zygote会创建一个新进程systemserver,最终调到frameworks/base/services/java/com/android/server/SystemServer.java里的main函数
systemserver里的main函数就是启动android framework里常见的各种service了,activitymanaerservice,contentservice,windowmanagerservice,accountmanagerservice.packagemanagerservice
activitymanagerservice启动后会调mActivityManagerService.systemReady函数,里面接着会调startHomeActivityLocked函数,根据android.intent.category.HOME的intent
找到Home Lancher app,然后启动此app,android系统就启动起来了,我们看到的各种app图标都是Lancher这个app显示出来的。
public void systemReady(final Runnable goingCallback) {

            // Start up initial activity.
            mBooting = true;
            startHomeActivityLocked(mCurrentUserId);
}

    boolean startHomeActivityLocked(int userId) {
        if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
                && mTopAction == null) {
            // We are running in factory test mode, but unable to find
            // the factory test app, so just sit around displaying the
            // error message and don't try to start anything.
            return false;
        }
        Intent intent = getHomeIntent();
        ActivityInfo aInfo =
            resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
        if (aInfo != null) {
            intent.setComponent(new ComponentName(
                    aInfo.applicationInfo.packageName, aInfo.name));
            // Don't do this if the home app is currently being
            // instrumented.
            aInfo = new ActivityInfo(aInfo);
            aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
            ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                    aInfo.applicationInfo.uid, true);
            if (app == null || app.instrumentationClass == null) {
                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
                mStackSupervisor.startHomeActivity(intent, aInfo);
            }
        }

        return true;
    }
描述
快速回复

批量上传需要先选择文件,再选择上传
认证码:

验证问题:
printf("%d", 13)
按"Ctrl+Enter"直接提交