3.2.1 解析配置文件
最后更新于:2022-04-02 05:48:40
根据上面的代码可知,在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是如何解析的。
';