Android 源码 解析 init.rc 脚本

624 阅读8分钟

init.rc 脚本是在 init.cpp 入口方法 main 中开始解析的,实际工作调用了 init_parser.cpp 内的 init_parse_config_file 方法。

system/core/init/init.cpp

......
int main(int argc, char** argv) {
    ......
    // 解析 init.rc 脚本
    init_parse_config_file("/init.rc");
    ......
    return 0;
}

下面我们以解析 system/core/rootdir/init.zygote32.rc 为例,具体 init.zygote32.rc 脚本的含义可以参考《Android 源码 读懂 init.zygote32.rc 文件》一节。

init.zygote32.rc 通过 import 语句导入到 init.rc。

system/core/rootdir/init.zygote32.rc

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
    writepid /dev/cpuset/foreground/tasks

system/core/rootdir/init.rc

......
import /init.${ro.zygote}.rc
......

init_parse_config_file 方法声明在头文件 init_parser.h 中,实现在对应的 cpp 文件中。init_parser.h 文件中还声明了 action、service 结构体,具体这两个结构体的定义是在 init.h 文件中。

system/core/init/init_parser.h

#ifndef _INIT_INIT_PARSER_H_
#define _INIT_INIT_PARSER_H_

#define INIT_PARSER_MAXARGS 64

struct action;
struct service;
......
int init_parse_config_file(const char *fn);
......
#endif

init_parse_config_file 方法中首先从 path 路径中读取 init.rc 文本文件对应的内容,接着调用 parse_config 函数做实际的解析工作。

system/core/init/init_parser.cpp

int init_parse_config_file(const char* path) {
    INFO("Parsing %s...\n", path);
    Timer t;
    std::string data;
    if (!read_file(path, &data)) {
        return -1;
    }

    data.push_back('\n'); // TODO: fix parse_config.
    parse_config(path, data);
    dump_parser_state();

    NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration());
    return 0;
}

parse_config 函数一行一行的处理 init.rc 脚本,做实际的解析工作。listnode 是用来实现双向链表的,parse_state 是一个解析状态结构体。parse_line_no_op 函数是个空实现,不解析任何行。我们要解析的 service 隐式声明为一个新的 Section。 所有命令(Command)或选项(Option)都属于最近声明的 Section。 所以下面的流程会进入 parse_new_section 函数解析 init.zygote32.rc 中定义的 service。当然因为 service 定义在 init.zygote32.rc 文件中,那么需要在 parser_done 标签处先处理。

system/core/init/init_parser.cpp

static void parse_config(const char *fn, const std::string& data)
{
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];

    int nargs = 0;

    parse_state state;
    state.filename = fn;
    state.line = 0;
    state.ptr = strdup(data.c_str());  // TODO: fix this code!
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;
    // 初始化 import 链表
    list_init(&import_list);
    state.priv = &import_list;

    for (;;) {
        // next_token 用来获取下一个标记,具体参见下文
        switch (next_token(&state)) {
        // 文件结尾 case
        case T_EOF:
            state.parse_line(&state, 0, 0);
            goto parser_done;
        // 新的文本行 case
        case T_NEWLINE:
            state.line++;
            if (nargs) {
                // 查找关键字
                int kw = lookup_keyword(args[0]);
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0);
                    // 解析 section
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    // 解析其他行,比如 service 定义下面的行
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        // 文本 case
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }
// 处理 import_list
parser_done:
    list_for_each(node, &import_list) {
         // 将 import_list 中的 listnode 转化为 import 结构体
         struct import *import = node_to_item(node, struct import, list);
         int ret;
         // 解析 import 语句对应的文件
         ret = init_parse_config_file(import->filename);
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}

先来研究一下 parser.h 头文件。这个头文件定义了 T_EOF、T_TEXT 和 T_NEWLINE,以及 parse_state 结构体,声明了 next_token 函数。

tips:

EOF 是一个计算机术语,为 End Of File 的缩写,在操作系统中表示资料源无更多的资料可读取。资料源通常称为档案或串流。通常在文本的最后存在此字符表示资料结束。

system/core/init/parser.h

#ifndef PARSER_H_
#define PARSER_H_

#define T_EOF 0
#define T_TEXT 1
#define T_NEWLINE 2

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

void dump_parser_state(void);
int next_token(struct parse_state *state);
void parse_error(struct parse_state *state, const char *fmt, ...);

#endif /* PARSER_H_ */

next_token 函数没什么趣味,主要工作就是寻找下一个标识(文件结束、新行和文本),当然它还会略过注释(以 # 开头)。

system/core/init/parser.cpp

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

再来研究一下 lookup_keyword 函数。根据传入的字符串去匹配返回 K_xxx ,每个 K_xxx 关联了一个 do_xxx 函数。

system/core/init/init_parser.cpp

static int lookup_keyword(const char *s)
{
    switch (*s++) {
    case 'b':
        if (!strcmp(s, "ootchart_init")) return K_bootchart_init;
        break;
    case 'c':
        if (!strcmp(s, "opy")) return K_copy;
        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, "nable")) return K_enable;
        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;
        if (!strcmp(s, "nstallkey")) return K_installkey;
        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;
        if (!strcmp(s, "oad_system_props")) return K_load_system_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 'p':
        if (!strcmp(s, "owerctl")) return K_powerctl;
        break;
    case 'r':
        if (!strcmp(s, "estart")) return K_restart;
        if (!strcmp(s, "estorecon")) return K_restorecon;
        if (!strcmp(s, "estorecon_recursive")) return K_restorecon_recursive;
        if (!strcmp(s, "mdir")) return K_rmdir;
        if (!strcmp(s, "m")) return K_rm;
        break;
    case 's':
        if (!strcmp(s, "eclabel")) return K_seclabel;
        // service 在这里
        if (!strcmp(s, "ervice")) return K_service;
        if (!strcmp(s, "etenv")) return K_setenv;
        if (!strcmp(s, "etprop")) return K_setprop;
        if (!strcmp(s, "etrlimit")) return K_setrlimit;
        if (!strcmp(s, "etusercryptopolicies")) return K_setusercryptopolicies;
        if (!strcmp(s, "ocket")) return K_socket;
        if (!strcmp(s, "tart")) return K_start;
        if (!strcmp(s, "top")) return K_stop;
        if (!strcmp(s, "wapon_all")) return K_swapon_all;
        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 'v':
        if (!strcmp(s, "erity_load_state")) return K_verity_load_state;
        if (!strcmp(s, "erity_update_state")) return K_verity_update_state;
        break;
    case 'w':
        if (!strcmp(s, "rite")) return K_write;
        if (!strcmp(s, "ritepid")) return K_writepid;
        if (!strcmp(s, "ait")) return K_wait;
        break;
    }
    return K_UNKNOWN;
}

现在可以继续关注 parse_new_section 函数了,根据不同的 kw 类型,进入相应的解析流程。parse_new_section 最终会调用 parse_service 方法解析 service。并将解析出的 service 结构体赋值给 parse_state 结构体的成员 context,下一步就会赋值 parse_state 结构体的成员 parse_line 为 parse_line_service,这是一个函数指针。然后接下来 parse_config 函数中进一步解析 service 下的其他配置(对应 service 下的其他行)调用 parse_line_service 函数进行的。

system/core/init/init_parser.cpp

static 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;
    ......
    }
    state->parse_line = parse_line_no_op;
}

parse_service 将输入的字符串解析成 service 结构体。

system/core/init/init_parser.cpp

static void *parse_service(struct parse_state *state, int nargs, char **args)
{
    ......
    service* svc = (service*) service_find_by_name(args[1]);
    ......
    nargs -= 2;
    svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
    ......
    svc->name = strdup(args[1]);
    svc->classname = "default";
    memcpy(svc->args, args + 2, sizeof(char*) * nargs);
    trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
    svc->args[nargs] = 0;
    svc->nargs = nargs;
    // onrestart 是一个 action 结构体
    list_init(&svc->onrestart.triggers);
    cur_trigger->name = "onrestart";
    list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist);
    list_init(&svc->onrestart.commands);
    // 将 service 添加到了 service_list 双链表尾部。
    list_add_tail(&service_list, &svc->slist);
    return svc;
}

下面是 action 结构体。

system/core/init/init.h

struct action {
        /* action 列表中的节点 */
    struct listnode alist;
        /* 等待队列中的节点 */
    struct listnode qlist;
        /* 触发器的 action 列表中的节点 */
    struct listnode tlist;

    unsigned hash;

        /* 触发命令的 action 列表*/
    struct listnode triggers;
    struct listnode commands;
    struct command *current;
};

parse_line_service 解析出 service class 名为 main,下一步解析出对应的 socketinfo 结构体,接着解析解析服务重启时执行的命令,最后解析 writepid 命令。

static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
    struct service *svc = (service*) 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) {
    // 解析 classname
    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 = (int*) 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 = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs);
        // do_xxx 函数指针赋给 command 结构体 func 成员
        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 */
        if (nargs < 3) {
            parse_error(state, "setenv option requires name and value arguments\n");
            break;
        }
        svcenvinfo* ei = (svcenvinfo*) 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;
    }
    // 解析 socket
    case K_socket: {/* name type perm [ uid gid context ] */
        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;
        }
        socketinfo* si = (socketinfo*) 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]);
        if (nargs > 6)
            si->socketcon = args[6];
        // 连接到 socket 单链表
        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:
        if (nargs != 2) {
            parse_error(state, "seclabel option requires a label string\n");
        } else {
            svc->seclabel = args[1];
        }
        break;
    // 解析 writepid 命令
    case K_writepid:
        if (nargs < 2) {
            parse_error(state, "writepid option requires at least one filename\n");
            break;
        }
        svc->writepid_files_ = new std::vector<std::string>;
        for (int i = 1; i < nargs; ++i) {
            svc->writepid_files_->push_back(args[i]);
        }
        break;

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

最后来看看 service 结构体。

system/core/init/init.h

struct service {
    void NotifyStateChange(const char* new_state);

        /* 连接到 service 双链表用的节点 */
    struct listnode slist;

    char *name;//服务名
    const char *classname;//类名

    unsigned flags;
    pid_t pid;
    time_t time_started;    /* 最后起动时间 */
    time_t time_crashed;    /* 第一次在检查窗口内发生 crash */
    int nr_crashed;         /* 窗口内 crash 的次数 */

    uid_t uid;
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];//补充组
    size_t nr_supp_gids;//补充组数量

    const char* seclabel;//安全相关

    struct socketinfo *sockets;// socket 单链表表头
    struct svcenvinfo *envvars;// 环境变量单链表表头

    struct action onrestart;  /* 重新启动时要执行的 Action。 */

    std::vector<std::string>* writepid_files_;// writepid 命令使用的 file path

    /* 通过 /dev/keychord 触发此服务的密钥代码 */
    int *keycodes;
    int nkeycodes;
    int keychord_id;

    IoSchedClass ioprio_class;
    int ioprio_pri;

    int nargs;
    /* "MUST BE AT THE END OF THE STRUCT" */
    char *args[1];
}; /*     ^-------'args' 一定要在结构体结尾! */

service 解析结束以后如下:
在这里插入图片描述