android解析配置文件

来源:互联网 发布:土豆网mac客户端 编辑:程序博客网 时间:2024/05/04 21:29

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 systemon 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 drmservice 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
原创粉丝点击