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