Android读取init.rc配置文件parse_config函数解析

来源:互联网 发布:网络销售的意义 编辑:程序博客网 时间:2024/04/19 17:54

Android源代码版本:4.0.3

static void parse_config(const char *fn, char *s)函数在Android的init程序启动过程中用于解析init.rc文件。init.rc文件是安卓系统的初始化文件,其中的内容可以分为三大类:

1. Action:一个action表示一个动作,以关键字on作为开头,并加上action的名称,接下来的是对应于这个action的各种command,而command就是一些基本点linux命令。一个action可以包含有多个command,每个command被独立的执行。

init.rc文件中一条action的格式如下:

on early-init    write /proc/1/oom_adj -16    setcon u:r:init:s0    start ueventd    setsebool debugfs 1
上述信息配置了一个名称为early-init的action,包含了4条command。在安卓源码中,action的对应的结构体如下:

struct action {    /* node in list of all actions */    struct listnode alist;    /* node in the queue of pending actions */    struct listnode qlist;    /* node in list of actions for a trigger */    struct listnode tlist;    unsigned hash;    const char *name;/* action名称 */        struct listnode commands;/* 所有command组成的链表 */    struct command *current;/* 当前执行的command,方便定位 */};

2. Service:一个service代表一个服务,以service作为关键字开头,代表一个守护进程,例如zygote、sysmon、servicemanager等都会在init.rc文件中配置。

init.rc中的一条service的格式如下:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server    class main    socket zygote stream 660 root system    onrestart write /sys/android_power/request_state wake    onrestart write /sys/power/state on    onrestart restart media    onrestart restart netd    onrestart restart sensorhubservice    onrestart restart bootchecker    onrestart restart gsiff_daemon
第一行表示要启动一个名称为zygote的service,相应的执行命令为“/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server”,一个service至少包含三个部分(service + name + path),参数项不是必须的。

安卓源码中的service结构体如下所示:

struct service {        /* list of all services */    struct listnode slist;    const char *name;    const char *classname;    unsigned flags;    pid_t pid;    time_t time_started;    /* time of last start */    time_t time_crashed;    /* first crash within inspection window */    int nr_crashed;         /* number of times crashed within window */        uid_t uid;    gid_t gid;    gid_t supp_gids[NR_SVC_SUPP_GIDS];    size_t nr_supp_gids;    struct socketinfo *sockets;    struct svcenvinfo *envvars;    struct action onrestart;  /* Actions to execute on restart. */        /* keycodes for triggering this service via /dev/keychord */    int *keycodes;    int nkeycodes;    int keychord_id;    int ioprio_class;    int ioprio_pri;    int nargs;    /* "MUST BE AT THE END OF THE STRUCT" */    char *args[1];}; /*     ^-------'args' MUST be at the end of this struct! */
所以根据上面的init.rc中的service记录,系统会创建一个名称为zygote的service,并根据内容设置他的name、classname、sockets以及onrestart变量。


3. import:一个import等于包含一个新文件,以import关键字区分,类似于c中的include。

init.rc中的import格式如下所示:

import /init.environ.rcimport /init.usb.rcimport /init.${ro.hardware}.rcimport /init.trace.rcimport /init.carrier.rcimport /init.container.rc


在init程序中,首先会调用init_parse_config_file函数载入并解析init.rc文件,该函数首先通过read_file将文件载入内存,并在文件尾部增加一个换行符和休止符,方便解析过程。解析函数parse_config是本文的重点。

parse_config:

static void parse_config(const char *fn, char *s)    /*在上面操作中已经将文件读取到了data中*/{     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; /* newline表示是新的一行,有两种情况会返回T_NEWLINE * 上一行有效数据已经读完了,但是还未解析,下一次调用next_tokern会返回T_NEWLINE * 读到了换行符,会直接返回T_NEWLINE */case T_NEWLINE:                                               state.line++;             if (nargs) {                 /* nargs表示还未解析的那一行的单词个数,如果nargs大于0,表示还有有效数据需要解析 *//* 根据每一行开头单词判断本行类型 */                int kw = lookup_keyword(args[0]);/* 如果起始单词是on、import、service的话,说明是一个新的section */                if (kw_is(kw, SECTION)) {                     state.parse_line(&state, 0, 0);                    parse_new_section(&state, kw, nargs, args);                 } else {                     /* 这行是一个子语句,此时的函数指针被指向相应的处理函数。 * service对应parse_line_service, * action对应parse_line_action, * 其它的是空操作*/                    state.parse_line(&state, nargs, args);                 }                 nargs = 0;             }             break;         case T_TEXT:                                  /*每一行有效数据会存储在args中,每一个单词占一个位置,nargs表示目前读取的这一行数据读了几个单词*/            if (nargs < INIT_PARSER_MAXARGS) {                 args[nargs++] = state.text;             }             break;         }     } }

首先定义一个状态结构体用来保存当前的解析状态,包括当前解析到哪里(ptr)以及当前解析的是属于action还是service(parse_line,不同的类别函数指针不同)等。然后循环调用next_token函数从文件中尝试获取下一个处理单元,一个处理单元可以是:新的一行,文件末尾以及一行中的一个单词。根据返回值的不同类型执行不同的操作。

1. 如果是EOF:表示读取到了文件尾部,将最后一行解析完毕后返回。

2. 如果是一个TEXT:表示读取到了一个有效单词,但是本行还未读完,由于是按照行来解析数据,将其存入args数组中并将计数器nargs加1。

3. 如果是NEXLINE:表示是一个新的行,分两种情况:

a). 在读取行时还没有读到单词就遇到了换行符,会直接返回T_NEWLINE,这时nargs等于0。没有有效数据,直接进行下一次循环。

b). 如果正在读取有效数据(已经读取了一些单词),遇到了换行符,会将state的nexttoken成员设置完T_NEWLINE,下一次调用的时候会直接返回T_NEWLINE。这种情况下计数器nargs是大于1的。然后调用lookup_keyword,并将本行的第一个单词作为参数传递。lookup_keyword根据参数判断本行数据的标签内容,如果是import、on、service的话,表示这是一个新的section,需要在parse_config中调用parse_new_section。最后将计数器nargs置为0为解析下一行做准备。

parse_new_section

void parse_new_section(struct parse_state *state, int kw,                       int nargs, char **args){    printf("[ %s %s ]\n", args[0],           nargs > 1 ? args[1] : "");    switch(kw) {/* 是一条service */    case K_service:        /* 通过调用service_find_by_name函数从service_list中根据名称查找 * 是否有对应的struct service,不能打开相同的service,如果没有找 * 到,通过调用calloc分配内存,初始化struct service参数,然后调 * 用list_add_tail将该service加入service_list循环双链表中,最后返回 * 新建的service */        state->context = parse_service(state, nargs, args);        if (state->context) {            /* 不为空表示是新建的service,将state的函数指针变换为parse_line_service, * 从这一行开始的语句将会用service函数进行解析 */            state->parse_line = parse_line_service;            return;        }        break;    /* 是一条action */    case K_on:        /*类似的将相应的action添加到action_list*/        state->context = parse_action(state, nargs, args);        if (state->context) {            /*action不为空的话将state的函数指针变为parse_line_action,接下来是解析action子语句,即command*/            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]);/*import的是类似于init.environ.rc的文件,也调用read_file和parse_config将其加载*/            if (ret)                ERROR("could not import file %s\n", args[1]);        }    }    state->parse_line = parse_line_no_op;}
可以看到在parse_new_section函数中不仅会新建service、action,也会把state的函数指针parse_line指向相应的解析函数。在parse_config函数中调用parse_line的实际执行过程也会改变,解析方式非常的灵活。。





0 0
原创粉丝点击