Android讀取init.rc配置文件parse_config函數解析

Android源代碼版本:4.0.3

static void parse_config(const char *fn, char *s)函數在Android的init程序啓動過程中用於解析init.rc文件。init.rc文件是安卓系統的初始化文件,其中的內容可以分爲三大類:

1. Action:一個action表示一個動作,以關鍵字on作爲開頭,並加上action的名稱,接下來的是對應於這個action的各種command,而command就是一些基本點linux命令。一個action可以包含有多個command,每個command被獨立的執行。

init.rc文件中一條action的格式如下:


on early-init
    write /proc/1/oom_adj -16
    setcon u:r:init:s0
    start ueventd
    setsebool debugfs 1
上述信息配置了一個名稱爲early-init的action,包含了4條command。在安卓源碼中,action的對應的結構體如下:


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;	/* action名稱 */
    
    struct listnode commands;/* 所有command組成的鏈表 */
    struct command *current;/* 當前執行的command,方便定位 */
};

2. Service:一個service代表一個服務,以service作爲關鍵字開頭,代表一個守護進程,例如zygote、sysmon、servicemanager等都會在init.rc文件中配置。

init.rc中的一條service的格式如下:


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
    onrestart restart sensorhubservice
    onrestart restart bootchecker
    onrestart restart gsiff_daemon
第一行表示要啓動一個名稱爲zygote的service,相應的執行命令爲“/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server”,一個service至少包含三個部分(service + name + path),參數項不是必須的。

安卓源碼中的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;

    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! */
所以根據上面的init.rc中的service記錄,系統會創建一個名稱爲zygote的service,並根據內容設置他的name、classname、sockets以及onrestart變量。


3. import:一個import等於包含一個新文件,以import關鍵字區分,類似於c中的include。

init.rc中的import格式如下所示:


import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.trace.rc
import /init.carrier.rc
import /init.container.rc


在init程序中,首先會調用init_parse_config_file函數載入並解析init.rc文件,該函數首先通過read_file將文件載入內存,並在文件尾部增加一個換行符和休止符,方便解析過程。解析函數parse_config是本文的重點。

parse_config:


static void parse_config(const char *fn, char *s)    /*在上面操作中已經將文件讀取到了data中*/
{ 
    struct parse_state state; 						/* 定義一個解析狀態變量 */
    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; 			/*函數指針,指向當前解析類型對應的解析函數,默認是空操作*/
    for (;;) { 
        switch (next_token(&state)) {               /*判斷下一個最小解析單元類型,這個單元可以是一個換行、結束符、一個單詞等*/

        case T_EOF:                                            
			/*如果讀到了文件底部返回*/
            state.parse_line(&state, 0, 0);            /*解析最後一行數據*/
            return; 
			
		/* newline表示是新的一行,有兩種情況會返回T_NEWLINE
		 * 上一行有效數據已經讀完了,但是還未解析,下一次調用next_tokern會返回T_NEWLINE
		 * 讀到了換行符,會直接返回T_NEWLINE */
	case T_NEWLINE:                                   
            state.line++; 
            if (nargs) { 
                /* nargs表示還未解析的那一行的單詞個數,如果nargs大於0,表示還有有效數據需要解析 */
				
				/* 根據每一行開頭單詞判斷本行類型 */
                int kw = lookup_keyword(args[0]);

				/* 如果起始單詞是on、import、service的話,說明是一個新的section */
				
                if (kw_is(kw, SECTION)) { 
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args); 

                } else { 
                    /* 這行是一個子語句,此時的函數指針被指向相應的處理函數。
					 * service對應parse_line_service,
					 * action對應parse_line_action,
					 * 其它的是空操作*/
                    state.parse_line(&state, nargs, args); 
                } 
                nargs = 0; 
            } 
            break; 
        case T_TEXT:                                  
			/*每一行有效數據會存儲在args中,每一個單詞佔一個位置,nargs表示目前讀取的這一行數據讀了幾個單詞*/
            if (nargs < INIT_PARSER_MAXARGS) { 
                args[nargs++] = state.text; 
            } 
            break; 
        } 
    } 
}

首先定義一個狀態結構體用來保存當前的解析狀態,包括當前解析到哪裏(ptr)以及當前解析的是屬於action還是service(parse_line,不同的類別函數指針不同)等。然後循環調用next_token函數從文件中嘗試獲取下一個處理單元,一個處理單元可以是:新的一行,文件末尾以及一行中的一個單詞。根據返回值的不同類型執行不同的操作。

1. 如果是EOF:表示讀取到了文件尾部,將最後一行解析完畢後返回。

2. 如果是一個TEXT:表示讀取到了一個有效單詞,但是本行還未讀完,由於是按照行來解析數據,將其存入args數組中並將計數器nargs加1。

3. 如果是NEXLINE:表示是一個新的行,分兩種情況:

a). 在讀取行時還沒有讀到單詞就遇到了換行符,會直接返回T_NEWLINE,這時nargs等於0。沒有有效數據,直接進行下一次循環。

b). 如果正在讀取有效數據(已經讀取了一些單詞),遇到了換行符,會將state的nexttoken成員設置完T_NEWLINE,下一次調用的時候會直接返回T_NEWLINE。這種情況下計數器nargs是大於1的。然後調用lookup_keyword,並將本行的第一個單詞作爲參數傳遞。lookup_keyword根據參數判斷本行數據的標籤內容,如果是import、on、service的話,表示這是一個新的section,需要在parse_config中調用parse_new_section。最後將計數器nargs置爲0爲解析下一行做準備。

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) {
	
	/* 是一條service */
    case K_service:
        /* 通過調用service_find_by_name函數從service_list中根據名稱查找
		 * 是否有對應的struct service,不能打開相同的service,如果沒有找
		 * 到,通過調用calloc分配內存,初始化struct service參數,然後調
		 * 用list_add_tail將該service加入service_list循環雙鏈表中,最後返回
		 * 新建的service */
        state->context = parse_service(state, nargs, args);
        if (state->context) {
            /* 不爲空表示是新建的service,將state的函數指針變換爲parse_line_service,
			 * 從這一行開始的語句將會用service函數進行解析 */
            state->parse_line = parse_line_service;
            return;
        }
        break;
		
    /* 是一條action */
    case K_on:
        /*類似的將相應的action添加到action_list*/
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            /*action不爲空的話將state的函數指針變爲parse_line_action,接下來是解析action子語句,即command*/
            state->parse_line = parse_line_action;
            return;
        }
        break;
    case K_import:
        if (nargs != 2) {
            ERROR("single argument needed for import\n");
        } else {
            int ret = init_parse_config_file(args[1]);/*import的是類似於init.environ.rc的文件,也調用read_file和parse_config將其加載*/
            if (ret)
                ERROR("could not import file %s\n", args[1]);
        }
    }
    state->parse_line = parse_line_no_op;
}
可以看到在parse_new_section函數中不僅會新建service、action,也會把state的函數指針parse_line指向相應的解析函數。在parse_config函數中調用parse_line的實際執行過程也會改變,解析方式非常的靈活。。





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