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的實際執行過程也會改變,解析方式非常的靈活。。