Android 启动流程代码分析

时间:2024-03-24 13:06:12

前言

做过接近两年的android,特整理文档。第一篇,先了解android系统的启动流程。主要讲的是从init进程开始。主要讲的是基于Android M的开机启动流程介绍,当然也会分析下Android N版本的启动流程。

Android 系统的平台架构

Android 启动流程代码分析

Android 系统的底层是建立在Linux系统之上,该平台是由应用层(System apps),应用框架层(framework),系统运行库层(C/C++程序库和Android运行时库ART),硬件抽象层(HAL),Linux 内核层构成。

做应用开发的同学,手上肯定有一本书叫做 《android疯狂讲义》,在这本书的一开始就贴出了这张android系统的体系架构图。每一歌部分的功能不再做介绍,有兴趣的同学可以自行查询。

而android的启动流程大体分为三个阶段,1 bootloader引导 2 启动Kernel 3 启动Android 细分如下图所示

Android 启动流程代码分析

第一步:启动电源以及系统启动

    当电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后执行。

第二步:系统引导

    相关代码在(bootable\bootloader)

    加电后,CPU先执行bootloader程序,正常启动系统,加载boot.img,boot.img中包含内核。

第三步 :内核kernel

    由bootloader加载kernel,kernel经自解压、初始化、载入built-in 驱动程序完成启动。Kernel启动后会创建若干内核线程(kernel thread),之后装入并执行程序/sbin/init/,载入init process,切换至user-space。
     内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。

第四步 :Init 进程启动

Android 从linux 系统启动有4个步骤: 1.Init  进程启动 2. Zygote服务启动 3. System server.android服务启动 4. HOME启动

Init进程,是第一个由内核启动的用户级进程。用户自行启动(已经被载入内存,开始运行,并已初始化所有设备驱动程序和数据结构等)之后,就通过启动一个用户级程序init的方式,完成引导进程。Init始终是第一个进程。我们可以说它是root进程或者说有进程的父进程。init进程有两个责任,一是挂载目录,比如/sys、/dev、/proc,二是运行init.rc脚本。
Init 进程启动后,根据Init.rc和init.xxx.rc的脚本文件建立基本服务。

init 进程是在kernel/init/main.c文件中启动的,启动过程如下start_kernel()->rest_init()->kernel_init()->run_init_process(“/init”)

run_init_process函数如下,通过传入文件名字,调用do_execve函数,来启动新的程序的,

 

static int run_init_process(const char *init_filename)
{
    argv_init[0] = init_filename;
    return do_execve(init_filename,
        (const char __user *const __user *)argv_init,
        (const char __user *const __user *)envp_init);
}

在 init.cpp main 函数中,首先做的事情的创建一些文件夹并且挂在设备,代码如下

       mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        mount("proc", "/proc", "proc", 0, NULL);
        mount("sysfs", "/sys", "sysfs", 0, NULL);

接下来是三个函数

    open_devnull_stdio();
    klog_init()
    klog_set_level(KLOG_NOTICE_LEVEL);
    open_devnull_stdio()是将标准输入,标准输出以及错误输出都重定向到了/dev/_null_的设备节点,而_null_ 是一个无底洞。
    klog_init()  查看源代码的话是 打开了一个/dev/__kmsg__的节点,设定init的输出设备为/dev/__kmsg__
    property_init() 主要是为属性分配一些存储空间.
 

下面到了我们最重要的init_parse_config_file("/init.rc")函数了。init解析。

这个函数是init.rc文件解析的开始函数,我们看它做了什么事。

int init_parse_config_file(const char* path) {
    read_file(path, &data)       // 将init.rc文件里面的数据读取到data里面
    parse_config(path, data);   // 对data数据进行解析
    dump_parser_state();
}

下面是parse_config 函数,代码较多

static void parse_config(const char *fn, const std::string& data)
{
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];

    int nargs = 0;

    parse_state state;
    state.filename = fn;
    state.line = 0;
    state.ptr = strdup(data.c_str());  // c_str():生成一个const char*指针,指向以空字符终止的数组。数组的数据是临时的,当有一个改变这些数据的成员函数被调用后,其中的数据就会失效。因此要么现用先转换,要么把它的数据复制到用户自己可以管理的内存中。
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;

    list_init(&import_list);
    state.priv = &import_list;/*  开始获取每一个token,然后分析这些token,每一个token就是有空格、字表符和回车符分隔的字符串 */

    for (;;) {
        switch (next_token(&state)) { //查看next_token函数源代码的话,next_token函数相当于词法分析器
        case T_EOF:                          // init.rc 文件分析完毕
            state.parse_line(&state, 0, 0);
            goto parser_done;
        case T_NEWLINE:                 // 分析每一行命令
            state.line++;
            if (nargs) {
                int kw = lookup_keyword(args[0]);   // 这个函数就有意思了,通过对每一行第一个单词进行匹配
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args); 
                } else {
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }

parser_done:
    list_for_each(node, &import_list) {
         struct import *import = node_to_item(node, struct import, list);
         int ret;

         ret = init_parse_config_file(import->filename);
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}

int init_parse_config_file(const char* path) {
    INFO("Parsing %s...\n", path);
    Timer t;
    std::string data;
    if (!read_file(path, &data)) {
        return -1;
    }

    data.push_back('\n'); // TODO: fix parse_config.
    parse_config(path, data);
    dump_parser_state();

    NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration());
    return 0;
}

以下是对与每一行语句进行分析,分类存储

static void parse_new_section(struct parse_state *state, int kw,
                       int nargs, char **args)
{
    switch(kw) {
    case K_service:
        state->context = parse_service(state, nargs, args);

       //如果标志是K-service 调用parse_service()中的 list_add_tail(&service_list, &svc->slist);将数据存储在service_list。

       //注意此时parse_service中的svc对象基本上是一个空壳,因为相关的options还没有解析
        if (state->context) {
            state->parse_line = parse_line_service;//解析service对应的options行,主要是填充parse_service()中创建的service对象。
            return;
        }
        break;
    case K_on:
        state->context = parse_action(state, nargs, args); //同理,如果标志位是K_on, 加到action_list上
        if (state->context) {
            state->parse_line = parse_line_action;
            return;
        }
        break;
    case K_import:
        parse_import(state, nargs, args);同理,如果标志位是K_import, 加到import_list上
        break;
    }
    state->parse_line = parse_line_no_op;
}

看下init.rc 文件就明白了

 

import /init.trace.rc

on early-init
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000

    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    start ueventd

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0

以上过程就是对init.rc文件存储的数据进行解析的。

action_list存放了所有探知的action

service_list存放了所有在init.rc文件中定义的 服务。

 

queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");

这个函数 维护action_queue,存放了 将要执行的 action。