您的位置:首页 > 移动开发 > Android开发

android解析配置文件

2012-06-29 14:30 302 查看
init进程中,会解析很多配置文件,其中包括是系统的配置文件init.rc,如果是其他启动模式比如meta模式则是init.factory.rc,还有就是加载硬件平台相关的配置文件,如init.MT6575.rc。但现在的项目中没用用到这个配置文件。这配置文件中保存了系统启动需要执行的各种操作,决定启动那些服务,设置属性等等。
下面就主要针对init.rc来分析配置文件的解析过程。

代码路径: /system/core/init/init_parser.c

init进程进程中会调用下面这个函数来执行配置文件的解析操纵。

int init_parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;

parse_config(fn, data);
DUMP();
return 0;
}

会根据传入参数对应的路径找到配置文件,读取文件中的字符串后调用下面的函数:

static void parse_config(const char *fn, char *s)
{
struct parse_state state;
char *args[INIT_PARSER_MAXARGS];
int nargs;

nargs = 0;
state.filename = fn;
state.line = 0;
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;
case T_NEWLINE:
state.line++;
if (nargs) {
int kw = lookup_keyword(args[0]);
if (kw_is(kw, SECTION)) {
state.parse_line(&state, 0, 0);
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;
}
}
}

可以说这个函数定义了整个配置文件解析的流程,其中的for循环完成所有的解析工作,特别定义了一个结构体 state保存字符串指针,用来操作每个字符。下面就来具体分析。

在循环体中,把state结构体传入下边的函数,然后对解析结果进行判断。

代码路径: /system/core/init/parser.c

int next_token(struct parse_state *state)
{
... ... //略去部分代码

for (;;) {
switch (*x) {
case 0:
state->ptr = x;
return T_EOF;
case '\n':
x++;
state->ptr = x;
return T_NEWLINE;
case ' ':
case '\t':
case '\r':
x++;
continue;
case '#':
while (*x && (*x != '\n')) x++;
if (*x == '\n') {
state->ptr = x+1;
return T_NEWLINE;
} else {
state->ptr = x;
return T_EOF;
}
default:
goto text;
}
}
... ... //略去部分代码
}

这个函数根据传进来的字符字符串指针state->ptr,找到每个字符,判断字符是否为换行符或者是关键字符,比如“#”。当检测的"#"这个字符,以后的所有的在换行符之前的字符都将被忽略,也就规定配置文件中用“#”来表示注释。非关键字符将当做文本去处理,并且将文本指针保存在state->ptr里,并返回。

可以看出解析过程是以行为处理单元的,每次解析到换行符都会返回T_NEWLINE结果。next_token()操作可以看作是具体解析前的预处理,找出有效的字符串,然后将它们按照命令+参数这样的顺序依次存入 args[]中,为以后使用。 如果解析新的一行,首先会对第一个字符串(也就是args[0])进行解析,会调用下面的函数:

代码路径: /system/core/init/parser.c

int lookup_keyword(const char *s)
{
switch (*s++) {
case 'c':
... ... //略去部分代码

if (!strcmp(s, "hown")) return K_chown;
if (!strcmp(s, "hmod")) return K_chmod;

... ... //略去部分代码
return K_UNKNOWN;
}

这个函数很简单,就是找到关键字对应的命令和配置选项,如常用到chown chmod命令,还包括后面要介绍的section类型的关键字,如service。值得一提的是这里定义关键字与对应实际操作的方式很有技巧,感兴趣的可以去仔细研究一下。

如果新的一行第一个关键字的类型是section,则会执行parse_new_section(),在这个函数中会根据section的类型设定不同操作函数。

代码路径: /system/core/init/init_parser.c

void parse_new_section(struct parse_state *state, int kw,
int nargs, char **args)
{
... ... //略去部分代码
switch(kw) {
case K_service:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
case K_on:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
case K_import:
if (nargs != 2) {
ERROR("single argument needed for import\n");
} else {
int ret = init_parse_config_file(args[1]);
if (ret)
ERROR("could not import file %s\n", args[1]);
}
}
state->parse_line = parse_line_no_op;
}

可以看到,根据不同section类型,分别解析了sevices和action操作,这一部分后面会详细说明,紧接着state->parse_line被赋给了不同的函数指针。这样设置后,在parse_config()中for循环中,就可以将 args[]作为参数传入这写parse_line()进行处理了。

其实从字面意思就可以去理解,android将配置文件的内容划分成一个个section,一个section包含一系列需要执行的命令、操作和参数。一个section包含很多行内容,而且我们知道配置文件第一行第一个关键字必然是section类型的,不然在下一个section关键字之前所有内容都无效。在检测到新的section之后,就会初始化对应的数据与操作,从而方便后续具体内容会在特定的parse_line()函数中去解析。

目前一共有三种section类型:

service 用于启动sevices、demo和特定的程序
action 用于某些具体执行操作,如设置属性,执行某个命令。同时表示某一执行阶段,如early init。
import 加载并解析其他配置文件,直接调用init_parse_config_file(),进行递归操作,并将该关键字后的第一个有效字符串作为参数,这里就不具体分析了。

在parse_new_section()中对于service与action的section类型,有不同的处理,下面就具体分析。

service

首先是对state->context赋值,调用了parse_service()。

代码路径: /system/core/init/init_parser.c

static void *parse_service(struct parse_state *state, int nargs, char **args)
{
... ... //略去部分代码

nargs -= 2;
svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
if (!svc) {
parse_error(state, "out of memory\n");
return 0;
}
svc->name = args[1];
svc->classname = "default";
memcpy(svc->args, args + 2, sizeof(char*) * nargs);
svc->args[nargs] = 0;
svc->nargs = nargs;
svc->onrestart.name = "onrestart";
list_init(&svc->onrestart.commands);
list_add_tail(&service_list, &svc->slist);
return svc;
}

这个函数会对需要启动的service名字进行检查后,然后检查是否第一次启动,不允许同一个service启动两次。 然后分配了内存,初始化一些数变量与对应的参数后,初始化了svc自己的commands链表,也把自己加入了service_list这个链表中。svc的指针最后返回,并赋给了state->context,这样就方便的在每次循环解析过程中下传递service的信息。

接下来分析在service section关键字下的其他内容的解析处理过程。因为解析过程是以行为单元的,所以非section关键字的参数都要在新的一行中才会被正常解析。

当新的一行解析完成, 在parse_config()循环中会判断首个字段是否是section类型,不是则会调用state.parse_line()。对于sevice section的情况,实际调用parse_line_service()。

代码路径: /system/core/init/init_parser.c

static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
... ... //略去部分代码

kw = lookup_keyword(args[0]);
switch (kw) {
case K_capability:
break;
case K_class:
if (nargs != 2) {
parse_error(state, "class option requires a classname\n");
} else {
svc->classname = args[1];
}
break;
case K_console:
svc->flags |= SVC_CONSOLE;
break;
case K_disabled:
svc->flags |= SVC_DISABLED;
svc->flags |= SVC_RC_DISABLED;
break;
... ... //略去部分代码

case K_oneshot:
svc->flags |= SVC_ONESHOT;
break;
case K_onrestart:
nargs--;
args++;
kw = lookup_keyword(args[0]);
if (!kw_is(kw, COMMAND)) {
parse_error(state, "invalid command '%s'\n", args[0]);
break;
}
kw_nargs = kw_nargs(kw);
if (nargs < kw_nargs) {
parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,
kw_nargs > 2 ? "arguments" : "argument");
break;
}

cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
cmd->func = kw_func(kw);
cmd->nargs = nargs;
memcpy(cmd->args, args, sizeof(char*) * nargs);
list_add_tail(&svc->onrestart.commands, &cmd->clist);
break;
... ... //略去部分代码

case K_socket: {/* name type perm [ uid gid ] */
struct socketinfo *si;
if (nargs < 4) {
parse_error(state, "socket option requires name, type, perm arguments\n");
break;
}
if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")
&& strcmp(args[2],"seqpacket")) {
parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");
break;
}
si = calloc(1, sizeof(*si));
if (!si) {
parse_error(state, "out of memory\n");
break;
}
si->name = args[1];
si->type = args[2];
si->perm = strtoul(args[3], 0, 8);
if (nargs > 4)
si->uid = decode_uid(args[4]);
if (nargs > 5)
si->gid = decode_uid(args[5]);
si->next = svc->sockets;
svc->sockets = si;
break;
}
... ... //略去部分代码
}

这个函数实现的工作也很简单,主要就是判断参数类型,然后执行相关操作与设定。这里还会判断不同关键字的参数是否正确。经常遇到的参数是oneshot、disabled等等控制service启动的参数,这些参数只是简单设置相关标志位而已。

需要关注的onrestart这个关键字,它一般用作执行相关命令的动作,这些命令在service启动的时候会同时进行,它们都被添加到了svc自己的commands链表里以方便执行。

还有socket关键字,Android中service之间通讯一般都用到socket机制,socket的建立规则也是在配置文件中定义的。可以看到一个sevice有多个socket时,它们是如何关联在一起的。

action

对于action section来说相对简单一些。

代码路径: /system/core/init/init_parser.c

static void *parse_action(struct parse_state *state, int nargs, char **args)
{
... ... //略去部分代码
act = calloc(1, sizeof(*act));
act->name = args[1];
list_init(&act->commands);
list_add_tail(&action_list, &act->alist);
/* XXX add to hash */
return act;
}

简单的分配了内存,初始化了act->commands链表,把act加入到action_list中去。注意action section关键字只有一个参数。在.rc文件中on就是action section关键字 ,并且一般用来标识某一section,设置property值。 与service的情况类似,action section关键字下每个行都会独立解析。每次解析到新的一行时,都会调用state->parse_line,即parse_line_action()。

代码路径: /system/core/init/init_parser.c

static void parse_line_action(struct parse_state* state, int nargs, char **args)
{
... ... //略去部分代码
kw = lookup_keyword(args[0]);
if (!kw_is(kw, COMMAND)) {
parse_error(state, "invalid command '%s'\n", args[0]);
return;
}

n = kw_nargs(kw);
if (nargs < n) {
parse_error(state, "%s requires %d %s\n", args[0], n - 1,
n > 2 ? "arguments" : "argument");
return;
}
cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
cmd->func = kw_func(kw);
cmd->nargs = nargs;
memcpy(cmd->args, args, sizeof(char*) * nargs);
list_add_tail(&act->commands, &cmd->clist);
}

可以看到action关键字下需要执行工作很简单很简单,就是将命令与命令的参数加入到act->commands链表中去。

到此为止解析配置文件的工作基本已经解析完成了,而具体的执行操作在Init进程中会单独执行。

最后摘录一段init.rc内容具体分析一下

import init.usb.rc #import关键字 属于section类型 负责查找并解析init.usb.rc

on early-init #表示early-init section 在init进程中分段执行各个操作
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16

start ueventd

# create mountpoints 执行具体命令
mkdir /mnt 0775 root system
mkdir /mnt/sdcard 0000 system system
mkdir /mnt/sdcard2 0000 system system
mkdir /mnt/cd-rom 0000 system system

on early_property:ro.build.type=user #在early_property阶段 设置ro.build.type属性值为user
write /proc/bootprof "INIT: user build setting"

on early_property:ro.build.type=eng
write /proc/bootprof "INIT: eng build setting"

on init #标识init阶段

sysclktz 0 #执行某一命令

loglevel 3
write /proc/bootprof "INIT: on init start"

…… …………省略部分内容

service servicemanager /system/bin/servicemanager #sevice section开始 定义启动的service名字 执行程序路径
class core  #定义service的属性
user system #定义service的属性
group system #定义service的属性
critical #定义service的调度优先级
onrestart restart zygote #设置重新启动时的一同启动的程序
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm

service vold /system/bin/vold
class core
socket vold stream 0660 root mount @ #设置sevices socket接口
ioprio be 2 #定义service的io通讯优先级

…… …………省略部分内容

附录-配置文件关键字列表

关键字类型参数个数对应函数说明
capabilityOPTION0null
chdirCOMMAND1do_chdir
chrootCOMMAND1do_chroot
classOPTION0null
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: