Android的init過程(二):初始化語言(init.rc)解析

http://www.cnblogs.com/nokiaguy/p/3164799.html

本文使用的軟件版本

Android:4.2.2

Linux內核:3.1.10

    在上一篇文章中介紹了init的初始化第一階段,也就是處理各種屬性。在本文將會詳細分析init最重要的一環:解析init.rc文件。

init.rc 文件並不是普通的配置文件,而是由一種被稱爲“Android初始化語言”(Android Init Language,這裏簡稱爲AIL)的腳本寫成的文件。在瞭解init如何解析init.rc文件之前,先了解AIL非常必要,否則機械地分析 init.c及其相關文件的源代碼毫無意義。

     爲了學習AIL,讀者可以到自己Android手機的根目錄尋找init.rc文件,最好下載到本地以便查看,如果有編譯好的Android源代碼, 在<Android源代碼根目錄>out/target/product/generic/root目錄也可找到init.rc文件。

AIL由如下4部分組成。

1.  動作(Actions)

2.  命令(Commands)

3. 服務(Services)

4.  選項(Options)

     這4部分都是面向行的代碼,也就是說用回車換行符作爲每一條語句的分隔符。而每一行的代碼由多個符號(Tokens)表示。可以使用反斜槓轉義符在 Token中插入空格。雙引號可以將多個由空格分隔的Tokens合成一個Tokens。如果一行寫不下,可以在行尾加上反斜槓,來連接下一行。也就是 說,可以用反斜槓將多行代碼連接成一行代碼。

     AIL的註釋與很多Shell腳本一行,以#開頭。

     AIL在編寫時需要分成多個部分(Section),而每一部分的開頭需要指定Actions或Services。也就是說,每一個Actions或 Services確定一個Section。而所有的Commands和Options只能屬於最近定義的Section。如果Commands和 Options在第一個Section之前被定義,它們將被忽略。

Actions和Services的名稱必須唯一。如果有兩個或多個Action或Service擁有同樣的名稱,那麼init在執行它們時將拋出錯誤,並忽略這些Action和Service。

下面來看看Actions、Services、Commands和Options分別應如何設置。

Actions的語法格式如下:

on <trigger>  
   <command>  
   <command>  
   <command>

      也就是說Actions是以關鍵字on開頭的,然後跟一個觸發器,接下來是若干命令。例如,下面就是一個標準的Action。

    on boot  
        ifup lo  
        hostname localhost  
        domainname localdomain  

其中boot是觸發器,下面三行是command

那麼init.rc到底支持哪些觸發器呢?目前init.rc支持如下5類觸發器。

1.  boot

   這是init執行後第一個被觸發Trigger,也就是在 /init.rc被裝載之後執行該Trigger

2.  <name>=<value>

   當屬性<name>被設置成<value>時被觸發。例如,

on property:vold.decrypt=trigger_reset_main

    class_reset main

3.  device-added-<path>

    當設備節點被添加時觸發

4.  device-removed-<path>

   當設備節點被移除時添加

5. service-exited-<name>

   會在一個特定的服務退出時觸發

Actions後需要跟若干個命令,這些命令如下:

1.  exec <path> [<argument> ]*

  創建和執行一個程序(<path>)。在程序完全執行前,init將會阻塞。由於它不是內置命令,應儘量避免使用exec ,它可能會引起init執行超時。

    2.  export <name> <value>

在全局環境中將 <name>變量的值設爲<value>。(這將會被所有在這命令之後運行的進程所繼承)

3.  ifup <interface>

   啓動網絡接口

4.  import <filename>

   指定要解析的其他配置文件。常被用於當前配置文件的擴展

5.  hostname <name>

   設置主機名

6.  chdir <directory>

   改變工作目錄

7.  chmod <octal-mode><path>

   改變文件的訪問權限

8.  chown <owner><group> <path>

   更改文件的所有者和組

9.  chroot <directory>

  改變處理根目錄

10.  class_start<serviceclass>

   啓動所有指定服務類下的未運行服務。

11  class_stop<serviceclass>

  停止指定服務類下的所有已運行的服務。

12.  domainname <name>

   設置域名

13.  insmod <path>

   加載<path>指定的驅動模塊

14.  mkdir <path> [mode][owner] [group]

   創建一個目錄<path> ,可以選擇性地指定mode、owner以及group。如果沒有指定,默認的權限爲755,並屬於root用戶和 root組。

15. mount <type> <device> <dir> [<mountoption> ]*

   試圖在目錄<dir>掛載指定的設備。<device> 可以是mtd@name的形式指定一個mtd塊設備。<mountoption>包括 "ro"、"rw"、"re

16.  setkey

   保留,暫時未用

17.  setprop <name><value>

   將系統屬性<name>的值設爲<value>。

18. setrlimit <resource> <cur> <max>

   設置<resource>的rlimit (資源限制)

19.  start <service>

   啓動指定服務(如果此服務還未運行)。

20.stop<service>

   停止指定服務(如果此服務在運行中)。

21. symlink <target> <path>

   創建一個指向<path>的軟連接<target>。

22. sysclktz <mins_west_of_gmt>

   設置系統時鐘基準(0代表時鐘滴答以格林威治平均時(GMT)爲準)

23.  trigger <event>

  觸發一個事件。用於Action排隊

24.  wait <path> [<timeout> ]

等待一個文件是否存在,當文件存在時立即返回,或到<timeout>指定的超時時間後返回,如果不指定<timeout>,默認超時時間是5秒。

25. write <path> <string> [ <string> ]*

向<path>指定的文件寫入一個或多個字符串。  

Services (服務)是一個程序,他在初始化時啓動,並在退出時重啓(可選)。Services (服務)的形式如下:

    service <name> <pathname> [ <argument> ]*  
          <option>  
          <option>  

例如,下面是一個標準的Service用法

複製代碼
    service servicemanager /system/bin/servicemanager  
        class core  
        user system  
        group system  
        critical  
        onrestart restart zygote  
        onrestart restart media  
        onrestart restart surfaceflinger  
        onrestart restart drm  
複製代碼

Services的選項是服務的修飾符,可以影響服務如何以及怎樣運行。服務支持的選項如下:

1.  critical

表明這是一個非常重要的服務。如果該服務4分鐘內退出大於4次,系統將會重啓並進入 Recovery (恢復)模式。

2. disabled

 表明這個服務不會同與他同trigger (觸發器)下的服務自動啓動。該服務必須被明確的按名啓動。

3.  setenv <name><value>

在進程啓動時將環境變量<name>設置爲<value>。

4.  socket <name><type> <perm> [ <user> [ <group> ] ]

   Create a unix domain socketnamed /dev/socket/<name> and pass

   its fd to the launchedprocess.  <type> must be"dgram", "stream" or "seqpacket".

   User and group default to0.

   創建一個unix域的名爲/dev/socket/<name> 的套接字,並傳遞它的文件描述符給已啓動的進程。<type> 必須是 "dgram","stream" 或"seqpacket"。用戶和組默認是0。

5.  user <username>

在啓動這個服務前改變該服務的用戶名。此時默認爲 root。

6.  group <groupname> [<groupname> ]*

在啓動這個服務前改變該服務的組名。除了(必需的)第一個組名,附加的組名通常被用於設置進程的補充組(通過setgroups函數),檔案默認是root。

7.  oneshot

   服務退出時不重啓。

8.  class <name>

   指定一個服務類。所有同一類的服務可以同時啓動和停止。如果不通過class選項指定一個類,則默認爲"default"類服務。

9. onrestart

    當服務重啓,執行一個命令(下詳)。

     現在接着分析一下init是如何解析init.rc的。現在打開system/core/init/init.c文件,找到main函數。在上一篇文章中 分析了main函數的前一部分(初始化屬性、處理內核命令行等),現在找到init_parse_config_file函數,調用代碼如下:

init_parse_config_file("/init.rc");

這個方法主要負責初始化和分析init.rc文件。init_parse_config_file函數在init_parser.c文件中實現,代碼如下:

複製代碼
    int init_parse_config_file(const char *fn)  
    {  
        char *data;  
        data = read_file(fn, 0);  
        if (!data) return -1;  
        /*  實際分析init.rc文件的代碼  */  
        parse_config(fn, data);  
        DUMP();  
        return 0;  
    }  
複製代碼

      init_parse_config_file方法開始調用了read_file函數打開了/init.rc文件,並返回了文件的內容(char*類 型),然後最核心的函數是parse_config。該函數也在init_parser.c文件中實現,代碼如下:

複製代碼
    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;  
        /*  開始獲取每一個token,然後分析這些token,每一個token就是有空格、字表符和回車符分隔的字符串 
       */  
        for (;;) {  
            /*  next_token函數相當於詞法分析器  */  
            switch (next_token(&state)) {  
            case T_EOF:  /*  init.rc文件分析完畢  */  
                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:  /*  處理每一個token  */  
                if (nargs < INIT_PARSER_MAXARGS) {  
                    args[nargs++] = state.text;  
                }  
                break;  
            }  
        }  
      
    parser_done:  
        /*  最後處理由import導入的初始化文件  */  
        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);  
        }  
    }  
複製代碼

    parse_config方法的代碼就比較複雜了,現在先說說該方法的基本處理流程。首先會調用  list_init(&import_list)初始化一個鏈表,該鏈表是用於存儲通過import語句導入的初始化文件名。然後開始開始在 for循環中分析init.rc文件中的每一行代碼。最後將init.rc文件分析完後,就會進入parser_done部分,並遞歸調用 init_parse_config_file方法分析通過import導入的初始化文件。

      通過分析parse_config方法的原理,感覺也並不是很複雜。不過分析parse_config方法的具體代碼,還需要點編譯原理的知識(只是概念 上的就可以)。在for循環中調用了一個next_token方法不斷從init.rc文件中獲取token。這裏的token,就是一種編程語言的最小 單元,也就是不可再分。例如,對於傳統的編程語言,if、then等關鍵字、變量名等標識符都屬於一個token。而對於init.rc文件來 說,import、on、以及觸發器的參數值,都屬於一個token。

     一個完整的編譯器(或解析器)最開始需要進行詞法和語法分析,詞法分析就是在源代碼文件中挑出一個個的Token,也就是說,詞法分析器的返回值是 Token,而語法分析器的輸入就是詞法分析器的輸出。也就是說,語法分析器需要分析一個個的token,而不是一個個的字符。由於init解析語言很簡 單,所以就將詞法和語法分析器放到了一起。詞法分析器就是next_token函數,而語法分析器就是T_NEWLINE分支中的代碼。這些就清楚多了。 現在先看看next_token函數(在parser.c文件中實現)是如何獲取每一個token的。

複製代碼
    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;  
    }  
複製代碼

      next_token函數的代碼還是很多的,不過原理到很簡單。就是逐一讀取init.rc文件(還有import導入的初始化文件)的字符,並將 由空格、“/t”和“/r”分隔的字符串挑出來,並通過state->text返回。如果返回了正常的token,next_token函數就返回 T_TEXT。如果一行結束,就返回T_NEWLINE,如果init.rc文件的內容已讀取完,就返回T_EOF。當返回T_NEWLINE時,開始語 法分析(由於init初始化語言是基於行的,所以語言分析實際上就是分析init.rc文件的每一行,只是這些行已經被分解成一個個token了)。感興 趣的讀者可以詳細分析一下next_token函數的代碼,儘管代碼很多,但並不複雜。而且還很有意思。

      現在回到parse_config函數,先看一下T_TEXT分支。該分支將獲得的每一行的token都存儲在args數組中。現在來看 T_NEWLINE分支。該分支的代碼涉及到一個state.parse_line函數指針,該函數指針指向的函數負責具體的分析工作。但我們發現,一看 是該函數指針指向了一個空函數parse_line_no_op,實際上,一開始該函數指針什麼都不做,只是爲了使該函數一開始不至於爲null,否則調 用出錯。

     現在來回顧一下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;  
複製代碼

      在上面的代碼中首先調用了lookup_keyword方法搜索關鍵字。該方法的作用是判斷當前行是否合法,也就是根據Init初始化語言預定義的關鍵字 查詢,如果未查到,返回K_UNKNOWN。lookup_keyword方法在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 '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;  
    }  
複製代碼

      lookup_keyword方法按26個字母順序(關鍵字首字母)進行處理。

     現在回到parse_config方法的T_NEWLIEN分支,接下來調用了kw_is宏具體判斷當前行是否合法,該宏以及SECTION宏的定義如下。根據這些代碼。明顯是keyword_info數組中的某個元素的flags成員變量的值取最後一位。

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

現在問題又轉到keyword_info數組了。該數組也在init_parser.c文件中定義,代碼如下:

複製代碼
    #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"  
    };  
複製代碼

       從表面上看,keyword_info數組是一個struct數組,但本質上,是一個map。爲每一個數組元素設置了一個key,例如,數組元素{ "unknown", 0, 0,0 }的key是K_UNKNOWN,而#include “keywords.h”大有玄機。上面的代碼中引用了兩次keywords.h文件,現在可以看一下keywords.h文件的代碼。

複製代碼
    #ifndef KEYWORD  
    int do_chroot(int nargs, char **args);  
    … …  
    int do_export(int nargs, char **args);  
    int do_hostname(int nargs, char **args);  
    int do_rmdir(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__  
    /* 
    "K_chdir", 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(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  
複製代碼

      從keywords.h文件的代碼可以看出,如果未定義KEYWORD宏,則在keywords.h文件中定義一個KEYWORD宏,以及一個枚舉類型, 其中K_##symbol的##表示連接的意思。而這個KEYWORD宏只用了第一個參數(symbol)。例 如,KEYWORD(chdir,       COMMAND, 1, do_chdir)就會生成K_chdir。

     而在keyword_info結構體數組中再次導入keywords.h文件,這是KEYWORD宏已經在init_parser.c文件中重新定義,所以第一次導入keywords.h文件使用的是如下的宏。

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

    這下就明白了,如果不使用keywords.h文件,直接將所有的代碼都寫到init_parser.c文件中,就會有下面的代碼。

複製代碼
    int do_chroot(int nargs, char **args);  
    … …  
    enum  
    {  
    K_UNKNOWN,  
    K_ capability,  
    K_ chdir,  
    … …  
    }  
    #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 },  
        [K_ capability] = {" capability ", 0, 1, OPTION },  
        [K_ chdir] = {"chdir", do_chdir ,2, COMMAND},  
        … …  
    #include "keywords.h"  
    };  
複製代碼

  可能我們還記着lookup_keyword方法,該方法的返回值就是keyword_info數組的key。

      在keywords.h前面定義的函數指針都是處理init.rc文件中service、action和command的。現在就剩下一個問題了,在哪裏 爲這些函數指針賦值呢,也就是說,具體處理每個部分的函數在哪裏呢。現在回到前面的語法分析部分。如果當前行合法,則會執行 parse_new_section函數(在init_parser.c文件中實現),該函數將爲section和action設置處理這兩部分的函數。 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) {  
        case K_service:  //  處理service  
            state->context = parse_service(state, nargs, args);  
            if (state->context) {  
                state->parse_line = parse_line_service;  
                return;  
            }  
            break;  
        case K_on:  //  處理action  
            state->context = parse_action(state, nargs, args);  
            if (state->context) {  
                state->parse_line = parse_line_action;  
                return;  
            }  
            break;  
        case K_import:   //  單獨處理import導入的初始化文件。  
            parse_import(state, nargs, args);  
            break;  
        }  
        state->parse_line = parse_line_no_op;  
    }  
複製代碼

現在看一下處理service的函數(parse_line_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]);  
        //  下面處理每一個option  
        switch (kw) {  
        case K_capability:  
            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;  
            … …  
         }  
        ……  
    }  
複製代碼

  Action的處理方式與service類似,讀者可以自行查看相應的函數代碼。現在一切都清楚了。處理service的函數是 parse_line_service,處理action的函數是parse_line_action。而前面的state.parse_line根據當 前是service還是action,指向這兩個處理函數中的一個,並執行相應的函數處理actioncommand和serviceoption。    

    綜合上述,實際上分析init.rc文件的過程就是通過一系列地處理,最終轉換爲通過parse_line_service或parse_line_action函數分析Init.rc文件中每一行的行爲。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章