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个解析函数是如何实现的。