Android6.0系統啟動流程分析一:init程序
到了Android6.0,Init程序使用c++來寫了,不過沒有關係,它和c寫的init沒有太大的區別。
Init程序的入口程式碼是:system\core\init\init.cpp
main函式:
int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "ueventd")) {
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
return watchdogd_main(argc, argv);
}
// Clear the umask.
umask(0);
add_environment("PATH", _PATH_DEFPATH);
bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
// Get the basic filesystem setup we need put together in the initramdisk
// on / and then we'll let the rc file figure out the rest.
if (is_first_stage) {
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL);
}
// We must have some place other than / to create the device nodes for
// kmsg and null, otherwise we won't be able to remount / read-only
// later on. Now that tmpfs is mounted on /dev, we can actually talk
// to the outside world.
open_devnull_stdio();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
property_init();
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
process_kernel_cmdline();
// Propogate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
}
// Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
selinux_initialize(is_first_stage);
// If we're in the kernel domain, re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (is_first_stage) {
if (restorecon("/init") == -1) {
ERROR("restorecon failed: %s\n", strerror(errno));
security_failure();
}
char* path = argv[0];
char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
if (execv(path, args) == -1) {
ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
security_failure();
}
}
// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("Running restorecon...\n");
restorecon("/dev");
restorecon("/dev/socket");
restorecon("/dev/__properties__");
restorecon_recursive("/sys");
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
ERROR("epoll_create1 failed: %s\n", strerror(errno));
exit(1);
}
signal_handler_init();
property_load_boot_defaults();
start_property_service();
init_parse_config_file("/init.rc");
action_for_each_trigger("early-init", action_add_queue_tail);
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
action_for_each_trigger("init", action_add_queue_tail);
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
char bootmode[PROP_VALUE_MAX];
if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail);
} else {
action_for_each_trigger("late-init", action_add_queue_tail);
}
// Run all property triggers based on current state of the properties.
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
while (true) {
if (!waiting_for_exec) {
execute_one_command();
restart_processes();
}
int timeout = -1;
if (process_needs_restart) {
timeout = (process_needs_restart - gettime()) * 1000;
if (timeout < 0)
timeout = 0;
}
if (!action_queue_empty() || cur_action) {
timeout = 0;
}
bootchart_sample(&timeout);
epoll_event ev;
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
if (nr == -1) {
ERROR("epoll_wait failed: %s\n", strerror(errno));
} else if (nr == 1) {
((void (*)()) ev.data.ptr)();
}
}
return 0;
}
1.這個函式是否往下執行取決於傳入的引數,如果第0個引數的basename為ueventd,則執行ueventd_main(argc, argv);如果basename為watchdogd_main,則執行watchdogd_main(argc, argv);只有basename不為這二者時,才會繼續往下執行。
2.如果argv[1]不為”–second-stage”或者只有一個引數的話,那麼is_first_stage就為true,就會建立/dev/pts和”/dev/socket”兩個裝置檔案節點,並掛載一個檔案系統。可以看出來init程序分兩個階段,不同的階段有不同的行為。具體的內涵鄙人還沒搞明白。
3.啟動屬性服務。建立一個socket,並在之後的死迴圈中監聽這個socket返回的檔案描述符。
3.解析init.rc。這個過程也是我最感興趣的,也是最重要的複雜的。
4.對各個階段的action排序。
5.進入死迴圈。
6.第一次進入死迴圈後,action_queue裡面有很多時間,因此需要不斷呼叫execute_one_command來執行命令。此時,action_queue_empty為假,timeout 為0,init執行緒不會在epoll_wait方法中休眠,因為設定的timeout=0哦,這一點曾一度困擾了我。
7.所有的命令執行完後,init程序進入休眠,監聽property_set_fd和signal_read_fd兩個檔案描述符,一點他們有事件過來,立刻被喚醒,進而做事件處理。
init.rc梳理
在我們分析init.rc的解析過程之前,我們還需要先對init.rc有個基本的認識。
先看一張我根據理解繪製的圖:
從圖來看,init.rc主要有section組成,section由on,import,section三個關鍵字標示。其中on標示的section叫做action。
import就不用說了,和c語言中的include功能有點類似。
service格式如下
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
action後面會跟一個觸發器,然後另起一行開始放置命令(command),格式如下:
on <trigger>
<command>
<command>
<command>
跟在service後面的是option,跟在action後面的是command.command都會對應一個處理函式,定義在keywords.h中:
...
KEYWORD(loglevel, COMMAND, 1, do_loglevel)
KEYWORD(mkdir, COMMAND, 1, do_mkdir)
KEYWORD(mount_all, COMMAND, 1, do_mount_all)
KEYWORD(mount, COMMAND, 3, do_mount)
...
命名也是很有規則的。比如mkdir,對應的函式就是do_mkdir。我們看看do_mkdir做了什麼:
int do_mkdir(int nargs, char **args)
{
mode_t mode = 0755;
int ret;
/* mkdir <path> [mode] [owner] [group] */
if (nargs >= 3) {
mode = strtoul(args[2], 0, 8);
}
ret = make_dir(args[1], mode);
/* chmod in case the directory already exists */
if (ret == -1 && errno == EEXIST) {
ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
}
if (ret == -1) {
return -errno;
}
if (nargs >= 4) {
uid_t uid = decode_uid(args[3]);
gid_t gid = -1;
if (nargs == 5) {
gid = decode_uid(args[4]);
}
if (lchown(args[1], uid, gid) == -1) {
return -errno;
}
/* chown may have cleared S_ISUID and S_ISGID, chmod again */
if (mode & (S_ISUID | S_ISGID)) {
ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
if (ret == -1) {
return -errno;
}
}
}
return e4crypt_set_directory_policy(args[1]);
}
其實就是呼叫了make_dir並做了一些許可權等方面的操作。所以,跟在action後面的命令並不能隨隨便便亂加,而是要確保這個命令被定義了,不然就會出錯。
init.rc的解析過程(以import為例)
因為init.rc的第一行程式碼就是Import語句。萬事開頭難,只要我們理清了第一行的解析過程,後面行的解析分析起來就不怎麼費勁了。所以下面我們主要看看init.rc中第一行的解析過程。
init.tc的解析函式為:init_parse_config_file
int init_parse_config_file(const char* path) {
INFO("Parsing %s...\n", path);
Timer t;
std::string data;
if (!read_file(path, &data)) {
return -1;
}
data.push_back('\n'); // TODO: fix parse_config.
parse_config(path, data);
dump_parser_state();
// MStar Android Patch Begin
INFO("(Parsing %s took %.2fs.)\n", path, t.duration());
// MStar Android Patch End
return 0;
}
這個函式把/init.rc中的內容讀出來,並讓data這個string型別的變數指向它。
把讀出來的data傳遞給parse_config函式做真正的解析工作。parse_config函式如下:
static void parse_config(const char *fn, const std::string& data)
{
char *args[UEVENTD_PARSER_MAXARGS];
int nargs = 0;
parse_state state;
state.filename = fn;
state.line = 1;
state.ptr = strdup(data.c_str()); // TODO: fix this code!
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
for (;;) {
int token = next_token(&state);
switch (token) {
case T_EOF:
parse_line(&state, args, nargs);
return;
case T_NEWLINE:
if (nargs) {
parse_line(&state, args, nargs);
nargs = 0;
}
state.line++;
break;
case T_TEXT:
if (nargs < UEVENTD_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
}
我看到這個函式的時候,我想起了xml解析方法之一的pull解析,感覺挺像的。每次迴圈都會找到一個token,token就是一個特定的符號,然後根據這個toke做不同的處理。這裡使用到了parse_state結構,啟動以如下:
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;
};
這個就夠中:ptr執行init.rc字元流的,text後面會用到,用來儲存引數,line當然就是行數了,nexttoken儲存下一個token,filename儲存init.rc的檔案描述符,filename當然是/init.rc了.parse_line是一個函式指標。context暫時沒明白…state.priv 指向Import的一個檔案連結串列。
我們開啟Init.rc看看,從頭分析它的解析過程。
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
...
init.rc前面幾行都是import語句,我們看看一開始的解析流程。
這個時候,parse_satate的狀態為:
state.filename = fn;
state.line = 1;
state.ptr = strdup(data.c_str()); // TODO: fix this code!
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
list_init(&import_list);
state.priv = &import_list;
step 1.第一次迴圈
然後進入死迴圈,第一次呼叫next_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;
}
這時候,init.rc中的第一個符號應該是i(impor,省去空格),所以next_token直接進入到text:標籤執行,執行的結果是state->text = s = ‘i’;然後繼續執行textresume:標籤後面的內容:
標籤後面的for死迴圈中,發現第一個字元是i,於是執行default分支:*s++ = *x++;這樣直到import的t被檢測完以後,在下一次迴圈變得到一個空格,於是執行x++,並goto textdone.。textdown執行完後函式返回,返回後,state->ptr 指向’/’符號 。*s = 0;意味著state.text就是字串“import”,因為0就是字串結束符了。注意返回值為T_TEXT。這個時候執行parse_config函式中的case T_TEXT:分支。
case T_TEXT:
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
這個時候,nargs為0,state.text位import,於是args陣列的第0項就存了”import”字串了。然後nargs++,也就是等於1了。然後進入下次迴圈。
step 2.第二次迴圈
第二次迴圈再次呼叫next_token函式,這次state->ptr=’\’,這我們分析過了。因此next_token函式不斷執行defaulty分支,最終state.text = “/init.environ.rc”,返回型別還是T_TEXT。於是和之前一樣,args[1]=”/init.environ.rc”,nargs=2。
step 3.第三次迴圈
這個時候一行結束,parse_config函式進入case T_NEWLINE:分支。
這個分支中,首先執行lookup_keyword函式,從名字來看是查詢關鍵字。肯定就是import了,它肯定是關鍵字。不信請看程式碼:
static int lookup_keyword(const char *s)
{
switch (*s++) {
case 'b':
if (!strcmp(s, "ootchart_init")) return K_bootchart_init;
break;
case 'c':
if (!strcmp(s, "opy")) return K_copy;
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 'e':
if (!strcmp(s, "nable")) return K_enable;
if (!strcmp(s, "xec")) return K_exec;
if (!strcmp(s, "xport")) return K_export;
break;
case 'g':
if (!strcmp(s, "roup")) return K_group;
break;
case 'h':
if (!strcmp(s, "ostname")) return K_hostname;
break;
case 'i':
if (!strcmp(s, "oprio")) return K_ioprio;
if (!strcmp(s, "fup")) return K_ifup;
if (!strcmp(s, "nsmod")) return K_insmod;
if (!strcmp(s, "mport")) return K_import;
if (!strcmp(s, "nstallkey")) return K_installkey;
break;
case 'k':
if (!strcmp(s, "eycodes")) return K_keycodes;
break;
case 'l':
if (!strcmp(s, "oglevel")) return K_loglevel;
if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;
if (!strcmp(s, "oad_all_props")) return K_load_all_props;
break;
case 'm':
if (!strcmp(s, "kdir")) return K_mkdir;
if (!strcmp(s, "ount_all")) return K_mount_all;
if (!strcmp(s, "ount")) return K_mount;
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 'p':
if (!strcmp(s, "owerctl")) return K_powerctl;
break;
case 'r':
if (!strcmp(s, "estart")) return K_restart;
if (!strcmp(s, "estorecon")) return K_restorecon;
if (!strcmp(s, "estorecon_recursive")) return K_restorecon_recursive;
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, "etenv")) return K_setenv;
if (!strcmp(s, "etprop")) return K_setprop;
if (!strcmp(s, "etrlimit")) return K_setrlimit;
if (!strcmp(s, "ocket")) return K_socket;
if (!strcmp(s, "tart")) return K_start;
if (!strcmp(s, "top")) return K_stop;
if (!strcmp(s, "wapon_all")) return K_swapon_all;
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 'v':
if (!strcmp(s, "erity_load_state")) return K_verity_load_state;
if (!strcmp(s, "erity_update_state")) return K_verity_update_state;
break;
case 'w':
if (!strcmp(s, "rite")) return K_write;
if (!strcmp(s, "ritepid")) return K_writepid;
if (!strcmp(s, "ait")) return K_wait;
break;
}
return K_UNKNOWN;
}
呼叫這個函式的時候,我們傳入的引數args[0]=”import”.顯而易見該函式返回K_import。它是一個整數。返回以後使用kw_is函式看他是不是一個Section。當然是一個section了,import也是一個section。不信看程式碼:
#define kw_is(kw, type) (keyword_info[kw].flags & (type))
keyword_info定義在system/core/init/keywords.h中:
...
KEYWORD(group, OPTION, 0, 0)
KEYWORD(hostname, COMMAND, 1, do_hostname)
KEYWORD(ifup, COMMAND, 1, do_ifup)
KEYWORD(import, SECTION, 1, 0)
...
擷取含有import的一部分程式碼,後面SECTION已經表明它是個Section了。KEYWORD自後一個引數是這個關鍵字對應的處理函式。比如這其中的hostname。如果你在init.rc中使用hostname 關鍵字,那麼最終會呼叫do_hostname函式來處理。
既然import是一個section。那麼parce_config就會呼叫state.parse_line函式,這裡是一個函式指標,其實呼叫的是parse_line_no_op,不記得回去看下state的初始就知道了。
static void parse_line_no_op(struct parse_state*, int, char**) {
}
這個函式是空的。接下來呼叫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:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
case K_on:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
case K_import:
parse_import(state, nargs, args);
break;
}
state->parse_line = parse_line_no_op;
}
我們當然是執行case K_import:分支了,想都不用想。所以接下來執行parse_import方法:
static void parse_import(struct parse_state *state, int nargs, char **args)
{
struct listnode *import_list = (listnode*) state->priv;
char conf_file[PATH_MAX];
int ret;
if (nargs != 2) {
ERROR("single argument needed for import\n");
return;
}
ret = expand_props(conf_file, args[1], sizeof(conf_file));
if (ret) {
ERROR("error while handling import on line '%d' in '%s'\n",
state->line, state->filename);
return;
}
struct import* import = (struct import*) calloc(1, sizeof(struct import));
import->filename = strdup(conf_file);
list_add_tail(import_list, &import->list);
INFO("Added '%s' to import list\n", import->filename);
}
這個函式首先使用expand_props方法對args[1]也就是“/init.environ.rc”做進一步處理。這個函式如下:
int expand_props(char *dst, const char *src, int dst_size)
{
char *dst_ptr = dst;
const char *src_ptr = src;
int ret = 0;
int left = dst_size - 1;
if (!src || !dst || dst_size == 0)
return -1;
/* - variables can either be $x.y or ${x.y}, in case they are only part
* of the string.
* - will accept $$ as a literal $.
* - no nested property expansion, i.e. ${foo.${bar}} is not supported,
* bad things will happen
*/
while (*src_ptr && left > 0) {
char *c;
char prop[PROP_NAME_MAX + 1];
char prop_val[PROP_VALUE_MAX];
int prop_len = 0;
int prop_val_len;
c = strchr(src_ptr, '$');
if (!c) {
while (left-- > 0 && *src_ptr)
*(dst_ptr++) = *(src_ptr++);
break;
}
...
可以看出,這個函式的作用是拓展args[1].這裡不需要拓展,因為我們的args[1]=”/init.environ.rc”沒有$符號,所以直接就跳出迴圈了。這裡應該是對那些有包含變數的字串,把變數的內容展開。
然後構建了一個import結構體。import中的filename項賦值為”/init.environ.rc”.並把它加入到import_list連結串列中。
import定義如下:
struct import {
struct listnode list;
const char *filename;
};
這樣,第一行就分析完了,從而我們也徹底明白了怎麼解析一個import 的section。
只要能看懂一個,其他的就簡單了。因為,他們都是類似的。
service的解析與啟動
service的解析
和import解析過程類似,遇到service關鍵字後,service關鍵字和後面的引數會儲存在args[]陣列中。然後通過對args[0]提取關鍵字,發現args[0]=”service”,於是開始執行parse_new_section函式。此時這個函式必然會進入 case K_service:分支執行:
case K_service:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
這裡做了兩件事情非常重要,一件是呼叫parse_service解析service這個section。另一件事情是給state->parse_line賦值為parse_line_service。也就是service 關鍵字所在的行後面的那些options行都是使用parse_line_service函式來解析的。我們從parse_service看起:
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
if (nargs < 3) {
parse_error(state, "services must have a name and a program\n");
return 0;
}
if (!valid_name(args[1])) {
parse_error(state, "invalid service name '%s'\n", args[1]);
return 0;
}
service* svc = (service*) service_find_by_name(args[1]);
if (svc) {
parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
return 0;
}
nargs -= 2;
svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
if (!svc) {
parse_error(state, "out of memory\n");
return 0;
}
svc->name = strdup(args[1]);
svc->classname = "default";
memcpy(svc->args, args + 2, sizeof(char*) * nargs);
trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
svc->args[nargs] = 0;
svc->nargs = nargs;
list_init(&svc->onrestart.triggers);
cur_trigger->name = "onrestart";
list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist);
list_init(&svc->onrestart.commands);
list_add_tail(&service_list, &svc->slist);
return svc;
}
可以看到和import做的事情差不多。import解析的最後,會建立一個import結構體,並把它新增到import_list雙向連結串列中。service解析從這裡看,也是構建一個service結構體,然後把service結構體新增到service_list連結串列中。
我們看下service結構體:
struct service {
void NotifyStateChange(const char* new_state);
/* list of all services */
struct listnode slist;
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;
const char* seclabel;
struct socketinfo *sockets;
struct svcenvinfo *envvars;
struct action onrestart; /* Actions to execute on restart. */
std::vector<std::string>* writepid_files_;
/* keycodes for triggering this service via /dev/keychord */
int *keycodes;
int nkeycodes;
int keychord_id;
IoSchedClass ioprio_class;
int ioprio_pri;
int nargs;
/* "MUST BE AT THE END OF THE STRUCT" */
char *args[1];
}; /*
socketinfo 用來儲存socket option的相關資訊。
classname 給service定義一個類名,如果多個service使用相同的型別,可以方便進行批量操作。
nargs 儲存引數的個數。
很多欄位不理解,沒關係,我們看看parse_service函式給service做了那些初始化:
1. svc->name = strdup(args[1]);名字就是service 關鍵字後面的第一個引數
2. svc->classname = “default”; 類別名是default
3. memcpy(svc->args, args + 2, sizeof(char*) * nargs); svc->args[nargs] = 0;
把所有引數儲存在args陣列中,並把最有一個成員只為0。
4. svc->nargs = nargs; nargs儲存引數個數
5. trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
list_init(&svc->onrestart.triggers);
cur_trigger->name = “onrestart”;
list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist);
構建一個觸發器,並把它新增到service中的onrestart.triger列表中。
因此,我們可以知道麼一個service都會有一個onrestart的action,這個action有一個觸發器。這個action用來重啟service。
解析完service後,就會解析service後面的option了,這個時候會呼叫parse_line_service。
static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
struct service *svc = (service*) state->context;
struct command *cmd;
int i, kw, kw_nargs;
if (nargs == 0) {
return;
}
svc->ioprio_class = IoSchedClass_NONE;
kw = lookup_keyword(args[0]);
switch (kw) {
case K_class:
if (nargs != 2) {
parse_error(state, "class option requires a classname\n");
} else {
svc->classname = args[1];
}
break;
case K_console:
svc->flags |= SVC_CONSOLE;
break;
case K_disabled:
svc->flags |= SVC_DISABLED;
svc->flags |= SVC_RC_DISABLED;
break;
case K_ioprio:
if (nargs != 3) {
parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");
} else {
svc->ioprio_pri = strtoul(args[2], 0, 8);
if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {
parse_error(state, "priority value must be range 0 - 7\n");
break;
}
if (!strcmp(args[1], "rt")) {
svc->ioprio_class = IoSchedClass_RT;
} else if (!strcmp(args[1], "be")) {
svc->ioprio_class = IoSchedClass_BE;
} else if (!strcmp(args[1], "idle")) {
svc->ioprio_class = IoSchedClass_IDLE;
} else {
parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");
}
}
break;
case K_group:
if (nargs < 2) {
parse_error(state, "group option requires a group id