[Android] [ Android启动流程 ] [ 上 ] [ UBoot,Linux 阶段 ]

第一阶段: Bootloader / UBoot

bootloader的实现依赖于CPU的体系结构,大多数bootloader都分为stage1和stage2两大部分。
2.1 stage1 (start.s代码结构) —- 基本的硬件初始化
U-Boot 的stage1代码通常放在start.s文件中,它用汇编语言写成,其主要代码部分如下:
手机上电后,硬件会从固定的地址(固化在ROM中)加载bootloader到RAM,然后跳转到bootloader的入口函数开始执行,
(1) 定义入口 。由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROM(Flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
(2)设置异常向量(ExceptionVector)。
(3)设置CPU的速度、时钟频率及中断控制寄存器。
(4)初始化(RAM)内存控制器 。
(5)将ROM中的程序复制到RAM中。
(6)初始化堆栈 。
(7)转到RAM中执行,该工作可使用指令ldrpc来完成。
2.2 stage2 C语言代码部分
libarm/board.c 中的start armboot 是C语言开始的函数,也是整个启动代码中C语言的主函数,同时还是整个U-boot(armboot)的主函数,该函数主要完成如下操作:
(1)调用一系列的初始化函数。
(2)初始化Flash设备。
(3)初始化系统内存分配函数。
(4)如果目标系统拥有NAND设备,则初始化NAND设备。
(5)如果目标系统有显示设备,则初始化该类设备。
(6)初始化相关网络设备,填写IP、MAC地址等。
(7)进入命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。
* 手机开机的第一个logo就是在这个阶段显示的,由于执行能力受限,只能是一张静态小图片*
曾经碰到开机白屏的问题,就是在此阶段修改屏驱解决的
uboot启动流程
uboot启动流程

uboot启动后的存储地址空间情况
uboot启动后的存储地址空间情况

第二阶段:Linux镜像加载与Linux启动阶段

 uBoot 引导内核启动的最后一步是:通过一个函数指针 thekernel()带三个参数跳转到内核( zImage )入口点开始执行,此时, u-boot 的任务已经完成,控制权完全交给内核( zImage )。
1.zImage解压缩
2.Kernel的汇编启动阶段
\arch\arm\kernel\head.S
内核启动第二阶段主要完成的工作有,cpu ID检查,machine ID(也就是开发板ID)检查,创建初始化页表,设置C代码运行环境,跳转到内核第一个真正的C函数startkernel开始执行。
3.Kernel的C启动阶段
将会进入init/Main.c中的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();

    //memblock_reserve((phys_addr_t)0x50000000,(phys_addr_t)0x100000);
    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();
    /*
     * 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();
    /* Interrupts are enabled now so all GFP allocations are safe. */
    gfp_allowed_mask = __GFP_BITS_MASK;
    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();
    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 */
    //printk(KERN_INFO "[mjdbg]MEM Check4:0x%x : 0x%x.\n", (int *)(phys_to_virt(0x50000000)),*(int *)(phys_to_virt(0x50000000)));
    rest_init();
}

1.打印版本信息,如内核、编译器、作者、日期。
2.setup_arch()主要做一些板级初始化
cpu初始化,tag参数解析,
u-boot传递的cmdline解析,
建立mmu工作页表(memtable_init),初始化内存布局,调用mmap_io建立GPIO,IRQ,MEMCTRL,UART,及其他外设的静态映射表,
对时钟,定时器,uart进行初始化,
cpu_init():打印一些关于cpu的信息,比如cpu id,cache 大小等。
另外重要的是设置了IRQ、ABT、UND三种模式的stack空间,分别都是12个字节。最后将系统切换到svc模式。
3.build_all_zonelists():建立系统内存页区(zone)链表
4.printk(KERN_NOTICE “Kernel command line: %s\n”, saved_command_line);打印出从uboot传递过来的command_line字符串,在setup_arch函数中获得的。
5.parse_early_param():这里分析的是系统能够辨别的一些早期参数(这个函数甚至可以去掉,__setup的形式的参数),而且在分析的时候并不是以setup_arch(&command_line)传出来的command_line为基础,而是以最原生态的saved_command_line为基础的。
6.parse_args(“Booting kernel”, command_line, __start___param, __stop___param - __start___param,&unknown_bootoption);
解析启动参数
7.sched_init():
初始化每个处理器的可运行队列,设置系统初始化进程即0号进程。
8.init_IRQ():
初始化系统中所有的中断描述结构数组:irq_desc[NR_IRQS]。
9.softirq_init():
内核的软中断机制初始化函数。
10.console_init():
初始化系统的控制台结构
12.rest_init():
调用kernel_thread()创建1号内核线程。

static noinline void __init_refok rest_init(void)
{
    int pid;
    //printk("**********************************************************\n");
    //printk(" rest_init: 0x%x!!!\n",(*(int *)phys_to_virt(0x50000000)));
    //printk("**********************************************************\n");
#ifdef CONFIG_KERNEL_PANIC_DUMP
    panic_dump_test();
#endif    
    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();
}

13.kernel_init 1号线程初始化
最后对Linux应用程序进行初始化。
1号kernel_init进程完成linux的各项配置(包括启动AP)后,就会在/sbin,/etc,/bin寻找init程序来运行。
该init程序会替换kernel_init进程(注意:并不是创建一个新的进程来运行init程序,而是一次变身,使用sys_execve函数改变核心进程的正文段,将核心进程kernel_init转换成用户进程init),
此时处于内核态的1号kernel_init进程将会转换为用户空间内的1号进程init。
父进程init将根据/etc/inittab中提供的信息完成应用程序的初始化调用。
调用init_post()创建用户模式1号进程。
在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.");
}

第三阶段:Android内核启动

Android的启动过程是从进程init开始的,所以它是后续所有进程的祖先进程。(system/core/init)
1.重新设置子进程终止时信号SIGCHLD的处理函数。

2.将kernel启动过程中建立好的文件系统框架mount到相应目录。

3.open_devnull_stdio(),将init进程的标准输入、输出、出错设备设置为新建的设备节点/dev/null

4.log_init(),创建并打开设备节点/dev/_kmsg_
内核消息常用来调试
cat /dev/kmsg 查看实时内核打印消息
dmesg 查看过往内核消息

5.读取并解析init.rc配置文件
这在ROM开发中,是一个非常重要的节点,经常要查看与修改
先从文件/sys/class/BOOT/BOOT/boot/boot_mode读出启动方式:Factory Mode, ‘4’;ATE Factory Mode, ‘6’。看是否是facatory模式。
如果是的话,需要读取并解析两个文件:init.factory.rc和init.rc。
如果是正常启动,则暂时先读取init.rc。
这里在读取解析文件的时候,是以行为最小可执行单位在解析。
关于书写init.rc文件的脚本语言的规则,可以上网查找。
解析之后并不会马上执行,而是在init进入服务循环之前统一根据其命令本身所带的条件来执行。

6.导入kernel的cmdline,也就是u-boot传递给kernel的参数

7.读取特定平台相关的initrc文件,如init.angler.rc

8.检查解析出来的所有命令行当中是否有属于early-init的,如果有,将其提出来加入到链表action_queue之中,马上将其执行掉。
init.rc中有定义不同阶段的触发动作,early-init是最早的,其它还有on init, on fs等

9.device_init()函数将会打开uevent的netlink socket,遍历/sys/class、/sys/block、/sys/devices目录,检查各级目录的uevent文件,处理在vold服务起来之前由kernel所发出来的device add, remove等事件。

10.property_init(), 顾名思义,是属性初始化。
首先创建一个名字为system_properties的匿名共享内存区域,对并本init进程做mmap读写映射,其余共享它的进程只有读的权限。
然后将这个prop_area结构体通过全局变量system_property_area传递给property services。
接着调用函数load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT)从/default.prop文件中加载编译时生成的属性。
start_property_service()函数首先会调用load_properties_from_file()函数,尝试加载一些属性脚本文件,并将其中的内容写入属性内存块里。从代码里可以看到,主要加载的文件有:
l /system/build.prop
l /system/default.prop(该文件不一定存在)
l /data/local.prop
l /data/property目录里的若干脚本
这也就是用getprop命令能获取到属性由来。
关于属性机制,展开又是一大篇

11.如果在root目录下有initlogo.rle文件存在,这个是两张android字样的缕空图片,将其读入fb中显示到LCD上。
这是android内置默认的logo机制,早被嫌弃
现在基本使用其第二分支,走动画流程,代码是BootAnimation.cpp,最终执行程序是可执行程序是/system/bin/bootanimation,关于动画制作,需绕道

12.设置相应的属性。
13.开始执行以init为trigger的命令行。
14.启动属性服务:property_set_fd = start_property_service();
15.创建一对socket,用来做信号方面的处理。
16.执行eraly-boot和boot为trigger的命令。
17.执行init.rc中以property:开头的属性设置语句,同时使能属性触发方式。
18.利用poll机制监听前面创建的几个fd的动态。

19.启动init.rc中的各种服务
其中,最重要的是zygote进程:
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

详细见下篇,从zygote开始,将开启真正的Android系统的新世界

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值