#############################################
本文为极度寒冰原创,转载请注明出处
#############################################
上一章讲述了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; }这里初始化的内容全部都是链表的操作。
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()的函数.
详情如下:
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);(实质是parse_line_action(&state, nargs, args)) } }那接下来就要看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的数据结构了。
我们一起在下篇文章中来看一下。