💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
根据上面的代码可知,在init中会解析两个配置文件,其中一个是系统配置文件init.rc,另外一个是和硬件平台相关的配置文件。以HTC G7手机为例,这个配置文件名为init.bravo.rc,其中bravo是硬件平台的名称。对这两个配置文件进行解析,调用的是同一个parse_config_file函数。下面就来看这个函数,在分析过程中以init.rc为主。 **parser.c** ~~~ int parse_config_file(const char *fn) { char *data; data = read_file(fn, 0);//读取配置文件的内容,这个文件是init.rc。 if (!data) return -1; parse_config(fn,data); //调用parse_config做真正的解析 return 0; } ~~~ 读取完文件的内容后,将调用parse_config进行解析,这个函数的代码如下所示: **parser.c** ~~~ static void parse_config(const char *fn, char*s) { struct parse_state state; char *args[SVC_MAXARGS]; int nargs; nargs = 0; state.filename = fn; state.line = 1; state.ptr = s; state.nexttoken = 0; state.parse_line = parse_line_no_op; //设置解析函数,不同的内容用不同的解析函数 for (;;) { switch(next_token(&state)) { case T_EOF: state.parse_line(&state, 0, 0); return; caseT_NEWLINE: if (nargs) { //得到关键字的类型 int kw = lookup_keyword(args[0]); if (kw_is(kw, SECTION)) { //判断关键字类型是不是SECTION。 state.parse_line(&state,0, 0); parse_new_section(&state,kw, nargs, args);//解析这个SECTION。 } else { state.parse_line(&state, nargs, args); } nargs = 0; } break; case T_TEXT: ...... break; } } } ~~~ 上面就是parse_config函数,代码虽短,实际却比较复杂。从整体来说,parse_config首先会找到配置文件的一个section,然后针对不同的 section使用不同的解析函数来解析。那么,什么是section呢?这和init.rc文件的组织结构有关。先不必急着去看init.rc,还是先到代码中去寻找答案。 1. 关键字定义 keywords.h这个文件定义了init中使用的关键字,它的用法很有意思,先来看这个文件,代码如下所示: **keywords.h** ~~~ #ifndef KEYWORD //如果没有定义KEYWORD宏,则走下面的分支 ......//声明一些函数,这些函数就是前面所说Action的执行函数。 int do_class_start(int nargs, char **args); int do_class_stop(int nargs, char **args); ...... int do_restart(int nargs, char **args); ...... #define __MAKE_KEYWORD_ENUM__ //定义一个宏 /* 定义KEYWORD宏,虽然有四个参数,不过这里只用第一个,其中K_##symbol中的##表示连接 的意思,即最后得到的值为K_symbol。symbol其实就是init.rc中的关键字 */ #define KEYWORD(symbol, flags, nargs, func)K_##symbol, enum { //定义一个枚举,这个枚举定义了各个关键字的枚举值。 K_UNKNOWN, #endif ...... //根据上面KEYWORD的定义,这里将得到一个枚举值K_class, KEYWORD(class, OPTION, 0, 0) KEYWORD(class_start, COMMAND, 1, do_class_start)//K_class_start, KEYWORD(class_stop, COMMAND, 1, do_class_stop)//K_class_stop, KEYWORD(on, SECTION, 0, 0)//K_on, KEYWORD(oneshot, OPTION, 0, 0) KEYWORD(onrestart, OPTION, 0, 0) KEYWORD(restart, COMMAND, 1,do_restart) KEYWORD(service, SECTION, 0,0) ...... KEYWORD(socket, OPTION, 0, 0) KEYWORD(start, COMMAND, 1,do_start) KEYWORD(stop, COMMAND, 1,do_stop) ...... #ifdef __MAKE_KEYWORD_ENUM__ KEYWORD_COUNT, }; #undef __MAKE_KEYWORD_ENUM__ #undef KEYWORD //取消KEYWORD宏定义 #endif ~~~ keywords.h好像没什么奇特,不过是个简单的头文件。为什么说它的用法很有意思呢?来看代码中是如何使用它的,如下所示: **parser.c** ~~~ ......//parser.c中将包含keywords.h头文件,而且还不只一次!! //第一次包含keywords.h,根据keywords.h的代码,我们首先会得到一个枚举定义 #include "keywords.h" /* 重新定义KEYWORD宏,这回四个参数全用上了,看起来好像是一个结构体。其中#symbol表示 一个字符串,其值为“symbol”。 */ #define KEYWORD(symbol, flags, nargs, func) \ [K_##symbol ] = { #symbol, func, nargs + 1, flags, }, //定义一个结构体keyword_info数组,它用来描述关键字的一些属性,请注意里面的注释内容。 struct { constchar *name; //关键字的名。 int(*func)(int nargs, char **args);//对应关键字的处理函数。 unsignedchar nargs;//参数个数,每个关键字的参数个数是固定的。 //关键字的属性,有三种属性,COMMAND、OPTION和SECTION。其中COMMAND有对应的处理函数 unsigned char flags; } keyword_info[KEYWORD_COUNT] = { [ K_UNKNOWN ] = { "unknown", 0, 0, 0}, /* 第二次包含keywords.h,由于已经重新定了KEYWORD宏,所以以前那些作为枚举值的关键字 现在变成keyword_info数组的索引了。 */ #include "keywords.h" }; #undef KEYWORD //一些辅助宏,帮助我们快速操作keyword_info中的内容。 #define kw_is(kw, type) (keyword_info[kw].flags& (type)) #define kw_name(kw) (keyword_info[kw].name) #define kw_func(kw) (keyword_info[kw].func) #define kw_nargs(kw) (keyword_info[kw].nargs) ~~~ 现在领略了keywords.h的神奇之处了吧?原来它干了两件事情: - 第一次包含keyworks.h时,它声明了一些诸如do_classstart这样的函数,另外还定义了一个枚举,枚举值为K_class,K_mkdir等关键字。 - 第二次包含keywords.h后,得到了一个keyword_info结构体数组,这个keyword_info结构体数组以前面定义的枚举值为索引,存储对应的关键字信息,这些信息包括关键字名、处理函数、处理函数的参数个数,以及属性。 目前,关键字信息中最重要的就是symbol和flags了。什么样的关键字被认为是section呢?根据keywords.h的定义,symbol为下面两个的关键字表示section: ~~~ KEYWORD(on, SECTION, 0, 0) KEYWORD(service, SECTION, 0, 0) ~~~ 有了上面的知识,再来看配置文件init.rc的内容。 2. init.rc的解析 init.rc的内容如下所示:(我们截取了部分内容,注意,其中的注释符号是#。) **init.rc** ~~~ on init #根据上面的分析,on关键字标示一个section,对应的名字是”init” ...... #下面所有的内容都属于这个section,直到下一个section开始时。 exportPATH /sbin:/system/sbin:/system/bin:/system/xbin exportLD_LIBRARY_PATH /system/lib exportANDROID_BOOTLOGO 1 #根据keywords.h的定义,export表示一个COMMAND export ANDROID_ROOT /system exportANDROID_ASSETS /system/app ...... #省略部分内容 on boot #这是一个新的section,名为”boot” ifup lo#这是一个COMMAND hostname localhost domainname localdomain ...... #class_start也是一个COMMAND,对应函数为do_class_start,很重要,切记。 class_startdefault ...... #下面这个section的意思是:待属性persist.service.adb.enable的值变为1后, #需要执行对应的COMMAND,这个COMMAND是start adbd onproperty:persist.service.adb.enable=1 start adbd //start是一个COMMAND on property:persist.service.adb.enable=0 stopadbd ...... #service也是section的标示,对应section的名为“zygote“ service zygote /system/bin/app_process -Xzygote/system/bin –zygote \ --start-system-server socketzygote stream 666 #socket关键字表示OPTION onrestart write /sys/android_power/request_state wake #onrestart也是OPTION onrestart write /sys/power/state on onrestart restart media #一个section,名为”media” service media /system/bin/mediaserver usermedia groupsystem audio camera graphics inet net_bt net_bt_admin net_raw iopriort 4 ~~~ 从上面对init.rc的分析中可知: - 一个section的内容从这个标示section的关键字开始,到下一个标示section的地方结束。 - init.rc中出现了名为boot和init的section,这里的boot和init,就是前面介绍的动作执行四个阶段中的boot和init。也就是说,在boot阶段执行的动作都是由boot这个section定义的。 另外还可发现,zygote被放在了一个servicesection中。下面以zygote这个section为例,介绍service是如何解析的。