android解析配置文件
2012-06-29 14:30
302 查看
在init进程中,会解析很多配置文件,其中包括是系统的配置文件init.rc,如果是其他启动模式比如meta模式则是init.factory.rc,还有就是加载硬件平台相关的配置文件,如init.MT6575.rc。但现在的项目中没用用到这个配置文件。这配置文件中保存了系统启动需要执行的各种操作,决定启动那些服务,设置属性等等。
下面就主要针对init.rc来分析配置文件的解析过程。
在init进程进程中会调用下面这个函数来执行配置文件的解析操纵。
会根据传入参数对应的路径找到配置文件,读取文件中的字符串后调用下面的函数:
可以说这个函数定义了整个配置文件解析的流程,其中的for循环完成所有的解析工作,特别定义了一个结构体 state保存字符串指针,用来操作每个字符。下面就来具体分析。
在循环体中,把state结构体传入下边的函数,然后对解析结果进行判断。
这个函数根据传进来的字符字符串指针state->ptr,找到每个字符,判断字符是否为换行符或者是关键字符,比如“#”。当检测的"#"这个字符,以后的所有的在换行符之前的字符都将被忽略,也就规定配置文件中用“#”来表示注释。非关键字符将当做文本去处理,并且将文本指针保存在state->ptr里,并返回。
可以看出解析过程是以行为处理单元的,每次解析到换行符都会返回T_NEWLINE结果。next_token()操作可以看作是具体解析前的预处理,找出有效的字符串,然后将它们按照命令+参数这样的顺序依次存入 args[]中,为以后使用。 如果解析新的一行,首先会对第一个字符串(也就是args[0])进行解析,会调用下面的函数:
这个函数很简单,就是找到关键字对应的命令和配置选项,如常用到chown chmod命令,还包括后面要介绍的section类型的关键字,如service。值得一提的是这里定义关键字与对应实际操作的方式很有技巧,感兴趣的可以去仔细研究一下。
如果新的一行第一个关键字的类型是section,则会执行parse_new_section(),在这个函数中会根据section的类型设定不同操作函数。
可以看到,根据不同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()。
这个函数会对需要启动的service名字进行检查后,然后检查是否第一次启动,不允许同一个service启动两次。 然后分配了内存,初始化一些数变量与对应的参数后,初始化了svc自己的commands链表,也把自己加入了service_list这个链表中。svc的指针最后返回,并赋给了state->context,这样就方便的在每次循环解析过程中下传递service的信息。
接下来分析在service section关键字下的其他内容的解析处理过程。因为解析过程是以行为单元的,所以非section关键字的参数都要在新的一行中才会被正常解析。
当新的一行解析完成, 在parse_config()循环中会判断首个字段是否是section类型,不是则会调用state.parse_line()。对于sevice section的情况,实际调用parse_line_service()。
这个函数实现的工作也很简单,主要就是判断参数类型,然后执行相关操作与设定。这里还会判断不同关键字的参数是否正确。经常遇到的参数是oneshot、disabled等等控制service启动的参数,这些参数只是简单设置相关标志位而已。
需要关注的onrestart这个关键字,它一般用作执行相关命令的动作,这些命令在service启动的时候会同时进行,它们都被添加到了svc自己的commands链表里以方便执行。
还有socket关键字,Android中service之间通讯一般都用到socket机制,socket的建立规则也是在配置文件中定义的。可以看到一个sevice有多个socket时,它们是如何关联在一起的。
action
对于action section来说相对简单一些。
简单的分配了内存,初始化了act->commands链表,把act加入到action_list中去。注意action section关键字只有一个参数。在.rc文件中on就是action section关键字 ,并且一般用来标识某一section,设置property值。 与service的情况类似,action section关键字下每个行都会独立解析。每次解析到新的一行时,都会调用state->parse_line,即parse_line_action()。
可以看到action关键字下需要执行工作很简单很简单,就是将命令与命令的参数加入到act->commands链表中去。
到此为止解析配置文件的工作基本已经解析完成了,而具体的执行操作在Init进程中会单独执行。
最后摘录一段init.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通讯优先级 …… …………省略部分内容
附录-配置文件关键字列表
关键字 | 类型 | 参数个数 | 对应函数 | 说明 |
capability | OPTION | 0 | null | |
chdir | COMMAND | 1 | do_chdir | |
chroot | COMMAND | 1 | do_chroot | |
class | OPTION | 0 | null |
相关文章推荐
- android启动过程配置文件的解析与语法 .
- Android代码混淆加密配置(Proguard文件解析)
- android 配置文件解析
- android启动过程配置文件的解析与语法
- Android 配置文件 AndroidManifest 解析
- AndroidManifest.xml配置文件解析一
- AndroidManifest.xml配置文件解析
- Android开机流程分析 -- init进程之配置文件解析
- android启动过程配置文件的解析与语法
- AndroidManifest.xml配置文件解析
- AndroidManifest.xml配置文件解析二
- android启动过程配置文件的解析与语法
- 深入理解init_2-----解析配置文件init.rc(基于Android 2.2,代码源于Google)
- android新bug,解析app配置文件时出错
- Android 配置文件 AndroidManifest 解析
- android 配置文件解析各个文件的内涵
- android中解析text配置文件
- 【Android开发日记】之入门篇(十)——Android应用配置文件解析
- Android init进程——解析配置文件
- Android配置文件Manifest.xml如何被加载、解析的