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

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

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

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

上一章讲述了android启动过程中,加载init.rc之后需要对其进行解析。

而解析又根据三个不同的SECTION来执行不同的初始化的文件,分别是parse_action,parse_service,parse_import.

那么,这一节,我们就从parse_action来讲一下是如何解析init.rc下面,on的关键字及其对应的action与command的。

按照惯例,先来看一下这个函数实现:

static void *parse_action(struct parse_state *state, int nargs, char **args)
{
    struct action *act;
    if (nargs < 2) {
        parse_error(state, "actions must have a trigger\n");
        return 0;
    }
    if (nargs > 2) {
        parse_error(state, "actions may not have extra parameters\n");
        return 0;
    }
    act = calloc(1, sizeof(*act)); //初始化结构体action
    act->name = args[1];  // 对action的name进行赋值
    list_init(&act->commands); // 初始化action的commands这条结构体的内部链表
    list_init(&act->qlist);  // 初始化qlist这条结构提内部的链表
    list_add_tail(&action_list, &act->alist); // 利用listnode alist,将当前的这个结构提加入到了以action_list为哨兵节点的链表中,
        /* XXX add to hash */
    return act;
}
这里初始化的内容全部都是链表的操作。
为了更好的理解,我们先来看看action的机构体:

struct action {
        /* node in list of all actions */
    struct listnode alist;  // 使用这个listnode将其加入action_list
        /* node in the queue of pending actions */
    struct listnode qlist;  
        /* node in list of actions for a trigger */
    struct listnode tlist;   

    unsigned hash;
    const char *name;

    struct listnode commands; 
    struct command *current;
};
好的,总结一下,在经过parse_action之后,当前的action会被加入到action_list为哨兵节点的链表中。

并且被初始化了commands以及qlist这两条结构体内部的链表。

在parse_new_section中,我们看到,在初始化完之后,会将当前state的parse_line置为parse_line_action

    case K_on:
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_action;
            return;
        }
        break;
在执行接下来的action的command的时候,就会去执行parse_line_action()的函数.

详情如下:

<span style="color:#333333;">                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else {
</span><span style="color:#ff9966;">                    state.parse_line(&state, nargs, args);(实质是parse_line_action(&state, nargs, args))
</span><span style="color:#333333;">                }
</span>
那接下来就要看parse_line_action的实现了:

static void parse_line_action(struct parse_state* state, int nargs, char **args)
{
    struct command *cmd;
    struct action *act = state->context;  // 通过state->context,得到了刚才正在解析的action
    int (*func)(int nargs, char **args);
    int kw, n;

    if (nargs == 0) {    // 判断是否为空,如果没有要执行的command的话,就会返回
        return;
    }

    kw = lookup_keyword(args[0]);   // 得到kw,原理与得到SECTION的一致
    if (!kw_is(kw, COMMAND)) {   // 如果这个命令不是一个command的话,则返回error。
        parse_error(state, "invalid command '%s'\n", args[0]);
        return;
    }     // 因为action下面,只执行command,而且这些command是已经定义在keywords里面的

    n = kw_nargs(kw);   // 从keywords里面得到这个command需要几个参数,是在初始化数组的第三项,nargs
    if (nargs < n) {  // 如果需要的参数没有满足的话,则会返回错误
        parse_error(state, "%s requires %d %s\n", args[0], n - 1,
            n > 2 ? "arguments" : "argument");
        return;
    }
    cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);   // 对action的结构体中的command结构体进行初始化
    cmd->func = kw_func(kw);   // 得到这个command需要执行的函数,并将其放在了func的这个指针里面
    cmd->line = state->line;   //  得到这个command是在文件中的那一行
    cmd->filename = state->filename;  // 是哪个文件中的commands
    cmd->nargs = nargs;  // 这个commands的参数有几个
    memcpy(cmd->args, args, sizeof(char*) * nargs); // 将这几个参数都copy到commands的数组里面
    list_add_tail(&act->commands, &cmd->clist); // 将当前要执行的commands,加入到action的结构体中,listnode为commands的链表中
}

那这边有个疑问了,action的结构体中,初始化了一个qlist,但是好像没有使用啊?

这个是为什么呢?

我们接着回到init.c的main函数中看一下:

    init_parse_config_file("/init.rc");
    ERROR("action for each trigger  <==== chao");
    action_for_each_trigger("early-init", action_add_queue_tail);
在解析完这个init.rc文件之后,会去执行action_for_each_trigger函数
void action_for_each_trigger(const char *trigger,
                             void (*func)(struct action *act))
{
    struct listnode *node;
    struct action *act;
    list_for_each(node, &action_list) { // 从刚才的action_list(所有的action)里面,进行遍历。
        act = node_to_item(node, struct action, alist); 
        if (!strcmp(act->name, trigger)) {  // 如果名字和“trigger”有相同的话,会去执行action_add_queue_tail函数

            func(act); // 实质执行的是action_add_queue_tail(act)函数
        }
    }
}

我们接着来看看这个函数中来做的是什么:

void action_add_queue_tail(struct action *act)
{
    if (list_empty(&act->qlist)) {
        list_add_tail(&action_queue, &act->qlist);
    }
}
因为每一个action都被初始化了qlist,所以,这里肯定不是空。

而会将这个action结构体,又加入到了action_queue的链表中。这下就奇怪了,为什么一个action要加入到两个链表中呢?

再回到init.c的main函数中来,

可以看到,在action_for_each_trigger完了之后呢,又进行了很多的操作

    action_for_each_trigger("early-init", action_add_queue_tail);

    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    queue_builtin_action(keychord_init_action, "keychord_init");
    queue_builtin_action(console_init_action, "console_init");

    ERROR("action for each trigger init  <==== chao");
    /* execute all the boot actions to get us started */
    action_for_each_trigger("init", action_add_queue_tail);
那queue_builtin_action又是做什么的呢?和我们前面的疑问有没有关系呢?

来看一下它的实现:

void queue_builtin_action(int (*func)(int nargs, char **args), char *name)  // 以第一个函数为例,这里的func为wait_for_coldboot_done_action, name为wait_for_coldboot_done
{
    struct action *act;
    struct command *cmd;

    act = calloc(1, sizeof(*act));  // 初始化action的结构体
    act->name = name;  // 给action的结构体赋名字
    list_init(&act->commands);  // 初始化command的链表
    list_init(&act->qlist);  // 初始化qlist的链表

    cmd = calloc(1, sizeof(*cmd));  // malloc command的结构体
    cmd->func = func;  // 将function赋值给cmd->func
    cmd->args[0] = name;  // 设置cmd的第一个参数为名字
    cmd->nargs = 1;  // 对这类的函数而言,args和nargs都是没有用的,仅仅是初始化,使其不为null
    list_add_tail(&act->commands, &cmd->clist);  //使用command的listnode为clist的节点,<span style="font-size: 13.63636302948px; line-height: 25.9943180084229px; font-family: Arial;">将当前要执行的commands,加入到action的结构体中,listnode为commands的链表中</span>

    list_add_tail(&action_list, &act->alist); // 将这个action加入到action_list的链表中
    action_add_queue_tail(act); // 将这个action加入到qlist的节点里面
}

这样一来,我们可以清晰的看到两条链表的差别。

对于action_list的这条链表来说,假如init.rc中的action分别为action1,action2,action3 ...的话,那我们action_list的这条链表为:

action_list -> action1 -> action2 -> action3 -> actoin4 ..... -> wait_for_coldboot_done -> mix_hwrng_into_linux_rng .....

而action_qlist所对应的这条链表来说,对应的是:

action_queue -> early-init -> wait_for_coldboot_done -> mix_hwrng_into_linux_rng -> ... -> init -> mix_hwrng_into_linux_rng -> property_service_init -> ....


即action_list是按照init.rc的解析顺序加上init.c中声明要添加的action进行排列的。

而action_queue是完全按照init.c的main函数中的声明执行的顺序进程排列的。

这些终于搞明白了action的解析后的数据结构,那么,另外一个大的难题就是service的数据结构了。

我们一起在下篇文章中来看一下。



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。