2015년 1월 31일 토요일

init.rc 스크립트 파서(parser) 분석 : 부제 : on script 안의 실행 순서


부제 : on script 안의 실행 순서

안드로이드에서 부팅시 init daemon이 호출하는 init.rc 를 파싱하는 부분을 분석하여 보았습니다.


ex) init.rc
on emmc-fs
    chmod 0666 /dev/block/mmcblk0p14
    mount ext4 /dev/block/mmcblk0p16 /cache nosuid nodev barrier=0


1. 아래와 같을때 실행 순서는 어떻게 될까?

on fs  << 이런걸 section이라고 합니다.
  내용1 << command라고 합니다.
  내용2

on fs
  내용3
  내용4



init_parser.c


init함수에서 parse_config를 호출하게 됩니다.
  1. static void parse_config(const char *fn, char *s)
  1. for (;;) {
  2. switch (next_token(&state)) {
  3. case T_EOF:
  4. state.parse_line(&state, 0, 0);
  5. goto parser_done;
  6. case T_NEWLINE:
  7. state.line++;
  8. if (nargs) {
  9. int kw = lookup_keyword(args[0]);
  10. if (kw_is(kw, SECTION)) {
  11. state.parse_line(&state, 0, 0);
  12. parse_new_section(&state, kw, nargs, args);
  13. } else {
  14. state.parse_line(&state, nargs, args);
  15. }
  16. nargs = 0;
  17. }
  18. break;
  19. case T_TEXT:
  20. if (nargs < INIT_PARSER_MAXARGS) {
  21. args[nargs++] = state.text;
  22. }
  23. break;
  24. }
on 을 파싱할면 K_on 토큰을 리턴하게 됩니다.
그리고 on 이 붙게되면 new section을 만들게 되면서 parse_new_section 함수를 호출합니다. 그리고 라인 파서는 parse_line_action 함수로 바뀌게 됩니다.

  1. case 'o':
  2. if (!strcmp(s, "n")) return K_on;

그리고 그것을 parser_action함수를 통해서 목록에 넣습니다.
  1. void parse_new_section(struct parse_state *state, int kw,
  2. int nargs, char **args)
  3. {
  4. printf("[ %s %s ]\n", args[0],
  5. nargs > 1 ? args[1] : "");
  6. switch(kw) {
  7. case K_service:
  8. state->context = parse_service(state, nargs, args);
  9. if (state->context) {
  10. state->parse_line = parse_line_service;
  11. return;
  12. }
  13. break;
  14. case K_on:
  15. state->context = parse_action(state, nargs, args);
  16. if (state->context) {
  17. state->parse_line = parse_line_action;
  18. return;
  19. }
  20. break;

action_list에 목록을 넣게 됩니다.
  1. static void *parse_action(struct parse_state *state, int nargs, char **args)
  1. list_init(&act->commands);
  2. list_add_tail(&action_list, &act->alist);

  1. void action_for_each_trigger(const char *trigger,
  2. void (*func)(struct action *act))
  3. {
  4. struct listnode *node;
  5. struct action *act;
  6. list_for_each(node, &action_list) {
  7. act = node_to_item(node, struct action, alist);
  8. if (!strcmp(act->name, trigger)) {
  9. func(act);
  10. }
  11. }
  12. }

chmod 의경우 K_chmod를 리턴합니다.
  1. int lookup_keyword(const char *s)
  2. {
  3. switch (*s++) {
  4. case 'c':
  5. if (!strcmp(s, "opy")) return K_copy;
  6. if (!strcmp(s, "apability")) return K_capability;
  7. if (!strcmp(s, "hdir")) return K_chdir;
  8. if (!strcmp(s, "hroot")) return K_chroot;
  9. if (!strcmp(s, "lass")) return K_class;
  10. if (!strcmp(s, "lass_start")) return K_class_start;
  11. if (!strcmp(s, "lass_stop")) return K_class_stop;
  12. if (!strcmp(s, "lass_reset")) return K_class_reset;
  13. if (!strcmp(s, "onsole")) return K_console;
  14. if (!strcmp(s, "hown")) return K_chown;
  15. if (!strcmp(s, "hmod")) return K_chmod;

인자를 검사해서 command와 인자를 list에 넣습니다.
이때 lookup_keyword로 인자가 어떤 명령인지 검사합니다.
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);
}

keyward.h 에보면 어떤 명령이 section으로 동작하는지 나타나있고
command에 따른 함수 리스트도 있습니다.
  1. KEYWORD(mkdir, COMMAND, 1, do_mkdir)
  2. KEYWORD(mount_all, COMMAND, 1, do_mount_all)
  3. KEYWORD(mount, COMMAND, 3, do_mount)
  4. KEYWORD(on, SECTION, 0, 0)

parse.h
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;
};




/init/init.c

파일에 아래 내용이 있습니다.

    action_for_each_trigger("init", action_add_queue_tail);
    drain_action_queue();


parser.c 에 아래와 같은 함수가 있습니다.
앞에서 section 별로 모아진 list가 존재하고
action_for_each_trigger함수뒤에 section명을 적으면 해당 section이름을 가진 command가 queue에 모이게 되고 drain_action_queue 에 의해서 실행된다.
void action_for_each_trigger(const char *trigger,
                             void (*func)(struct action *act))
{
    struct listnode *node;
    struct action *act;
    list_for_each(node, &action_list) {
        act = node_to_item(node, struct action, alist);
        if (!strcmp(act->name, trigger)) {
            func(act);
        }
    }
}

static void drain_action_queue(void)
{
    struct listnode *node;
    struct command *cmd;
    struct action *act;
    int ret;

    while ((act = action_remove_queue_head())) {
        INFO("processing action %p (%s)\n", act, act->name);
        list_for_each(node, &act->commands) {
            cmd = node_to_item(node, struct command, clist);
            ret = cmd->func(cmd->nargs, cmd->args);
            INFO("command '%s' r=%d\n", cmd->args[0], ret);
        }
    }
}

대부분의 동작은 queue와 list로 관리하게 됩니다.
따라서 section을 획득한 순서대도 실행하게됩니다.





댓글 없음:

댓글 쓰기