3.4.3 解析配置文件
我们已经熟悉了Android初始化语言和init.rc的内容。那init程序又是如何解析init.rc的呢?
解析init.rc的函数是init_parse_config_file("/init.rc"),位于/system/core/init/init_parser.c。代码如下:
int init_parse_config_file(const char*fn)
{
char*data;
/*调用open、read, malloc读取init.rc到buffer;
*并配合struct stat记录文件状态,主要记录的是文件大小,存入read_file的
第二个参数保存,这里传入0,即不带回文件大小。stat结构体也是Linux定义的/
data=read_file(fn,0);
if(!data)return-1;
parse_config(fn, data);//开始解析init.rc
/*DUMP()位于/system/core/init/parser.c中,用于输出service_list和
action_list中存储的Service和Action。调试的时候作用很大,默认是关闭的/
DUMP();
return 0;
}
从init_parse_config_file的函数体可以看出,它主要做了读取文件、解析文件、调试文件这三部分工作。读取文件和调试文件比较简单,都是基本的C函数。这里重点分析解析文件的部分,即parse_config(fn, data),该函数定义于init_parser.c中,代码如下:
static void parse_config(const charfn, chars)
{
struct parse_state state;//保存当前解析状态的结构体
char*args[INIT_PARSER_MAXARGS];//每行支持的最大参数个数为64
int nargs;//当前参数个数
nargs=0;
state.filename=fn;//init.rc文件路径
state.line=0;//当前解析的行号
/*ptr指向当前将要解析的内容,初始时传入的是fn的文件内容,即init_parse_config_file中
的data指针。解析过程中,不断移动到将要解析的数据/
state.ptr=s;
/*当前解析的是什么类型的行,分为
文件结束(T_EOF)、新的一行(T_NEWLINE)、参数(T_TEXT)/
state.nexttoken=0;
/*针对不同类型的行,采用不同的解析方法,初始化为
parse_line_no_op,这是个空函数体,不做任何操作/
state.parse_line=parse_line_no_op;
/在无限循环中解析init.rc,直到遇到文件结束符EOF,退出循环/
for(;){
/next_token函数以state为参数把每行的关键字存入args数组/
switch(next_token(&state)){
case T_EOF://文件结束
state.parse_line(&state,0,0);//传入0,表示末尾
goto parser_done;
case T_NEWLINE://新的一行
state.line++;
if(nargs){
/根据第一个关键字匹配Section/
int kw=lookup_keyword(args[0]);
/如果该行是Section,即第一个关键字是on或者service/
if(kw_is(kw, SECTION)){
/链表中最后一个元素写入0,即NULL,结束上一个Section/
state.parse_line(&state,0,0);
/开始新的Section解析过程/
parse_new_section(&state, kw, nargs, args);
}else{
state.parse_line(&state, nargs, args);
}
nargs=0;
}
break;
case T_TEXT:
if(nargs<INIT_PARSER_MAXARGS){
args[nargs++]=state.text;
}
break;
}
}
parser_done:
……
}
parse_config函数中主要做了两部分工作。首先提供了一个parse_state结构体存储当前解析状态,然后提供一个无限循环开始解析初始化文件。
解析过程是按行解析,并根据关键字匹配,如果遇到Section,便调用parse_new_section函数,进入Section的解析过程。parse_state定义在parser.h中,lookup_keyword和parse_new_section定义在init_parser.c中,其中lookup_keyword就是一个简单的switch语句,用来根据传入的参数匹配不同的关键字。
下面继续分析parse_new_section的内容,代码如下:
void parse_new_section(struct parse_statestate, int kw, int nargs, char*args)
{
/匹配Section类型,分成Service和Action,分别对应不同的解析函数/
switch(kw){
case K_service://如果是关键字service,开始解析Service
/返回初始化的Service,由state->context引用/
state->context=parse_service(state, nargs, args);
if(state->context){
/*解析并填充Service,用真正的Service解析函数parse_line_service
代替parse_line_no_op/
state->parse_line=parse_line_service;
return;
}
break;
case K_on://如果是关键字on,即action,开始解析Action
/返回初始化的Action,由state->context引用/
state->context=parse_action(state, nargs, args);
if(state->context){
/*解析并填充Action,用真正的Action解析函数parse_line_action
代替parse_line_no_op/
state->parse_line=parse_line_action;
return;
}
break;
case K_import://如果是import语句,则重复解析初始化文件的过程
parse_import(state, nargs, args);
break;
}
/出错,未找到匹配项,复位解析函数为空函数体/
state->parse_line=parse_line_no_op;
}
分析到这里,init.rc的解析过程基本明朗了。从parse_new_section函数可以看出,init.rc实际上是分成Action和Service两部分分别解析的。
解析Service调用了parse_service和parse_line_service。
解析Action调用了parse_action和parse_line_action。
接下来具体分析这4个解析函数是如何实现的。