Android启动过程的底层实现(一)

     目录

1、正常模式的启动流程

2、内核启动过程

2.1 内核引导阶段

2.2 内核启动阶段


        Android支持多种启动模式,主要有正常模式(normal mode)、安全模式(safe mode)、恢复模式(recorvery mode)、工厂模式(factory mode)、快速启动模式(fastboot mode)等。本文讲述正常模式下Android的启动过程。本文代码基于Android kernel 4.4。

1、正常模式的启动流程

        (1)、Boot ROM阶段:系统加电,固化在处理器ROM中的启动引导代码(BIOS)开始执行,进行一系列加电自检动作,之后会去寻找BootLoader的代码并将其加载进内存。(由芯片厂商设计实现,代码是固定在某个位置的)

        (2)、BootLoader阶段:BootLoader代码又称为“引导装入程序”。其中MBR()中主引导程序扫描分区表,寻找活动分区,将活动分区引导记录中的次引导程序加载进内存并执行。次引导记录负责加载Linux内核映像,之后将控制权交给内核。

        (3)、kernel阶段:内核加载进内存后,将首先进入内核引导阶段。进行初始化各种硬件环境、加载驱动程序。最后通过调用start_kernel进入内核启动阶段。在内核启动阶段,进行一系列应用级初始化后,最终启动用户空间的init程序。

        (4)、init进程:init程序首先创建基本的文件系统目录并挂载相关的文件系统。然后通过对init.rc配置文件解析将相关进程启动起来,其中最重要的两个守护进程就是zygote进程和servicemanager进程。之后在一个无限循环中监听和处理事件,包括重启异常退出的service、循环处理来自属性服务(property service)、signal和keyboard的事件。

        (5)、zygote进程:zygote虚拟机启动子进程system_server,在其中开启Android核心系统服务并将核心系统服务添加到ServiceManager中,然后系统进入systemReady状态。

        (6)、ActivityManagerService进程:在systemReady状态下,ActivityManagerService与zygote中的socket进行通信,通过zygote启动home应用进入系统桌面。

2、内核启动过程

2.1 内核引导阶段

        head.S是Linux内核启动的汇编程序入口,但head.S中并没有直接调用start_kernel()。可以先找到调用start_kernel()的函数,然后通过回溯法找到其调用的源头。在head-common.S中可以找到start_kerne()的调用位置,代码如下:

//函数_mmap_switched 
__mmap_switched:
......
//这里真正调用了start kernel 
    b    start kernel //第104行
//b指令是跳转指令。遇到b指令,处理器跳转到给定@的目标地址,从目标地址继续执行

        从head-common.S代码中可以看到,系统在__mmap_switched中通过b汇编指令调用了 start_kernel。那么__mmap_switched又是在哪里被调用的呢?回到head.s这个汇编入口,代码如下:

//MMU可用后,将跳转到这个地址 
ldr    r13, =__map_switched //第135行
adr    1r, BSYM(1f)    @ 返回地址(PIC)
mov    r8, r4
ARM(   add pc   r10, #PROCINFO INITFUNC) 
THUMB( add r12, r10, #PROCINFO INITFUNC) 
THUMB( mov pc, r12)
1:b    __enable_mmu @启动MMU 
ENDPROC(stext)

        这里将__mmap_switched的地址保存到r13。那pc指针又是什么时候指向r13的呢!在源码中搜索,发现是在__turn_mmu_on结束之时将pc指针指向r13的。代码如下:

.align  5
__turn_mmu_on:
...//省略部分代码
mov r3, r13 //跳转到r13,即__mmap_switched //第478行 
mov pc, r3 //pc指针,指向当前要运行的指令 
__enable_mmu_end:
ENDPROC(_turn_mmu_on)

        到这里,可以继续跟踪__turn_mmu_on是在哪里调用的。这很容易找到,系统通过b汇编指令跳转到__turn_mmu_on。代码如下:

__enable_mmu:
//省略部分代码
mov r5,#(domain val(DOMAIN USER, DOMAIN MANAGER)\
    domain val(DOMAIN KERNEL, DOMAIN MANAGER)I\ 
    domain val(DOMAIN TABLE, DOMAIN MANAGER)IY 
    domain val(DOMAIN IO,DOMAIN CLIENT)) 
// load domain access register
mcr p5,0, r5, c3,c0,0
// load page table pointer
mcr p15, 0, r4, c2, c0, 0
//系统在这里跳转到_turn_mmu_on 
b __turn_mmu_on //第441行
ENDPROC(__enable_mmu)

        到这里,可以看出start_kernel的调用流程:系统在开启MMU的时候 【 MMU(Memory Management Unit )是内存管理单元的简称】,在函数__enable_mmu中调用__turn_mmu_on ,然后__turn_mmu_on通过“mov r3, r13”和“mov pc, r3”两条指令调用__mmap_switched 。而函数__mmap_switched是head-common.S中的函数,在该函数中调用start _kernel。

        start_kernel函数是Linux内核通用的启动函数,也是汇编代码执行完毕后的第一个C语言函数,它的实现代码位于init/main.c中。start_kernel的代码比较复杂,其中做了很多软硬件初始化的工作,比如调用了setup_arch()、timer_init()、init_IRQ、console_init()。

2.2 内核启动阶段

        内核初始化阶段结束时通过调用start_kernel函数进入内核启动阶段。在内核启动阶段,首先进行一些应用级的初始化工作,最后调用rest_init函数启动init进程。init进程是 Android用户空间的1号进程,担负Android运行环境启动的重要职责。本节首先分析内核启动阶段都做了哪些工作。这部分代码主要位于kerel/init/main.c,如下所示:

asmlinkage void __init start_kernel(void)
{
    ......//省略部分内容
    local irq_disable();                    //关闭当前CPU中断 
    early_boot_irgs_disabled=true;
    page_address_init();                    //页地址初始化 
    printk(KERN_NOTICE "s",linux_banner);   //输出内核版本信息 
    setup_arch(&command line);              //体系结构相关的初始化 
    //command_1ine由bootloader传入 
    ......//省略部分内容
    sched_init();                          //初始化进调度器 
    preempt_disable();                     //禁止内核抢占 
    early_irg_init(                        //初始化中断处理函数 
    init_IRQ();
    init_timers();                         //初始化定时器 
    hrtimers_init();                       //高精度时钟的初始化 
    softirq_init():                        //初始化软中断 
    time_init();                           //初始化系统时间 
    profile_init();                        //初始化内核性能调试工具profile 
    early_boot_irgs_disabled=false;
    local_irg_enable():                    //打开IRO中断 
    console_init():                        //初始化控制台显示printk打印的内容 
    //在此之前调用的printk只是缓存信息
    fork_init(totalram pages);             //计算允许创建进程的数量 
    ......//省略部分内容
    signals_init();                        //初始化信号量 
    ftrace_init();
    rest_init();                           //创建init进程
}

start_kernel的源代码可以分成以下两部分:

(1)C部分的初始化过程。

(2)调用rest_init创建init进程。

        如果对C部分的初始化感兴趣,可以参考专门介绍Linux内核的书。本文将重点放在init进程创建过程。在start_kernel函数的最后调用了rest_init函数进行后续的初始化,定位到rest_init方法的实现部分kerel/init/main.c,代码如下:

static noinline void __init_refok rest_init(void)
{
    int pid;
    rcu_scheduler_starting();
    /*通过kernel_thread启动内核线程,执行kernel init*函数,
    此函数首先创建了init进程,以使其成为1号进程*/
    kernel_thread(kernel_init, NULL,CLONE_FS | CLONE_SIGHAND); 
    numa_default_policy();
    /*启动内核线程kthreadd,始终运行在内核空间, 负责所有内核线程的调度和管理*/
    pid = kernel_thread(kthreadd, NULL,CLONE_FS | CLONE_FILES);
    ....//省略部分代码
    /*调用schedule,释放当前线程占用的CPU,以激活其他线程*/ 
    schedule();
    preempt_disable();
    /*调用cpu_idle,使当前线程变成idle线程(空闲进程),减少系统资源占用*/ 
    cpu_idle();
}

        rest_init中的主要任务是通过kernel_thread启动了两个内核线程。kernel_thread的实现代码位于kernel/common/arch/arm/kernel/process.c,函数原型如下:

pid_t kernel_thread(int (*fn)(void *),void *arg,unsigned 1ong flags)

        kernel_thread用来启动一个内核线程,第一个参数fn是要执行的函数的指针;第二个参数arg是传递给该函数的参数;第三个参数flags为do_fork创建线程时的标志。对于rest_init方法中第一个kernel_thread调用,实际上调用了kernel_init函数。进入kernal_init 函数体一看究竟(kerel/init/main.c),代码如下:

static int __init kernel_init(void * unused)
{
        //等待kthreadd进程完成所有设置
        wait_for_completion(&kthreadd_done);
        /*Now the scheduler is fully set up and can do blocking allocations */
        gfp_allowed_mask = __GFP_BITS_MASK;
        //init进程可以给任何节点分配页
        set_mems_allowed(node_states[N_HIGH_MEMORY]);
        //init进程可以运行在任何一个CPU上
        set_cpus_allowed_ptr(current, cpu_all_mask);
        /* 多CPU激活开始 */
        cad_pid = task_pid(current);
        smp_prepare_cpus(setup_max_cpus);
        do_pre_smp_initcalls();
        lockup_detector_init();
        //激活SMP系统中其他CPU
        smp_init();
        sched_init_smp();
        /*此时与体系结构相关的部分已经完成了初始化,接下来do_basic_setup函数初始化设备,完成外设及其驱动程序的加载和初始化*/
        do_basic_setup();

        /* Open the /dev/console on the rootfs, this should never fail */
        if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
                printk(KERN_WARNING "Warning: unable to open an initial console.\n");
        (void) sys_dup(0);
        (void) sys_dup(0);
        
         
        //定义init进程 
        if (!ramdisk_execute_command)
                ramdisk_execute_command = "/init";
        if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
                ramdisk_execute_command = NULL;
                prepare_namespace();
        }

        /*最后调用init_post,启动进程负责用户空间的初始化*/
        init_post();
        return 0;
}

        kernel_init方法中,前面部分做了一些初始化工作,然后定义了init进程的位置,接着调用了init_post函数。接下来定位到init_post函数体(kerel/init/main.c),代码如下:

static noinline int init_post(void)
{
        /* 释放内存之前完成所有异步 __init 代码 */
        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执行后将不再返回
                run_init_process(ramdisk_execute_command);
                printk(KERN_WARNING "Failed to execute %s\n",
                                ramdisk_execute_command);
        }

        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.");
}

        execute_command是bootloader传递给内核的参数,一般是/init(即根目录下的init程序),也即是调用文件系统里的init进程。如果找不到就会继续寻找“/sbin/init”、“/etc/initt”、“/bin/init”、“bin/sh”,找到后便执行run_init_process,且不再返回。run_init_process的函数体非常简单。仅仅是对kernel_execve函数的封装,代码如下:

static void run_init_process(const char *init_filename)
{
        argv_init[0] = init_filename;
        kernel_execve(init_filename, argv_init, envp_init);
}

        kernel_execute是Linux内核中创建用户进程的方法接口,实现位于arch/arm/kernel/sys_arm.c。至此,已经对Android kernel如何引导以及用户空间1号进程(init进程)如何启动做了详细分析。下一节详细分析Android init进程的执行过程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值