1. 程式人生 > >Android啟動流程分析(七) init.rc的解析

Android啟動流程分析(七) init.rc的解析

#############################################

本文為極度寒冰原創,轉載請註明出處

#############################################

Init.rc的解析過程是筆者認為在android啟動過程中,最複雜,最難理解的部分。

雖然它的內容很少,但是卻包含了非常多的處理,接下來我們來慢慢的分析。

經過前面的分析,我們知道了read完init.rc的檔案後,儲存到了data的陣列,傳遞到了parse_config的函式裡。

我們來看一下parse_config函式的處理:

static void parse_config(const char *fn, char *s) //僅針對init.rc來說,fn指向的內容為init.rc這個檔案,s指向的就是data的陣列
{
    struct parse_state state;   // parse_state的結構體
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn; // 標明解析的是init.rc的檔案
    state.line = 0;   // 將初始化的line置為0
    state.ptr = s;   // ptr指向s的第一個元素
    state.nexttoken = 0;  // 設定nexttoken為0
    state.parse_line = parse_line_no_op;  // 設定解析的方式為parse_line_no_op, 即為不需要處理。需要注意的為parse_line是一個函式指標。

    list_init(&import_list);   // 初始化前面的import的連結串列
    state.priv = &import_list; // 將priv指向了初始化厚的import的連結串列。

    for (;;) { // 開始解析檔案
        switch (next_token(&state)) { // next_token的函式的原理是,針對state->ptr的指標進行解析,依次向後讀取data陣列中的內容,如果讀取到"\n","0"的話,返回T_EOF和T_NEWLINE, 如果讀取出來的是一個詞的話,則將內容儲存在args的陣列中,內容依次向後
        case T_EOF: // 如果檔案讀取結束的時候,
            state.parse_line(&state, 0, 0); //如果檔案是空的,那麼執行的function是parse_line_no_op, 如果不是空的,則執行的是parse_line_action 或者service,而這兩個函式中,如果nargs是0的話,都會返回掉。
            goto parser_done;  // go to parser done
        case T_NEWLINE:
            state.line++;   // 如果遇到"\n"的話,state.line會+1行
            if (nargs) { // 如果nargs有值的話,說明這一行需要解析了。
                int kw = lookup_keyword(args[0]); // 利用這一行的第一個關鍵字即args[0],獲取到kw
                if (kw_is(kw, SECTION)) { // 如果這個kw是一個SECTION的話,則會返回true,如果不是的話,則會返回false.
                    state.parse_line(&state, 0, 0); \\ 清除掉現在的parse line,開啟一個新的section
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args); // 如果不是一個section的話,則將nargs與args做為引數傳遞到parse_line對應的函式中去
                }
                nargs = 0; // 在執行完一行以後,由於有新的內容需要讀取到args中,所以將nargs設定為0.
            }
            break;
        case T_TEXT:
            if (nargs < INIT_PARSER_MAXARGS) { 
                args[nargs++] = state.text; \\ 每取出來一個token,就會將其放入到args的陣列中,且nargs會自動+1
            }
            break;
        }
    }

parser_done: \\在檔案結束的時候,會去執行到parse_done
    list_for_each(node, &import_list) {  // 這裡會去遍歷所有的import_list的節點
         struct import *import = node_to_item(node, struct import, list); // 取出這些import的節點
         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);
    }
}
在看完上面的這一段分析之後,我們會對下面的兩個函式產生濃厚的興趣,分別是
parse_new_section,kw_is
以及一個函式指標的 state.parse_line.

那接下來,我們先去看看kw_is函式:

函式原型如下:

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

而keyword_info是什麼東西呢?

static 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"
};
我們看到這個其實是一個結構體,但是在初始化這個結構體的時候,除了將第一個值置為了k_unknown之外,剩下的值都是從keywords.h中讀取的。

那我們接著看看keywords.h裡面寫的是什麼

#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(enable,      COMMAND, 1, do_enable)
    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(powerctl,    COMMAND, 1, do_powerctl)
    KEYWORD(restart,     COMMAND, 1, do_restart)

看到這裡,我們就可以看一下剛才感興趣的SECTION是什麼了,可以看到import,on,service是三個唯一的SECTION。

這也就跟前面的init.rc裡面的語法對應上了,這三個是主要的關鍵字。

如果解析到一個新的檔案是以這三個關鍵字開頭的話,會在清除掉當前的function後,去執行函式

parse_new_section

那我們接下來就去看看parse_new_section的實現:

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: \\ 如果是以service開頭的話
        state->context = parse_service(state, nargs, args);  \\ 進行parse service的初始化工作,稍後進行分析
        if (state->context) {
            state->parse_line = parse_line_service; // 將parse_line的函式指標置為parse_line_service,後面呼叫的時候就會呼叫到這個函式
            return;
        }
        break;
    case K_on: \\ 如果是以on開頭的話
        state->context = parse_action(state, nargs, args);  //進行parse_action的初始化準備工作,後面進行分析
        if (state->context) {
            state->parse_line = parse_line_action; // 將parse_line的函式指標置為parse_line_action,後面呼叫的時候就會執行這個函式。
            return;
        }
        break;
    case K_import: // 如果是以import開頭的話
        parse_import(state, nargs, args); // import這個檔案
        break;
    }
    state->parse_line = parse_line_no_op; //如果不是這三個關鍵字的話,我們不會進行處理。
}
從上面可以看到,除了進行parse action,service的初始化工作以外,最重要的工作就是將函式的指標給進行了初始化。

這樣一來,除非執行到下一個section的關鍵字,都呼叫該函式指標進行操作。

接下來的兩篇,我們會去分析,如何解析action以及service兩個關鍵的SECTION