android启动代码init.c文件分析(二)

本文深入分析了Android系统启动时init.c文件中关于属性系统的代码,详细解释了如何处理属性值的加载和解析。此外,文章还介绍了如何解析init.rc文件,探讨了init.rc在Android启动过程中的重要作用。

/*****************************************************************

*version:android4.2

*author:冷雨

*嵌入式开发群:122879839

*****************************************************************/

 

  上一部分我们分析了init.c文件开始的那部分代码,下面这部分我们要做两部分工作。第一,简单分析一下这部分属性相关的代码;第二,解析init.rc文件。我们先贴出这这部分的代码。

    is_charger = !strcmp(bootmode, "charger");

    INFO("property init\n");
    if (!is_charger)
        property_load_boot_defaults();

    INFO("reading config file\n");
init_parse_config_file("/init.rc");


 

  这里首先判断机器是否是以字符模式启动,将结果保存到变量is_charger中。如果不是字符模式启动则执行函数property_load_boot_defaults()。这个函数是和属性相关的操作。函数位于system/core/init/property_service.c文件中。

void property_load_boot_defaults(void)
{
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);
}


 

 

  这里又调用了另一个函数。

static void load_properties_from_file(const char *fn)
{
    char *data;
    unsigned sz;

    data = read_file(fn, &sz);

    if(data != 0) {
        load_properties(data);
        free(data);
    }
}


 

  在这个函数中,会读取PROP_PATH_RAMDISK_DEFAULT文件中的内容,这个文件中存储的是系统的一些默认的属性值。最后调用load_properties函数加载这些属性。注意到,我们这里传送过去的是data,也就是我们刚才读出来的数据。看一下load_properties这个函数。

static void load_properties(char *data)
{
    char *key, *value, *eol, *sol, *tmp;

    sol = data;
    while((eol = strchr(sol, '\n'))) {
        key = sol;
        *eol++ = 0;
        sol = eol;

        value = strchr(key, '=');
        if(value == 0) continue;
        *value++ = 0;

        while(isspace(*key)) key++;
        if(*key == '#') continue;
        tmp = value - 2;
        while((tmp > key) && isspace(*tmp)) *tmp-- = 0;

        while(isspace(*value)) value++;
        tmp = eol - 2;
        while((tmp > value) && isspace(*tmp)) *tmp-- = 0;

        property_set(key, value);
    }
}


 

  这个函数主要完成对传入的字符串进行解析,然后调用property_set函数。我们现在看一下,google是怎样解析这个字符串的。这样我们先找一段属性文件如下,

# begin build properties
ro.build.type=eng
ro.build.user=root


 

  上面那三行是咱家产品的属性文件的一部分,我们用他作为例子来理解一下解析算法。首先我们先把上面的那些数据编号。我们假设他们在内存中的地址从x开始。

#          b     e      g     I     n             b     u        I        l         d                 p       r        o       p       e        r       t

x x+1 x+2 x+3 x+5 x+6 x+7 x+8 x+9 x+10 x+11 x+12 x+13 x+14 x+15 x+15 x+16 x+17 x+18 x+19 x+20

   I         e       s        \n      r       o        .         b     u        I        l         d       .        t        y        p       e       =

 x+21 x+22 x+23 x+24 x+25 x+26 x+27 x+28x+29 x+30 x+31 x+32 x+33 x+34 x+35 x+36 x+37 x+38

   e       n       g       \n……

x+39 x+40 x+41 x+42……

  我们假设data中存储的就是上面那三行代码,首先将data赋值给sol,进入while循环。注意到这里有一个函数strchr(sol, '\n'),这里返回给eol的就是’\n’sol第一次出现的地址,在这里也就是x+24,然后我们将这个值赋值给key。接下来*eol++ = 0,这里将以eol为地址的数据’\n’改成0,并且eol完成了自加1,也就是说现在的字符串已经以x+24为断点截成了两段,因为0恰好代表的就是一个字符串的终点。下面看value = strchr(key, '=')这一行,这句返回的是第一个’=’出现的位置,这里value0,因为没有找到那个’=’,所以这一次循环就直接退出了。

  我们注意到现在这里solx+25,继续咱们刚才的分析。我们看一下执行到value = strchr(key, '=')这一行的时候,各个值的变化。key= x+25eol= x+42, sol=x+42,value= x+38。好的,到这里我们就跟刚才的东西接上了,我们继续往下分析,下面的这行代码是*value++ = 0,完成的功能跟上面*eol++ = 0的功能一样,将以value为地址的数据置位为0,然后让value指向下一个字符。下面的while语句就是让key往后挪,直到指向第一个不为空的字符。接下来的那个while直接飘过,第二个条件达不到。接下来我们让value重复刚才key的操作,也就是让value往后挪,直到指向第一个不为空的字符。下一个while同样是第二个条件达不到。最后调用函数property_set(key, value)。我们需要注意的是这里传递进去的这两个参数,由于我们把x+38x+42这两个位置的字符换成了0,所以这里传进去的key,value其实是ro.build.typeeng。如果了解android的话这里的意思就是这次编译的类型是eng,啥是eng,好吧,如果你编译过android源代码的话会明白的。我们还是继续去看下面的代码吧。

int property_set(const char *name, const char *value)
{
    prop_area *pa;
    prop_info *pi;

    int namelen = strlen(name);
    int valuelen = strlen(value);

    if(namelen >= PROP_NAME_MAX) return -1;
    if(valuelen >= PROP_VALUE_MAX) return -1;
    if(namelen < 1) return -1;

    pi = (prop_info*) __system_property_find(name);

    if(pi != 0) {
        /* ro.* properties may NEVER be modified once set */
        if(!strncmp(name, "ro.", 3)) return -1;

        pa = __system_property_area__;
        update_prop_info(pi, value, valuelen);
        pa->serial++;
        __futex_wake(&pa->serial, INT32_MAX);
    } else {
        pa = __system_property_area__;
        if(pa->count == PA_COUNT_MAX) return -1;

        pi = pa_info_array + pa->count;
        pi->serial = (valuelen << 24);
        memcpy(pi->name, name, namelen + 1);
        memcpy(pi->value, value, valuelen + 1);

        pa->toc[pa->count] =
            (namelen << 24) | (((unsigned) pi) - ((unsigned) pa));

        pa->count++;
        pa->serial++;
        __futex_wake(&pa->serial, INT32_MAX);
    }
    /* If name starts with "net." treat as a DNS property. */
    if (strncmp("net.", name, strlen("net.")) == 0)  {
        if (strcmp("net.change", name) == 0) {
            return 0;
        }
       /*
        * The 'net.change' property is a special property used track when any
        * 'net.*' property name is updated. It is _ONLY_ updated here. Its value
        * contains the last updated 'net.*' property.
        */
        property_set("net.change", name);
    } else if (persistent_properties_loaded &&
            strncmp("persist.", name, strlen("persist.")) == 0) {
        /*
         * Don't write properties to disk until after we have read all default properties
         * to prevent them from being overwritten by default values.
         */
        write_persistent_property(name, value);
#ifdef HAVE_SELINUX
    } else if (strcmp("selinux.reload_policy", name) == 0 &&
               strcmp("1", value) == 0) {
        selinux_reload_policy();
#endif
    }
    property_changed(name, value);
    return 0;
}


 

  下面分别使用strlen函数获得传过来的那两个参数的长度并保存在namelenvaluelen中。再下面就是一些判断逻辑,namelen不能大于PROP_NAME_MAXvaluelen 不能大于PROP_VALUE_MAX 。我们继续往下看,接下来遇到这个__system_property_find。看看他是干嘛的。

const prop_info *__system_property_find(const char *name)
{
    prop_area *pa = __system_property_area__;
    unsigned count = pa->count;
    unsigned *toc = pa->toc;
    unsigned len = strlen(name);
    prop_info *pi;

    while(count--) {
        unsigned entry = *toc++;
        if(TOC_NAME_LEN(entry) != len) continue;

        pi = TOC_TO_INFO(pa, entry);
        if(memcmp(name, pi->name, len)) continue;

        return pi;
    }

    return 0;
}


 

  一开始就涉及到其他的数据,我们先把他们补齐。

static unsigned dummy_props = 0;


 

prop_area *__system_property_area__ = (void*) &dummy_props;


 

  看到这里,下面的几乎都不用再看了。那个while的初始条件都不满足。直接返回了。这里我们假设先不返回,我们看看这个函数有什么作用。从名字上判断这里是从系统中寻找以传进来的参数name为名字的一个结构体。prop_area结构体中的count用于计数,代表的是属性的数目。如果这个count不为零就代表着这里面还有属性。上面这个函数其实就是在在属性系统里面根据名字来查找是否有这样一个属性,有的话就返回这样一个prop_info结构体。当然我们这里返回的是0了。

 

  回到property_set函数中,因为返回是0,所以这里要执行else里面的代码。在这部分代码中我们主要是将prop_info结构体放到合适位置,填充了prop_info结构体指针pi的成员,并且改变了prop_area 结构体指针pa成员变量的值,即改变属性计数。最后会调用property_changed函数去通知系统属性已经改变。

void property_changed(const char *name, const char *value)
{
    if (property_triggers_enabled)
        queue_property_triggers(name, value);
}


 

  这里如果需要执行queue_property_triggers去做一些事情的话那个if判断必须通过,可现在我们这个property_triggers_enabled还没有达到这种地步,所以这里是不会执行的。那什么时候可以执行的,如果看一下代码的话,将property_triggers_enabled设置为1差不多在系统init的最后阶段,这是后话了……

  这样我们的第一个属性就添加完毕,我们应该还记得我们这是在load_properties函数里面的一个while循环中。后面还有好多属性,就让while自己在那里慢慢添加吧。

 

  回到init.c文件的main函数中,接下来是执行init_parse_config_file("/init.rc"),这可是重头戏。我们知道android虽然叫做操作系统可是我们通常意义上也仅仅是把android当做文件系统,它的底层实际上是linux系统。在linux的根目录下面有一个init.rc文件。这个文件里有很多重要的东西。而现在这个函数需要做的就是解析这个文件,将文件中的东西保存到内存中,方便我们以后去使用,当然在这里并不是杂乱无章的去保存。init_parse_config_file位于system/core/init/init_parser.c文件中

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;
}


 

  首先使用read_file函数读出"/init.rc"中的内容,保存到data中。然后调用parse_config(fn,data)进行真正的解析。

static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    struct listnode import_list;
    struct listnode *node;
    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;

    list_init(&import_list);
    state.priv = &import_list;

    for (;;) {
        switch (next_token(&state)) {
        case T_EOF:
            state.parse_line(&state, 0, 0);
            goto parser_done;
        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;
        }
    }

parser_done:
    list_for_each(node, &import_list) {
         struct import *import = node_to_item(node, struct import, list);
         int ret;

         INFO("importing '%s'", import->filename);
         ret = init_parse_config_file(import->filename);
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}


 

  里面涉及到的结构体比较多,先来看struct parse_state

struct parse_state
{
    char *ptr;
    char *text;
    int line;
    int nexttoken;
    void *context;
    void (*parse_line)(struct parse_state *state, int nargs, char **args);
    const char *filename;
    void *priv;
};


 

  在这个结构体中我们通过成员名字凭借经验能比较容易判断出的是line是用来表示行号的,这个比较容易理解,解析一个文件的时候需要一个变量来记录行号。*filename是一个const类型,这个现在也可以确定了应该就是解析的文件的名字。其他几个成员我们在分析代码遇到的时候再解释。

  看一下代码,首先对struct parse_state类型的state成员进行一些赋值。在这里,把fn赋给了state.filename,从参数传递的过程中我们知道这里的fn指针代表的正是文件的名字也就是"/init.rc"state.line = 0,我们解析文件从第一行开始,所以这里的line就被赋值为0。接下来吧s赋值给了state.ptr,注意到这里的s其实是从上一个文件中传递过来的参数,它里面存储了init.rc文件中的内容。nexttoken不知道什么意思,不过现在也被赋值为0state.parse_line这里所赋的值比较有意思,是parse_line_no_op。从名字上我们试着判断,这个parse_line成员貌似是个用于解析行的函数指针。我们看一下parse_line_no_op这个函数里面有什么东西。

void parse_line_no_op(struct parse_state *state, int nargs, char **args)
{
}


 

  这是一个空函数。

  我们回到parse_config函数中继续往下看代码。接下来初始化了一个链表import_list,并且将它赋值给了statepriv成员。到目前为止我们还没有真正的解析那个文件的内容,在完成了这些初始化的工作后,我们进入了一个for循环,可以解析的任务应该是在这个for中完成的。

  在for循环中首先调用了函数next_token(&state)。这个函数位于system/core/init/parser.c文件中。

int next_token(struct parse_state *state)
{
    char *x = state->ptr;
    char *s;

    if (state->nexttoken) {
        int t = state->nexttoken;
        state->nexttoken = 0;
        return t;
    }

    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;
        }
    }

textdone:
    state->ptr = x;
    *s = 0;
    return T_TEXT;
text:
    state->text = s = x;
textresume:
    for (;;) {
        switch (*x) {
        case 0:
            goto textdone;
        case ' ':
        case '\t':
        case '\r':
            x++;
            goto textdone;
        case '\n':
            state->nexttoken = T_NEWLINE;
            x++;
            goto textdone;
        case '"':
            x++;
            for (;;) {
                switch (*x) {
                case 0:
                        /* unterminated quoted thing */
                    state->ptr = x;
                    return T_EOF;
                case '"':
                    x++;
                    goto textresume;
                default:
                    *s++ = *x++;
                }
            }
            break;
        case '\\':
            x++;
            switch (*x) {
            case 0:
                goto textdone;
            case 'n':
                *s++ = '\n';
                break;
            case 'r':
                *s++ = '\r';
                break;
            case 't':
                *s++ = '\t';
                break;
            case '\\':
                *s++ = '\\';
                break;
            case '\r':
                    /* \ <cr> <lf> -> line continuation */
                if (x[1] != '\n') {
                    x++;
                    continue;
                }
            case '\n':
                    /* \ <lf> -> line continuation */
                state->line++;
                x++;
                    /* eat any extra whitespace */
                while((*x == ' ') || (*x == '\t')) x++;
                continue;
            default:
                    /* unknown escape -- just copy */
                *s++ = *x++;
            }
            continue;
        default:
            *s++ = *x++;
        }
    }
    return T_EOF;
}


 

  这个函数可真够长的,不过没一行都很短,看起来很苗条。

  在这个函数中首先把state->ptr给了字符指针x,也就是说现在这个x里面存储的就是init.rc文件中的内容,然后判断一下state->nexttoken的值,我们刚才把这个值设为了0,所以if里面的代码是不能执行了。接下来又是一个for循环。

  在继续看代码之前我们先随便打开一个init.rc文件看看。你可以随便从网上找一个这样的文件,或者从自己编译的android源代码的out目录里搜一下就能够找到。打开看看,很长的,如果你是第一次看的话可能会有点蒙,里面的东西类似于shell脚本,但又不一样,里面没有多少逻辑的东西。我在这里随便贴一点。

on early-init
    # Set init and its forked children's oom_adj.
	write /proc/1/oom_adj -16


 

  我们先看这三行也先解析这三行。继续回到next_token函数中的第一个for里面,我没有说错就是第一个for,如果你的眼睛瞟的能远一些你能够清楚的看到在这个for下面还有一个forfor是年年有,今年特别多。

  我们假设我刚才贴出的那个代码就是init.rc文件的前三行。首先一个switch,里面判断的是*x,在我们假设的这个理想环境下,这里自然就是我们那里贴出的那个on early-init的第一个字符o了。为什么?因为我们刚刚才把那个state->ptr赋值给了x,咱们不能这么健忘是吧?与switch搭配的显然是case。我们一个一个慢慢看。

  case 0,如果那个x里面是0代表什么?代表里面的东西已经结束了,为什么结束了,想不明白的话可以拿出c语言的书重新翻翻……x已经到末尾了,也就代表着我们这里的解析任务已经完成了。我们把x赋值给state->ptr后返回一个T_EOF标志,这样上面的调用函数就知道我们已经完成了任务。

  case '\n',这代表什么?这代表我们遇到了一个换行符,这个换行符对我们来说没什么意义,我们想要的是我们需要的东西。既然这个东西没用我们就让这个x加一吧,然后同样要把这个x赋值给state->ptr,返回一个T_NEWLINE标志,告诉调用函数我们这里遇到的了一个换行符。

  case ' 'case '\t'case '\r',这三个字符同样没有什么意义,但是,这三个字符又没有换行符那么有魄力,遇到换行符代表这一行已经到末尾了,直接返回到调用函数中,但是遇到这三个字符,我们解析的这一行后面还有东西,那些东西我们得要啊,怎么做?让x加一,然后continue退出本次循环,重新解析下一个字符。

  case '#',如果熟悉linux的话大家应该能猜的到,在init.rc文件中不仅有有意义的语句,同样还有注释的部分,如果没有注释的东西,那程序还怎么看?在c语言中我们是用//或者/*****/来注释我们的话,在shell中,我们用#来注释,在init.rc文件中我们采用后者的注释方法,即以#来注释我们的语句。知道这些了下面的就好解释了。如果我们遇到了#,那就代表着在这一行后面的部分我们都可以不要了,对,不要了,别管你后面是金山还是银山我们都不要了,大方不?case后面接着是一个while循环。退出while的条件是*x = 0或者*x = '\n'。如果*x = 0代表着文件已经结束了,因为遇到了结束符。如果*x = '\n'那么代表着我们需要到下一行继续解析了。看while后面的那个if else 结构正是针对这两种情况进行处理,如果遇到的是换行符,那么返回一个T_NEWLINE标志,如果遇到结束符就返回一个T_EOF标志。

  default,如果我们的字符不是上面的那些那就走这一条路吧。以我们提供的那个理想的环境下,我们的字符是o,不符合上面的那些case,直接就到这里了——goto text!我们现在就到text的那个标号去。

  在这个标号里首先是保存了相关变量state->text = s = x,然后下面就是那个for循环。在这个for循环里面还是那个典型的switch case结构。在这里前面的那些判断和我们上面那个for很相似,就不一一解释了。首先会执行到default里面,*s++ = *x++,也就是让xs分别自加一。我们这里的第一行是on early-init。所以当我们遇到onearly-init之间的那个空格时,便会执行goto textdone。看一下textdone是什么东西。

    state->ptr = x;

    *s = 0;

    return T_TEXT;

  首先保存xstate->ptr,然后让*s0,当*s0后,原有的那个文件就被从现在这个s地址开始截断成了两部分,前面一部分是on,后面的那些则是原有的文件剩余的那些字符串。最后返回一个T_TEXT标志。

  我们回到parse_config的那个for循环中。这里就是通过返回的标志来决定这里后面的执行过程。这里返回T_TEXT所以执行对应的case。首先判断一下这个计数用的args是否小于INIT_PARSER_MAXARGSINIT_PARSER_MAXARGS64,也就是args应该小于这个值。然后执行[nargs++] = state.text。我们知道这个state.text里面存的是一段字符串。还记得我们刚刚截断的那个字符串吧,现在这个state.text里面存储的就是那个on。接下来又开始了for循环,继续执行next_token

  不打算详细的去看了。这里我们需要知道的是我们一开始那个state->ptr现在已经指向了下一个字符。在我们这个环境下就是指向了early-inite。在这个函数中一旦遇到换行符便会返回到上面的函数,

        case '\n':

           state->nexttoken = T_NEWLINE;

            x++;

            goto textdone;

 

………………………………………………………………

textdone:

    state->ptr = x;

   *s = 0;

   return T_TEXT;

  注意到我们这里返回的标识符仍然是T_TEXT,同时state->nexttoken现在被改成了T_NEWLINE。回到parse_config中,我们仍然会执行case T_TEXT:这一个case,这样我们就会把early-init保存到args[1]中。同时nargs也完成了自加的过程,在这里变成了2。然后又进入到next_token函数中。不过这次进入并不像前几次那样需要进入到各种for中去处理。因为这里代码开始部分的那个if判断会对我们大显身手。

    if (state->nexttoken) {
        int t = state->nexttoken;
        state->nexttoken = 0;
        return t;
}


 

  因为前面我们把state->nexttoken设置成了T_NEWLINE,而T_NEWLINE是一个大于0的数,所以直接执行if里面的代码了。If里面的逻辑很简单,返回T_NEWLINE

  回到parse_config函数中执行case T_NEWLINE对应的代码。

        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;

  首先让state.line++自加1,这个我们现在可以理解了stateline成员存放的就是init.rc的行数。没遇到一个T_NEWLINE就让它加1,这种统计方法真的不错。我们知道现在nargs2,所以if里面的代码也是可以执行的。下面调用lookup_keyword(args[0])。在这里我们需要回顾一下,在args[0]中我们放的是on,args[1]中我们放的是1。注意到什么没有,我们把没一行中所有分隔开的单词都保存到了args数组中。只要我们找到这个数组就能够依次找到里面所有保存的单词。我们还是看一下lookup_keyword这个函数吧。这个函数同样位于system/core/init/init_parser.c文件中。

int lookup_keyword(const char *s)
{
    switch (*s++) {
    case 'c':
    if (!strcmp(s, "opy")) return K_copy;
        if (!strcmp(s, "apability")) return K_capability;
        if (!strcmp(s, "hdir")) return K_chdir;
        if (!strcmp(s, "hroot")) return K_chroot;
        if (!strcmp(s, "lass")) return K_class;
        if (!strcmp(s, "lass_start")) return K_class_start;
        if (!strcmp(s, "lass_stop")) return K_class_stop;
        if (!strcmp(s, "lass_reset")) return K_class_reset;
        if (!strcmp(s, "onsole")) return K_console;
        if (!strcmp(s, "hown")) return K_chown;
        if (!strcmp(s, "hmod")) return K_chmod;
        if (!strcmp(s, "ritical")) return K_critical;
        break;
    case 'd':
        if (!strcmp(s, "isabled")) return K_disabled;
        if (!strcmp(s, "omainname")) return K_domainname;
        break;
    case 'e':
        if (!strcmp(s, "xec")) return K_exec;
        if (!strcmp(s, "xport")) return K_export;
        break;
    case 'g':
        if (!strcmp(s, "roup")) return K_group;
        break;
    case 'h':
        if (!strcmp(s, "ostname")) return K_hostname;
        break;
    case 'i':
        if (!strcmp(s, "oprio")) return K_ioprio;
        if (!strcmp(s, "fup")) return K_ifup;
        if (!strcmp(s, "nsmod")) return K_insmod;
        if (!strcmp(s, "mport")) return K_import;
        break;
    case 'k':
        if (!strcmp(s, "eycodes")) return K_keycodes;
        break;
    case 'l':
        if (!strcmp(s, "oglevel")) return K_loglevel;
        if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;
        break;
    case 'm':
        if (!strcmp(s, "kdir")) return K_mkdir;
        if (!strcmp(s, "ount_all")) return K_mount_all;
        if (!strcmp(s, "ount")) return K_mount;
        break;
    case 'o':
        if (!strcmp(s, "n")) return K_on;
        if (!strcmp(s, "neshot")) return K_oneshot;
        if (!strcmp(s, "nrestart")) return K_onrestart;
        break;
    case 'r':
        if (!strcmp(s, "estart")) return K_restart;
        if (!strcmp(s, "estorecon")) return K_restorecon;
        if (!strcmp(s, "mdir")) return K_rmdir;
        if (!strcmp(s, "m")) return K_rm;
        break;
    case 's':
        if (!strcmp(s, "eclabel")) return K_seclabel;
        if (!strcmp(s, "ervice")) return K_service;
        if (!strcmp(s, "etcon")) return K_setcon;
        if (!strcmp(s, "etenforce")) return K_setenforce;
        if (!strcmp(s, "etenv")) return K_setenv;
        if (!strcmp(s, "etkey")) return K_setkey;
        if (!strcmp(s, "etprop")) return K_setprop;
        if (!strcmp(s, "etrlimit")) return K_setrlimit;
        if (!strcmp(s, "etsebool")) return K_setsebool;
        if (!strcmp(s, "ocket")) return K_socket;
        if (!strcmp(s, "tart")) return K_start;
        if (!strcmp(s, "top")) return K_stop;
        if (!strcmp(s, "ymlink")) return K_symlink;
        if (!strcmp(s, "ysclktz")) return K_sysclktz;
        break;
    case 't':
        if (!strcmp(s, "rigger")) return K_trigger;
        break;
    case 'u':
        if (!strcmp(s, "ser")) return K_user;
        break;
    case 'w':
        if (!strcmp(s, "rite")) return K_write;
        if (!strcmp(s, "ait")) return K_wait;
        break;
    }
    return K_UNKNOWN;
}

 

  函数的代码很长但是逻辑很简单。我们这里传过来的是字符串”on”。相关代码如下。

    case 'o':
        if (!strcmp(s, "n")) return K_on;
        if (!strcmp(s, "neshot")) return K_oneshot;
        if (!strcmp(s, "nrestart")) return K_onrestart;
        break;


 

  这里返回K_on关键字,并保存到kw中。接下来在if中调用另一个函数kw_is(kw, SECTION),看一下它的实现代码。

#define kw_is(kw, type) (keyword_info[kw].flags & (type))


 

  看到没,这里出现了一些新东西。keyword_info是哪里定义的?我们找找。在上面那还代码前面有更多的一些代码。我们把它贴出来。

#include "keywords.h"

#define KEYWORD(symbol, flags, nargs, func) \
    [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },

struct {
    const char *name;
    int (*func)(int nargs, char **args);
    unsigned char nargs;
    unsigned char flags;
} keyword_info[KEYWORD_COUNT] = {
    [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
#include "keywords.h"
};
#undef KEYWORD

  keyword_info是一个结构体数组。keyword_info[0]直接在代码中就已经给出来了,而除了keyword_info[0]之外的变量则是通过#include "keywords.h"来引入的。为了方便看,我们还是把这个文件都贴出来吧。

#ifndef KEYWORD
int do_chroot(int nargs, char **args);
int do_chdir(int nargs, char **args);
int do_class_start(int nargs, char **args);
int do_class_stop(int nargs, char **args);
int do_class_reset(int nargs, char **args);
int do_domainname(int nargs, char **args);
int do_exec(int nargs, char **args);
int do_export(int nargs, char **args);
int do_hostname(int nargs, char **args);
int do_ifup(int nargs, char **args);
int do_insmod(int nargs, char **args);
int do_mkdir(int nargs, char **args);
int do_mount_all(int nargs, char **args);
int do_mount(int nargs, char **args);
int do_restart(int nargs, char **args);
int do_restorecon(int nargs, char **args);
int do_rm(int nargs, char **args);
int do_rmdir(int nargs, char **args);
int do_setcon(int nargs, char **args);
int do_setenforce(int nargs, char **args);
int do_setkey(int nargs, char **args);
int do_setprop(int nargs, char **args);
int do_setrlimit(int nargs, char **args);
int do_setsebool(int nargs, char **args);
int do_start(int nargs, char **args);
int do_stop(int nargs, char **args);
int do_trigger(int nargs, char **args);
int do_symlink(int nargs, char **args);
int do_sysclktz(int nargs, char **args);
int do_write(int nargs, char **args);
int do_copy(int nargs, char **args);
int do_chown(int nargs, char **args);
int do_chmod(int nargs, char **args);
int do_loglevel(int nargs, char **args);
int do_load_persist_props(int nargs, char **args);
int do_wait(int nargs, char **args);
#define __MAKE_KEYWORD_ENUM__
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum {
    K_UNKNOWN,
#endif
    KEYWORD(capability,  OPTION,  0, 0)
    KEYWORD(chdir,       COMMAND, 1, do_chdir)
    KEYWORD(chroot,      COMMAND, 1, do_chroot)
    KEYWORD(class,       OPTION,  0, 0)
    KEYWORD(class_start, COMMAND, 1, do_class_start)
    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)
    KEYWORD(class_reset, COMMAND, 1, do_class_reset)
    KEYWORD(console,     OPTION,  0, 0)
    KEYWORD(critical,    OPTION,  0, 0)
    KEYWORD(disabled,    OPTION,  0, 0)
    KEYWORD(domainname,  COMMAND, 1, do_domainname)
    KEYWORD(exec,        COMMAND, 1, do_exec)
    KEYWORD(export,      COMMAND, 2, do_export)
    KEYWORD(group,       OPTION,  0, 0)
    KEYWORD(hostname,    COMMAND, 1, do_hostname)
    KEYWORD(ifup,        COMMAND, 1, do_ifup)
    KEYWORD(insmod,      COMMAND, 1, do_insmod)
    KEYWORD(import,      SECTION, 1, 0)
    KEYWORD(keycodes,    OPTION,  0, 0)
    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)
    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)
    KEYWORD(mount,       COMMAND, 3, do_mount)
    KEYWORD(on,          SECTION, 0, 0)
    KEYWORD(oneshot,     OPTION,  0, 0)
    KEYWORD(onrestart,   OPTION,  0, 0)
    KEYWORD(restart,     COMMAND, 1, do_restart)
    KEYWORD(restorecon,  COMMAND, 1, do_restorecon)
    KEYWORD(rm,          COMMAND, 1, do_rm)
    KEYWORD(rmdir,       COMMAND, 1, do_rmdir)
    KEYWORD(seclabel,    OPTION,  0, 0)
    KEYWORD(service,     SECTION, 0, 0)
    KEYWORD(setcon,      COMMAND, 1, do_setcon)
    KEYWORD(setenforce,  COMMAND, 1, do_setenforce)
    KEYWORD(setenv,      OPTION,  2, 0)
    KEYWORD(setkey,      COMMAND, 0, do_setkey)
    KEYWORD(setprop,     COMMAND, 2, do_setprop)
    KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)
    KEYWORD(setsebool,   COMMAND, 1, do_setsebool)
    KEYWORD(socket,      OPTION,  0, 0)
    KEYWORD(start,       COMMAND, 1, do_start)
    KEYWORD(stop,        COMMAND, 1, do_stop)
    KEYWORD(trigger,     COMMAND, 1, do_trigger)
    KEYWORD(symlink,     COMMAND, 1, do_symlink)
    KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)
    KEYWORD(user,        OPTION,  0, 0)
    KEYWORD(wait,        COMMAND, 1, do_wait)
    KEYWORD(write,       COMMAND, 2, do_write)
    KEYWORD(copy,        COMMAND, 2, do_copy)
    KEYWORD(chown,       COMMAND, 2, do_chown)
    KEYWORD(chmod,       COMMAND, 2, do_chmod)
    KEYWORD(loglevel,    COMMAND, 1, do_loglevel)
    KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)
    KEYWORD(ioprio,      OPTION,  0, 0)
#ifdef __MAKE_KEYWORD_ENUM__
    KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif


 

  这里面的内容可真够长的,如果我们仔细看的话会发现在开头部分使用了一个#ifndef KEYWORD……#endif包起来了一部分内容,但是很显然我们在init_parser.c文件中已经#define KEYWORD过了,而在定义keyword_info的上面几行里,我们看到已经include一次这个keywords.h文件了,在keywords.h这个文件的最后几行我们注意到#undef __MAKE_KEYWORD_ENUM__这么一行代码。所以在我们第二次include这个文件的时候我们仅仅是include了从KEYWORD(capability,  OPTION, 0, 0)KEYWORD(ioprio,     OPTION,  0, 0)这部分代码。那现在keyword_info的成员就很清晰了,我们把他们写出来。

keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = "unknown", 0, 0, 0 ,
KEYWORD(capability,  OPTION,  0, 0)
    KEYWORD(chdir,       COMMAND, 1, do_chdir)
    KEYWORD(chroot,      COMMAND, 1, do_chroot)
    KEYWORD(class,       OPTION,  0, 0)
    KEYWORD(class_start, COMMAND, 1, do_class_start)
    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)
    KEYWORD(class_reset, COMMAND, 1, do_class_reset)
    KEYWORD(console,     OPTION,  0, 0)
    KEYWORD(critical,    OPTION,  0, 0)
    KEYWORD(disabled,    OPTION,  0, 0)
    KEYWORD(domainname,  COMMAND, 1, do_domainname)
    KEYWORD(exec,        COMMAND, 1, do_exec)
    KEYWORD(export,      COMMAND, 2, do_export)
    KEYWORD(group,       OPTION,  0, 0)
    KEYWORD(hostname,    COMMAND, 1, do_hostname)
    KEYWORD(ifup,        COMMAND, 1, do_ifup)
    KEYWORD(insmod,      COMMAND, 1, do_insmod)
    KEYWORD(import,      SECTION, 1, 0)
    KEYWORD(keycodes,    OPTION,  0, 0)
    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)
    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)
    KEYWORD(mount,       COMMAND, 3, do_mount)
    KEYWORD(on,          SECTION, 0, 0)
    KEYWORD(oneshot,     OPTION,  0, 0)
    KEYWORD(onrestart,   OPTION,  0, 0)
    KEYWORD(restart,     COMMAND, 1, do_restart)
    KEYWORD(restorecon,  COMMAND, 1, do_restorecon)
    KEYWORD(rm,          COMMAND, 1, do_rm)
    KEYWORD(rmdir,       COMMAND, 1, do_rmdir)
    KEYWORD(seclabel,    OPTION,  0, 0)
    KEYWORD(service,     SECTION, 0, 0)
    KEYWORD(setcon,      COMMAND, 1, do_setcon)
    KEYWORD(setenforce,  COMMAND, 1, do_setenforce)
    KEYWORD(setenv,      OPTION,  2, 0)
    KEYWORD(setkey,      COMMAND, 0, do_setkey)
    KEYWORD(setprop,     COMMAND, 2, do_setprop)
    KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)
    KEYWORD(setsebool,   COMMAND, 1, do_setsebool)
    KEYWORD(socket,      OPTION,  0, 0)
    KEYWORD(start,       COMMAND, 1, do_start)
    KEYWORD(stop,        COMMAND, 1, do_stop)
    KEYWORD(trigger,     COMMAND, 1, do_trigger)
    KEYWORD(symlink,     COMMAND, 1, do_symlink)
    KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)
    KEYWORD(user,        OPTION,  0, 0)
    KEYWORD(wait,        COMMAND, 1, do_wait)
    KEYWORD(write,       COMMAND, 2, do_write)
    KEYWORD(copy,        COMMAND, 2, do_copy)
    KEYWORD(chown,       COMMAND, 2, do_chown)
    KEYWORD(chmod,       COMMAND, 2, do_chmod)
    KEYWORD(loglevel,    COMMAND, 1, do_loglevel)
    KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)
    KEYWORD(ioprio,      OPTION,  0, 0)
}


 

  显然,这并不是我们想要的结果,我们需要把KEYWORD也转化一下。

init_parser.c文件中有这么两行

#define KEYWORD(symbol, flags, nargs, func) \
[ K_##symbol ] = { #symbol, func, nargs + 1, flags, },


 

  很显然这是一个宏定义,基于此我们把刚才那个keyword_info数组里面的东西都替换一下。

  以KEYWORD(capability,  OPTION, 0, 0)为例子。经过宏替换后这一行会被替换成如下的形式:“[ K_ capability ] = { capability, 0, 1, OPTION, },”为什么会如此替换?其他的不多解释了,只说两点。##”是连字符,在宏替换的时候我们只需要把“k_”和“symbol”这两个字符连接成一个就可以了。“##”是连字符,“#”又是什么意思呢?我没有写错,你也没有看错,我前面是两个#,后面只有一个#。本来由{}包起来的代码里面那个symbol是不能当成一个一个符号来看的,可是我们在前面加上一个#后,这个symbol就可以被看成一个符号了。这个符号显然是从宏使用的时候传递过来的。还是以刚才这个例子来说,如果我们#symbol前面没有那个#,我们最终的转化结果是“[ K_ capability ] = { symbol, 0, 1, OPTION, },”,而我们现在则是“[K_ capability ] = { capability, 0, 1, OPTION, },”。看出区别没?好了,我们把那个keyword_info结构体数组根据我们刚才说的再次转化一下,当然这次我们这次仅仅贴出来几个,其他的大家自己去转吧,这种工作是只费体力不费脑力的。

keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = "unknown", 0, 0, 0,
[ K_ capability ] = { capability, 0, 1, OPTION, }, 
……………………………………………………
[ K_ on ] = { on, 0, 1, SECTION, },
……………………………………………………
[ K_ service ] = { service, 0, 1, SECTION, },
……………………………………………………
[ K_ write] = { write, do_write, 3, COMMAND, },
KEYWORD(write,       COMMAND, 2, do_write)
……………………………………………………
}


 

  到现在为止我们已经把那个结构体数组keyword_info彻彻底底的翻译了一遍,我们还是再次回到kw_is那个宏定义中,看看这个宏返回的到底是什么东西。我们还是用老办法一一把它给替换掉。keyword_info[kw].flags & (type)在这里我们知道传入的kwK_on,而type则是SECTION,代码变换成keyword_info[K_on].flags& (SECTION)。我们知道keyword_info是一个结构体数组,每一个结构体的第四个成员就是与其对应的flags我们找到keyword_info[K_on]flags成员在上面我已经贴出来了正是SECTION。现在那个宏定义的值我们能够看出来了,两个SECTION相与这里就是true。那么这里返回true的情况有几种呢?如果我们仔细查看keyword_info数组的话会发现有三种的flagsSECTION,分别是service,on,import。我们现在回到parse_config函数中,刚才kw_is函数返回的是true所以这里需要执行if里面的代码。首先执行state.parse_line函数,如果我们还有印象的话,我们在parse_config开始部分给state.parse_line赋值为parse_line_no_op,这是一个空函数。接下来执行parse_new_section函数,传递的参数中kwK_onnargs=2, args里面刚才从init.rc中得到的所有的单词。我们看一下这个函数的实现代码。

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) {
    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:
        parse_import(state, nargs, args);
        break;
    }
    state->parse_line = parse_line_no_op;
}


 

  这里面还是一个switch……case机构的代码。我们可以看到这里的case只有三种,这正对应于我们的service,on,import三大关键字,不同的case对应不一样的操作。其他的不看了,我们只看一下case K_on的情况,剩下那两个我们一会再聊。首先调用parse_actionstate->context赋值。

static void *parse_action(struct parse_state *state, int nargs, char **args)
{
    struct action *act;
    if (nargs < 2) {
        parse_error(state, "actions must have a trigger\n");
        return 0;
    }
    if (nargs > 2) {
        parse_error(state, "actions may not have extra parameters\n");
        return 0;
    }
    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;
}


 

  在这个函数中有一个结构体我们需要看一下。

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;
    
    struct listnode commands;
    struct command *current;
};


 

  在这里首先检查参数的个数,看来这里传递进来的nargs只能是2,否则要报错了。也就是说我们在init.rc中以on这个SECTION属性开头的参数只能有两个,比如我们刚刚得到的那个on early-init。参数检查完毕后,就用callocaction结构体申请内存。然后初始化act->name = args[1],这里也就是early-init了。初始化&act->commands链表,并把act->alist添加到action_list链表的尾部。action_list是一个全局链表。当我们把act->alist添加到action_list后,我们就可以从全局链表中找到这个action变量了。回到parse_new_section中,如果刚才分配内存成功的话就会执行if里面的代码state->parse_line = parse_line_service;下次当我们调用state->parse_line的时候就直接调用parse_line_service了。

  回到parse_config函数,接下来我们重新把nargs归零,然后又开启for循环继续解析init.rc下一行,这一行会遇到#,所以后面的会被忽略掉,这是我们的注释,不必解析。接下来就是解析write /proc/1/oom_adj -16这一行。解析过程同第一行差不多,我们就不看了。我们需要知道的是最后的存储结构args[0]对应的是writeargs[1]对应的是write /proc/1/oom_adj -16args[2]对应的是-16,最后nargs等于3。解析完毕后返回的仍然是一个T_NEWLINE标志,我们看对它的处理情况。

  跟刚才那个差不多,这里仍然是state.line++,然后通过lookup_keyword找到关键字K_write,这里这个关键字对应keyword_info成员的flags并不是SECTION,所以这里返回的是false,也就是说这里会执行else里面的语句state.parse_line(&state,nargs, args)。在我们刚才解析那个K_on关键字的时候我们曾经对state.parse_line重新赋过值。这里调用的就是我们刚刚说过的那个函数parse_line_service。我们看一下这个函数的实现代码。

static void parse_line_action(struct parse_state* state, int nargs, char **args)
{
    struct command *cmd;
    struct action *act = state->context;
    int (*func)(int nargs, char **args);
    int kw, n;

    if (nargs == 0) {
        return;
    }

    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);
}


 

  这里首先调用lookup_keyword返回K_write关键字。然后调用kw_nargs,这也是一个宏定义,我们看一下它。

#define kw_nargs(kw) (keyword_info[kw].nargs)


 

  从这里可以看出它其实获得的就是keyword_info[kw]成员的nargs,这里的就是对应于这个关键字的参数个数。我们从刚才解析的那些里面可以看出来这里是3,接下来是对它的检测代码。处理完这些,后面是对command结构体变量的初始化操作。看一下这个结构体的实现代码:

struct command
{
        /* list of commands in an action */
    struct listnode clist;

    int (*func)(int nargs, char **args);
    int nargs;
    char *args[1];
};


 

  在parse_line_action函数中首先使用malloc对其分配内存,然后调用cmd->func = kw_func(kw)。我们先看一下kw_func这个宏定义是什么。

#definekw_func(kw) (keyword_info[kw].func)

  很显然这里返回的是keyword_info[kw]成员的func,在我们这个K_write里面这里返回的是do_write

  在parse_line_action后面的那三行代码里首先对cmd->nargs = nargs赋值,然后,将我们从参数中传递过来的args的内容拷贝到cmd->args中。最后将这个command链入一个action链表中。这个action是从state->context传过来的。在我们解析具有SECTION属性的那个关键字的时候我们已经创建了这么一个action结构体。现在我们可以回顾一下,创建的这个action是链入全局链表action_list的,而这里创建的这个commands又链入了这个action中。这样我们从action_list中找到那个对应的action结构体,再从action中找到我们想要的command结构体,在这个command结构体中我们就能找到感兴趣的东西。

 

  我们现在把以on开头的SECTION说完了,我们看看我们还有什么疑问。在parse_new_section函数中提到里面有三个case, K_serviceK_onK_import。现在我们就说说K_service的情况。

parse_config函数中for循环里的case T_NEWLINE情况下,如果我们从init.rc的某一行开始的第一个单词里得到的是service,即我们的args[0]service,我们通过lookup_keyword就可以返回K_service,这样kw_is函数就可以返回true,也就是说要执行if里面的代码。state.parse_line(&state, 0, 0)这一行由于我们传递的参数是nargs0,所以在调用的过程中会什么都不操作,接下来调用parse_new_section函数。在这个函数中会执行case K_service那部分代码。

    case K_service:
        state->context = parse_service(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_service;
            return;
        }
        break;


 

  首先,调用parse_servicestate->context赋值。看一下这个函数。

static void *parse_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc;
    if (nargs < 3) {
        parse_error(state, "services must have a name and a program\n");
        return 0;
    }
    if (!valid_name(args[1])) {
        parse_error(state, "invalid service name '%s'\n", args[1]);
        return 0;
    }

    svc = service_find_by_name(args[1]);
    if (svc) {
        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
        return 0;
    }

    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结构体。

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;

#ifdef HAVE_SELINUX
    char *seclabel;
#endif

    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! */


 

  有了前面分析parse_action函数的经验,对这个函数我们大概看看。这个函数主要构造了一个service结构体,并将这个结构体添加到service_list的尾部。这中间牵扯到一个service_find_by_name (args[1])函数。这个函数的意义是当我们添加某一个service结构体的时候会首先去service_list中查找这个结构体是否已经存在了,如果已经存在了这么一个结构体就报错返回吧。

  回到parse_new_section函数中,接下来会给state->parse_line函数重新赋值为parse_line_service。接下来解析init.rc的下一行数据,最终又会调用到这个parse_line_service函数去设置这个service的一些成员。

static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc = state->context;
    struct command *cmd;
    int i, kw, kw_nargs;

    if (nargs == 0) {
        return;
    }

    svc->ioprio_class = IoSchedClass_NONE;

    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_ioprio:
        if (nargs != 3) {
            parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");
        } else {
            svc->ioprio_pri = strtoul(args[2], 0, 8);

            if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {
                parse_error(state, "priority value must be range 0 - 7\n");
                break;
            }

            if (!strcmp(args[1], "rt")) {
                svc->ioprio_class = IoSchedClass_RT;
            } else if (!strcmp(args[1], "be")) {
                svc->ioprio_class = IoSchedClass_BE;
            } else if (!strcmp(args[1], "idle")) {
                svc->ioprio_class = IoSchedClass_IDLE;
            } else {
                parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");
            }
        }
        break;
    case K_group:
        if (nargs < 2) {
            parse_error(state, "group option requires a group id\n");
        } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
            parse_error(state, "group option accepts at most %d supp. groups\n",
                        NR_SVC_SUPP_GIDS);
        } else {
            int n;
            svc->gid = decode_uid(args[1]);
            for (n = 2; n < nargs; n++) {
                svc->supp_gids[n-2] = decode_uid(args[n]);
            }
            svc->nr_supp_gids = n - 2;
        }
        break;
    case K_keycodes:
        if (nargs < 2) {
            parse_error(state, "keycodes option requires atleast one keycode\n");
        } else {
            svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));
            if (!svc->keycodes) {
                parse_error(state, "could not allocate keycodes\n");
            } else {
                svc->nkeycodes = nargs - 1;
                for (i = 1; i < nargs; i++) {
                    svc->keycodes[i - 1] = atoi(args[i]);
                }
            }
        }
        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_critical:
        svc->flags |= SVC_CRITICAL;
        break;
    case K_setenv: { /* name value */
        struct svcenvinfo *ei;
        if (nargs < 2) {
            parse_error(state, "setenv option requires name and value arguments\n");
            break;
        }
        ei = calloc(1, sizeof(*ei));
        if (!ei) {
            parse_error(state, "out of memory\n");
            break;
        }
        ei->name = args[1];
        ei->value = args[2];
        ei->next = svc->envvars;
        svc->envvars = ei;
        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;
    }
    case K_user:
        if (nargs != 2) {
            parse_error(state, "user option requires a user id\n");
        } else {
            svc->uid = decode_uid(args[1]);
        }
        break;
    case K_seclabel:
#ifdef HAVE_SELINUX
        if (nargs != 2) {
            parse_error(state, "seclabel option requires a label string\n");
        } else {
            svc->seclabel = args[1];
        }
#endif
        break;

    default:
        parse_error(state, "invalid option '%s'\n", args[0]);
    }
}


 

  这些代码实在是太长了,我们不去一一的看了,在代码中我们可以看出对service成员的操作均是通过不同的关键字设置的。了解了这些就够啦。



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值