Android启动流程分析(七) init.rc的解析

#############################################

本文为极度寒冰原创,转载请注明出处

#############################################

Init.rc的解析过程是笔者认为在android启动过程中,最复杂,最难理解的部分。

虽然它的内容很少,但是却包含了非常多的处理,接下来我们来慢慢的分析。

经过前面的分析,我们知道了read完init.rc的文件后,保存到了data的数组,传递到了parse_config的函数里。

我们来看一下parse_config函数的处理:

static void parse_config(const char *fn, char *s) //仅针对init.rc来说,fn指向的内容为init.rc这个文件,s指向的就是data的数组
{
    struct parse_state state;   // parse_state的结构体
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn; // 标明解析的是init.rc的文件
    state.line = 0;   // 将初始化的line置为0
    state.ptr = s;   // ptr指向s的第一个元素
    state.nexttoken = 0;  // 设置nexttoken为0
    state.parse_line = parse_line_no_op;  // 设置解析的方式为parse_line_no_op, 即为不需要处理。需要注意的为parse_line是一个函数指针。

    list_init(&import_list);   // 初始化前面的import的链表
    state.priv = &import_list; // 将priv指向了初始化厚的import的链表。

    for (;;) { // 开始解析文件
        switch (next_token(&state)) { // next_token的函数的原理是,针对state->ptr的指针进行解析,依次向后读取data数组中的内容,如果读取到"\n","0"的话,返回T_EOF和T_NEWLINE, 如果读取出来的是一个词的话,则将内容保存在args的数组中,内容依次向后
        case T_EOF: // 如果文件读取结束的时候,
            state.parse_line(&state, 0, 0); //如果文件是空的,那么执行的function是parse_line_no_op, 如果不是空的,则执行的是parse_line_action 或者service,而这两个函数中,如果nargs是0的话,都会返回掉。
            goto parser_done;  // go to parser done
        case T_NEWLINE:
            state.line++;   // 如果遇到"\n"的话,state.line会+1行
            if (nargs) { // 如果nargs有值的话,说明这一行需要解析了。
                int kw = lookup_keyword(args[0]); // 利用这一行的第一个关键字即args[0],获取到kw
                if (kw_is(kw, SECTION)) { // 如果这个kw是一个SECTION的话,则会返回true,如果不是的话,则会返回false.
                    state.parse_line(&state, 0, 0); \\ 清除掉现在的parse line,开启一个新的section
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args); // 如果不是一个section的话,则将nargs与args做为参数传递到parse_line对应的函数中去
                }
                nargs = 0; // 在执行完一行以后,由于有新的内容需要读取到args中,所以将nargs设置为0.
            }
            break;
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text; \\ 每取出来一个token,就会将其放入到args的数组中,且nargs会自动+1
            }
            break;
        }
    }

parser_done: \\在文件结束的时候,会去执行到parse_done
    list_for_each(node, &import_list) {  // 这里会去遍历所有的import_list的节点
         struct import *import = node_to_item(node, struct import, list); // 取出这些import的节点
         int ret;

         INFO("importing '%s'", import->filename);
         ret = init_parse_config_file(import->filename); // 继续对这些文件进行解析
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}

在看完上面的这一段分析之后,我们会对下面的两个函数产生浓厚的兴趣,分别是

parse_new_section,kw_is

以及一个函数指针的 state.parse_line.

那接下来,我们先去看看kw_is函数:

函数原型如下:

#define kw_is(kw, type) (keyword_info[kw].flags & (type))

而keyword_info是什么东西呢?

static struct {
    const char *name;
    int (*func)(int nargs, char **args);
    unsigned char nargs;
    unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {
    [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
#include "keywords.h"
};

我们看到这个其实是一个结构体,但是在初始化这个结构体的时候,除了将第一个值置为了k_unknown之外,剩下的值都是从keywords.h中读取的。

那我们接着看看keywords.h里面写的是什么

#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
    K_UNKNOWN,
#endif
    KEYWORD(capability,  OPTION,  0, 0)
    KEYWORD(chdir,       COMMAND, 1, do_chdir)
    KEYWORD(chroot,      COMMAND, 1, do_chroot)
    KEYWORD(class,       OPTION,  0, 0)
    KEYWORD(class_start, COMMAND, 1, do_class_start)
    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)
    KEYWORD(class_reset, COMMAND, 1, do_class_reset)
    KEYWORD(console,     OPTION,  0, 0)
    KEYWORD(critical,    OPTION,  0, 0)
    KEYWORD(disabled,    OPTION,  0, 0)
    KEYWORD(domainname,  COMMAND, 1, do_domainname)
    KEYWORD(enable,      COMMAND, 1, do_enable)
    KEYWORD(exec,        COMMAND, 1, do_exec)
    KEYWORD(export,      COMMAND, 2, do_export)
    KEYWORD(group,       OPTION,  0, 0)
    KEYWORD(hostname,    COMMAND, 1, do_hostname)
    KEYWORD(ifup,        COMMAND, 1, do_ifup)
    KEYWORD(insmod,      COMMAND, 1, do_insmod)
    KEYWORD(import,      SECTION, 1, 0)
    KEYWORD(keycodes,    OPTION,  0, 0)
    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)
    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)
    KEYWORD(mount,       COMMAND, 3, do_mount)
    KEYWORD(on,          SECTION, 0, 0)
    KEYWORD(oneshot,     OPTION,  0, 0)
    KEYWORD(onrestart,   OPTION,  0, 0)
    KEYWORD(powerctl,    COMMAND, 1, do_powerctl)
    KEYWORD(restart,     COMMAND, 1, do_restart)

看到这里,我们就可以看一下刚才感兴趣的SECTION是什么了,可以看到import,on,service是三个唯一的SECTION。

这也就跟前面的init.rc里面的语法对应上了,这三个是主要的关键字。

如果解析到一个新的文件是以这三个关键字开头的话,会在清除掉当前的function后,去执行函数

parse_new_section

那我们接下来就去看看parse_new_section的实现:

static void parse_new_section(struct parse_state *state, int kw,
                       int nargs, char **args)
{
    printf("[ %s %s ]\n", args[0],
           nargs > 1 ? args[1] : "");
    switch(kw) {
    case K_service: \\ 如果是以service开头的话
        state->context = parse_service(state, nargs, args);  \\ 进行parse service的初始化工作,稍后进行分析
        if (state->context) {
            state->parse_line = parse_line_service; // 将parse_line的函数指针置为parse_line_service,后面调用的时候就会调用到这个函数
            return;
        }
        break;
    case K_on: \\ 如果是以on开头的话
        state->context = parse_action(state, nargs, args);  //进行parse_action的初始化准备工作,后面进行分析
        if (state->context) {
            state->parse_line = parse_line_action; // 将parse_line的函数指针置为parse_line_action,后面调用的时候就会执行这个函数。
            return;
        }
        break;
    case K_import: // 如果是以import开头的话
        parse_import(state, nargs, args); // import这个文件
        break;
    }
    state->parse_line = parse_line_no_op; //如果不是这三个关键字的话,我们不会进行处理。
}

从上面可以看到,除了进行parse action,service的初始化工作以外,最重要的工作就是将函数的指针给进行了初始化。

这样一来,除非执行到下一个section的关键字,都调用该函数指针进行操作。

接下来的两篇,我们会去分析,如何解析action以及service两个关键的SECTION

时间: 2024-10-21 10:25:06

Android启动流程分析(七) init.rc的解析的相关文章

Android启动流程分析(二) init进程的启动

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 分析init进程,首先要分析init进程是如何启动的, init的源码位于(system/core/init),我们先来看看init进程的android.mk 下面是从system/core/init模块拿出来的一段code: LOCAL_MODULE:= init LOCAL_

Android启动流程分析(一)

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# Android的启动流程绝大部分人都知道,但是大多数人都是这样描述的: Android启动,首先是启动Bootloader,然后挂载kernel,挂载完kernel之后,会启动android的init进程,init进程会去孵化Zygote,Zygote是android的重要支柱之

Android启动流程分析(十) action的执行和service的启动

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 在前面的文章分析完init.rc的解析以后,我们知道现在action按照init.c的main函数中的秩序,维护了一条qlist的链表,listnode为action_qlist service也维护了一条链表,listnode为service_list. 那么,在android

Android启动流程分析(九) 解析init.rc的service

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 在分析完解析init.rc的action之后,剩下的一部分就是解析service了. 而解析service还是需要回到parse_config里面来.根据前面的知识,我们也可以很容易的知道在关键字为section的时候,会进入到parse_new_section. 这里会先执行p

Android启动流程分析(八) 解析init.rc的action

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 上一章讲述了android启动过程中,加载init.rc之后需要对其进行解析. 而解析又根据三个不同的SECTION来执行不同的初始化的文件,分别是parse_action,parse_service,parse_import. 那么,这一节,我们就从parse_action来讲

Android 启动流程分析

原文:https://www.jianshu.com/p/a5532ecc8377 作者曾经在高通的Android性能组工作,主要工作是优化Android Application的启动时间. APP基础理论 要想优化App启动时间, 第一步就是了解App启动进程的工作原理. 有几个基础理论: Android Application与其他移动平台有两个重大不同点: 每个Android App都在一个独立空间里,意味着其运行在一个单独的进程中,拥有自己的VM, 被系统分配一个唯一的user ID A

Android启动流程分析(十二) SystemServer

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# SystemServer也是系统的一个重要的守护进程,从SystemServer的进程中,我们看到了系统的各种关键的Service的启动. 另外,根据前面的zygote的分析,我们知道了systemServer在android的启动过程中是肯定要启动的. 因为在init.rc里面

Android启动流程分析(十一) zygote的启动

############################################# 本文为极度寒冰原创,转载请注明出处 ############################################# 前面的文章花了很大的篇幅去介绍了init进程如何去解析init.rc,如何去执行系统的一些服务. 那么,我们所说的zygote是怎么启动的呢?zygote又是具体负责了哪些工作呢? 本文我们来一探究竟. zygote在inir.rc中有如下的描述: service zygote

android 启动流程 相关2 init进程 属性服务

Init属性服务 系统属性服务 属性梳理 来源和读取时机 来源:内核参数 ro.kernel.*   代表有qemu内核参数才会设置(在虚拟机中) ro.boot.*     1.内核设备树相关的设备属性,从 /proc/cmdline 的androidboot.* 中来 2.内部变量export_kernel_boot_props()中有个默认值表,当内核所给出的属性如ro.boot.serialno没有值时,那么ro.serialno的默认值将是表中给出的 来源:属性文件 ctl.* 一般用