Android情景分析之詳解init程序(以啟動zygote為例)
概述
init是linux系統中使用者空間的第一個程序。由於Android是基於linux核心的,所以init也是Android系統中使用者空間的第一個程序,它的程序號為1。
作為系統中的第一個使用者空間程序,init程序被賦予了很多及其重要的工作職責。
1. init程序建立系統中幾個關鍵程序,例如zygote等。
今天我們主要以第1點來詳細說明下init程序的工作。
init程序的main函式
init程序的主要程式碼如下:
int main(int argc, char **argv) { int fd_count = 0; struct pollfd ufds[4]; char *tmpdev; char* debuggable; char tmp[32]; int property_set_fd_init = 0; int signal_fd_init = 0; int keychord_fd_init = 0; bool is_charger = false; ... mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); 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); ... // 屬性服務初始化 property_init(); get_hardware_name(hardware, &revision); process_kernel_cmdline(); // selinux安全機制初始化 union selinux_callback cb; cb.func_log = klog_write; selinux_set_callback(SELINUX_CB_LOG, cb); cb.func_audit = audit_callback; selinux_set_callback(SELINUX_CB_AUDIT, cb); selinux_initialize(); /* 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. */ restorecon("/dev"); restorecon("/dev/socket"); restorecon("/dev/__properties__"); restorecon_recursive("/sys"); is_charger = !strcmp(bootmode, "charger"); INFO("property init\n"); if (!is_charger) property_load_boot_defaults(); // 解析init.rc配置檔案 INFO("reading config file\n"); init_parse_config_file("/init.rc"); /* 解析完init.rc配置檔案後,會得到一系列的Action,action_for_each_trigger函式用來執行early-init階段的Action。 init將動作執行的時間劃分為4個階段:early-init、init、early-boot、boot。由於有些動作必須要在其他動作完成後才能執行,所以就有了先後之分。哪些動作屬於哪個階段由配置檔案決定。 */ action_for_each_trigger("early-init", action_add_queue_tail); queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); 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"); /* execute all the boot actions to get us started */ // 執行init階段的動作 action_for_each_trigger("init", action_add_queue_tail); /* skip mounting filesystems in charger mode */ if (!is_charger) { action_for_each_trigger("early-fs", action_add_queue_tail); action_for_each_trigger("fs", action_add_queue_tail); action_for_each_trigger("post-fs", action_add_queue_tail); action_for_each_trigger("post-fs-data", 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"); queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(check_startup_action, "check_startup"); // 執行early-boot和boot階段的動作 if (is_charger) { action_for_each_trigger("charger", action_add_queue_tail); } else { action_for_each_trigger("early-boot", action_add_queue_tail); action_for_each_trigger("boot", 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"); #if BOOTCHART queue_builtin_action(bootchart_init_action, "bootchart_init"); #endif // 無限迴圈,用來處理各種訊息 for(;;) { int nr, i, timeout = -1; execute_one_command(); restart_processes(); // 重啟那些已經死去的程序 // 用來監聽屬性設定服務的事件 if (!property_set_fd_init && get_property_set_fd() > 0) { ufds[fd_count].fd = get_property_set_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; property_set_fd_init = 1; } if (!signal_fd_init && get_signal_fd() > 0) { ufds[fd_count].fd = get_signal_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; signal_fd_init = 1; } if (!keychord_fd_init && get_keychord_fd() > 0) { ufds[fd_count].fd = get_keychord_fd(); ufds[fd_count].events = POLLIN; ufds[fd_count].revents = 0; fd_count++; keychord_fd_init = 1; } if (process_needs_restart) { timeout = (process_needs_restart - gettime()) * 1000; if (timeout < 0) timeout = 0; } if (!action_queue_empty() || cur_action) timeout = 0; #if BOOTCHART if (bootchart_count > 0) { if (timeout < 0 || timeout > BOOTCHART_POLLING_MS) timeout = BOOTCHART_POLLING_MS; if (bootchart_step() < 0 || --bootchart_count == 0) { bootchart_finish(); bootchart_count = 0; } } #endif nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; // 處理具體的訊息 for (i = 0; i < fd_count; i++) { if (ufds[i].revents == POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } } return 0; }
從以上程式碼來看,init程序的工作量還是很大的,主要集中在如下幾個事情:
1. 解析init.rc配置檔案。
2. 執行各個階段的動作,建立zygote的工作就是在其中的某個階段完成的。
3. 初始化property service(屬性服務)。
4. init進入一個無限迴圈,並且等到一些事情的發生。
解析init.rc
init的main函式主要是解析了init.rc檔案,然後執行相應的初始化操作。我們先來看看init_parse_config_file函式的實現:
int init_parse_config_file(const char *fn) { char *data; data = read_file(fn, 0); // 讀取inic.rc檔案的內容 if (!data) return -1; // 開始解析 parse_config(fn, data); DUMP(); return 0; } 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; // 設定解析函式,在android4.1上此函式為空 list_init(&import_list); state.priv = &import_list; // 開始解析init.rc檔案內容,以行為單位 for (;;) { switch (next_token(&state)) { case T_EOF: // 檔案末尾 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: // 文字內容,設定為args引數 if (nargs < INIT_PARSER_MAXARGS) { args[nargs++] = state.text; } break; } } parser_done: 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); } }
再來看看keywords.h對init.rc檔案中關鍵字的定義
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(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) KEYWORD(restorecon, COMMAND, 1, do_restorecon) KEYWORD(rm, COMMAND, 1, do_rm) KEYWORD(rmdir, COMMAND, 1, do_rmdir) KEYWORD(seclabel, OPTION, 0, 0) KEYWORD(service, SECTION, 0, 0) KEYWORD(setcon, COMMAND, 1, do_setcon) KEYWORD(setenforce, COMMAND, 1, do_setenforce) KEYWORD(setenv, OPTION, 2, 0) KEYWORD(setkey, COMMAND, 0, do_setkey) KEYWORD(setprop, COMMAND, 2, do_setprop) KEYWORD(setrlimit, COMMAND, 3, do_setrlimit) KEYWORD(setsebool, COMMAND, 2, do_setsebool) KEYWORD(socket, OPTION, 0, 0) KEYWORD(start, COMMAND, 1, do_start) KEYWORD(stop, COMMAND, 1, do_stop) KEYWORD(swapon_all, COMMAND, 1, do_swapon_all) KEYWORD(trigger, COMMAND, 1, do_trigger) KEYWORD(symlink, COMMAND, 1, do_symlink) KEYWORD(sysclktz, COMMAND, 1, do_sysclktz) KEYWORD(user, OPTION, 0, 0) KEYWORD(wait, COMMAND, 1, do_wait) KEYWORD(write, COMMAND, 2, do_write) KEYWORD(copy, COMMAND, 2, do_copy) KEYWORD(chown, COMMAND, 2, do_chown) KEYWORD(chmod, COMMAND, 2, do_chmod) KEYWORD(loglevel, COMMAND, 1, do_loglevel) KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props) KEYWORD(ioprio, OPTION, 0, 0)
Init.rc檔案的主要內容如下(system\core\rootdir\init.rc):
# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.trace.rc
# 根據上面的分析可知,on關鍵字標示一個section,對應的名字為early-init
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16
# Set the security context for the init process.
# This should occur before anything else (e.g. ueventd) is started.
setcon u:r:init:s0
start ueventd
# create mountpoints
mkdir /mnt 0775 root system
…
# 又一個新的section,名為boot
on boot
...
# class_start是一個COMMAND,對應處理函式是do_class_start
class_start core
class_start main
…
# system server cannot write to /proc/sys files, so proxy it through init
on property:sys.sysctl.extra_free_kbytes=*
write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
## Daemon processes to be run by init.
##
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
…
# 下面這個section的意思是,如果屬性ro.kernel.qemu為1,那麼執行相應的COMMAND ,# 即start adbd
on property:ro.kernel.qemu=1
start adbd
...
# service關鍵字也是一個SECTION,對應的SECTION名為zygote。
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
service drm /system/bin/drmserver
class main
user drm
group drm system inet drmrpc
...
從上面對init.rc檔案大致分析可知
1、 一個section的內容從這個標識section的關鍵字開始,到下一個標識section的地方結束。
2、 Init.rc中出現了名為early-init和boot的section,這裡的early-init和boot就是前面介紹的4個動作執行階段中的early-init和boot。也就是說在boot階段執行的動作都是由boot這個section定義的。
另外,zygote被放在了一個service section中,下面以zygote這個section為例,具體介紹下service是如何解析的。
解析zygote 服務
Zygote對應的service section內容如下:
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
解析section的入口函式是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:
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;
}
其中,解析service時,用到了parse_service和parse_line_service這兩個函式,在介紹這兩個函式之前,我們首先看看init是如何組織這個service的。
service結構體
struct service {
/*
用來連線所有的services。Init中有一個全域性的service_list變數,專門用來儲存解析配置檔案後得到的service。
*/
struct listnode slist;
const char *name; // service的名字,在我們的例子中為zygote
const char *classname; // service所屬的class名字,預設為default
unsigned flags; // service的屬性
pid_t pid; // 程序號
time_t time_started; /* 上一次啟動時間 */
time_t time_crashed; /* 上一次死亡時間*/
int nr_crashed; /* 死亡次數 */
uid_t uid;
gid_t gid;
gid_t supp_gids[NR_SVC_SUPP_GIDS];
size_t nr_supp_gids;
char *seclabel;
/*
有些service需要使用socket來通訊,下面這個socketinfo用來描述socket的相關資訊。
我們的zygote也使用了socket,配置檔案中的內容是socket zygote stream 660 root system。
它表示將建立一個AF_STREAM型別的socket(其實就是TCP socket),該socket的名為zygote,讀寫許可權是660。
*/
struct socketinfo *sockets;
// service一般執行在一個單獨的程序中,envvars用來描述建立這個程序時所需的環境
// 變數資訊。
struct svcenvinfo *envvars;
/*
雖然onrestart關鍵字是一個OPTION,可是這個OPTION後面一般都跟著一個COMMAND,action結構體就是用來儲存command資訊的。
*/
struct action onrestart; /* Actions to execute on restart. */
/* keycodes for triggering this service via /dev/keychord */
int *keycodes;
int nkeycodes;
int keychord_id;
// io優先順序
int ioprio_class;
int ioprio_pri;
int nargs; // 引數個數
/* "MUST BE AT THE END OF THE STRUCT" */
char *args[1]; // 用於儲存引數內容
};
瞭解了service結構,那麼其中的action是怎麼儲存的呢,我們再來看看action結構體。
struct action {
/*
一個action結構可以被鏈入三個不同的雙向連結串列中,其中alist用來儲存所有的action,
qlist用來連結那些等待執行的action,tlist用來連結那些待某些條件滿足後就需要執行的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; // 名字一般為"onrestart"
/*
一個command的結構列表,command結構如下:
struct command
{
/* list of commands in an action */
struct listnode clist;
int (*func)(int nargs, char **args);
int nargs;
char *args[1];
};
*/
struct listnode commands;
struct command *current;
};
解析parse_service
瞭解了關鍵的結構體之後,我們接下來看看parse_service函式的具體思路:
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
struct service *svc;
// ….省略引數檢測
// 根據服務名在全域性服務連結串列service_list裡面查詢,如果已經有了,則返回
svc = service_find_by_name(args[1]);
if (svc) {
parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
return 0;
}
// 分配service結構記憶體
nargs -= 2;
svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
if (!svc) {
parse_error(state, "out of memory\n");
return 0;
}
svc->name = args[1]; // 服務名,此處為zygote
svc->classname = "default"; // 設定classname為default,此處很關鍵
memcpy(svc->args, args + 2, sizeof(char*) * nargs); // 拷貝引數,此處拷貝的是字元指標
svc->args[nargs] = 0;
svc->nargs = nargs; // 儲存引數個數
svc->onrestart.name = "onrestart";
list_init(&svc->onrestart.commands); // 初始化action結構的commands連結串列
list_add_tail(&service_list, &svc->slist); // 將當前服務新增到service_list連結串列中
return svc;
}
由上面分析可知,parse_service函式只是搭建了一個svc的框架,並最後把它鏈入了service_list連結串列。我們再來看看parse_line_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]);
switch (kw) {
case K_capability:
break;
case K_class: // 用來修改classname
if (nargs != 2) {
parse_error(state, "class option requires a classname\n");
} else {
svc->classname = args[1];
}
break;
…
case K_oneshot:
svc->flags |= SVC_ONESHOT;
break;
case K_onrestart: // 開始解析onrestart後面的COMMAND
nargs--;
args++;
kw = lookup_keyword(args[0]);
if (!kw_is(kw, COMMAND)) {
parse_error(state, "invalid command '%s'\n", args[0]);
break;
}
kw_nargs = kw_nargs(kw);
if (nargs < kw_nargs) {
parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,
kw_nargs > 2 ? "arguments" : "argument");
break;
}
// 分配command結構的記憶體,拷貝引數等
cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
cmd->func = kw_func(kw);
cmd->nargs = nargs;
memcpy(cmd->args, args, sizeof(char*) * nargs);
// 新增到commands連結串列中
list_add_tail(&svc->onrestart.commands, &cmd->clist);
break;
…
case K_socket: {/* name type perm [ uid gid ] */
struct socketinfo *si;
if (nargs < 4) {
parse_error(state, "socket option requires name, type, perm arguments\n");
break;
}
if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")
&& strcmp(args[2],"seqpacket")) {
parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");
break;
}
si = calloc(1, sizeof(*si));
if (!si) {
parse_error(state, "out of memory\n");
break;
}
si->name = args[1]; // socket名字
si->type = args[2]; // socket型別
si->perm = strtoul(args[3], 0, 8); // socket讀寫許可權
if (nargs > 4)
si->uid = decode_uid(args[4]);
if (nargs > 5)
si->gid = decode_uid(args[5]);
si->next = svc->sockets;
svc->sockets = si;
break;
}
…
default:
parse_error(state, "invalid option '%s'\n", args[0]);
}
}
parse_line_service函式將根據配置檔案的內容填充service結構體,zygote service解析完之後的結果如下圖所示:
解析完之後:
1. 全域性的service_list連結串列將解析後的service全部連結到了一起。
2. socketinfo也是一個雙向連結串列,這裡連結了service用到的socket資訊,zygote程序只有一個socket。
3. onrestart通過commands指向一個commands連結串列,zygote共有4個commands。
init控制service
啟動zygote
init.rc的zygote服務中有這樣一句話:service zygote /system/bin/app_process-Xzygote /system/bin --zygote --start-system-server
class main
服務預設建立的時候classname為“default”,這裡通過“class main”來修改classname為“main”。之後,我們再回過頭去看init.rc中有這個一句話:
在init程序啟動的boot階段,有一句“class_start main”。而class_start是一個COMMAND,其對應的處理函式是do_class_start。接著來看看do_class_start函式吧:
int do_class_start(int nargs, char **args)
{
/*
Args為do_class_start的引數,init.rc中只有一個引數,就是“main“、“core”等。
service_for_each_class函式就是從service_list連結串列中找到classname和引數一致的service,
然後呼叫service_start_if_not_disabled函式。
*/
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
void service_for_each_class(const char *classname,
void (*func)(struct service *svc))
{
struct listnode *node;
struct service *svc;
list_for_each(node, &service_list) {
svc = node_to_item(node, struct service, slist);
if (!strcmp(svc->classname, classname)) {
func(svc);
}
}
}
service_start_if_not_disabled函式具體做了什麼呢?接著看:
static void service_start_if_not_disabled(struct service *svc)
{
// 如果flags不為SVC_DISABLED,那麼開始啟動服務
if (!(svc->flags & SVC_DISABLED)) {
service_start(svc, NULL);
}
}
具體來看看服務的啟動過程:
void service_start(struct service *svc, const char *dynamic_args)
{
struct stat s;
pid_t pid;
int needs_console;
int n;
char *scon = NULL;
int rc;
svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART));
svc->time_started = 0;
if (svc->flags & SVC_RUNNING) {
return;
}
needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0;
if (needs_console && (!have_console)) {
ERROR("service '%s' requires console\n", svc->name);
svc->flags |= SVC_DISABLED;
return;
}
/*
Service一般運行於另外一個程序中,這個程序也是init的子程序,所以啟動service前需要判斷對應的可執行檔案是否存在,zygote服務對應的可執行檔案為/system/bin/app_process
*/
if (stat(svc->args[0], &s) != 0) {
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED;
return;
}
if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) {
ERROR("service '%s' must be one-shot to use dynamic args, disabling\n",
svc->args[0]);
svc->flags |= SVC_DISABLED;
return;
}
// … 省略selinux相關的內容
// 通過fork建立一個子程序
pid = fork();
if (pid == 0) { // 在子程序中
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;
umask(077);
// 設定屬性服務資訊到環境變數
if (properties_inited()) {
get_property_workspace(&fd, &sz);
sprintf(tmp, "%d,%d", dup(fd), sz);
add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
}
// 設定屬性程序的環境變數
for (ei = svc->envvars; ei; ei = ei->next)
add_environment(ei->name, ei->value);
setsockcreatecon(scon);
// 根據socketinfo建立socket,用於通訊
for (si = svc->sockets; si; si = si->next) {
int socket_type = (
!strcmp(si->type, "stream") ? SOCK_STREAM :
(!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
int s = create_socket(si->name, socket_type,
si->perm, si->uid, si->gid);
if (s >= 0) {
publish_socket(si->name, s);
}
}
….
if (!dynamic_args) {
/*
呼叫execve函式來執行/system/bin/app_process,這樣就進入了app_process的main函式中了。
*/
if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
} else {
…
}
_exit(127);
}
…
// 父程序中設定子服務程序的資訊,啟動時間、pid等。
svc->time_started = gettime();
svc->pid = pid;
svc->flags |= SVC_RUNNING;
// 如果屬性服務已經初始化完畢了,那麼就設定相應服務為running狀態
if (properties_inited())
notify_service_state(svc->name, "running");
}
原來,zygote是通過fork和execv共同建立的,但是service結構中的onrestart欄位好像沒有派上用場,原因何在?我們接著往下看。
重啟zygote
init程序在初始化的時候會呼叫signal_init函式來初始化一個socket對,主要用來父子程序間通訊。
void signal_init(void)
{
int s[2];
struct sigaction act;
memset(&act, 0, sizeof(act));
/*
父程序設定sa_flags為SA_NOCLDSTOP,表明只有子程序在退出時父程序才能收到SIGCHLD的訊號,處理函式為sigchld_handler
*/
act.sa_handler = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, 0);
/* create a signalling mechanism for the sigchld handler */
if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
signal_fd = s[0];
signal_recv_fd = s[1];
fcntl(s[0], F_SETFD, FD_CLOEXEC);
fcntl(s[0], F_SETFL, O_NONBLOCK);
fcntl(s[1], F_SETFD, FD_CLOEXEC);
fcntl(s[1], F_SETFL, O_NONBLOCK);
}
handle_signal();
}
因此,子程序在退出時,父程序收到SIGCHLD訊息,此時,訊號處理函式為sigchld_handler。
static void sigchld_handler(int s)
{
write(signal_fd, &s, 1);
}
signal_fd被寫入值之後,那麼對應的signal_recv_fd便能收到訊息了。處理邏輯在init程序main函式的最後面的for迴圈中進行處理:
int get_signal_fd()
{
return signal_recv_fd;
}
for (i = 0; i < fd_count; i++) {
…
else if (ufds[i].fd == get_signal_fd())
handle_signal();
}
}
void handle_signal(void)
{
char tmp[32];
/* we got a SIGCHLD - reap and restart as needed */
read(signal_recv_fd, tmp, sizeof(tmp));
while (!wait_for_one_process(0))
;
}
static int wait_for_one_process(int block)
{
pid_t pid;
int status;
struct service *svc;
struct socketinfo *si;
time_t now;
struct listnode *node;
struct command *cmd;
// 獲得退出程序的pid
while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
if (pid <= 0) return -1;
INFO("waitpid returned pid %d, status = %08x\n", pid, status);
// 遍歷service_list連結串列,找到退出的那個服務
svc = service_find_by_pid(pid);
if (!svc) {
ERROR("untracked pid %d exited\n", pid);
return 0;
}
NOTICE("process '%s', pid %d exited\n", svc->name, pid);
/*
如果不是SVC_ONESHOT即一次性啟動的服務,那麼就殺死該程序的所有子程序,這也是為什麼zygote死後,java世界崩潰的原因。
*/
if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
kill(-pid, SIGKILL);
NOTICE("process '%s' killing any children in process group\n", svc->name);
}
/* 刪除服務建立的socket資訊*/
for (si = svc->sockets; si; si = si->next) {
char tmp[128];
snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
unlink(tmp);
}
svc->pid = 0;
svc->flags &= (~SVC_RUNNING);
/*
如果服務程序的標誌SVC_ONESHOT置位了,則表明此服務為一次性服務,死了之後就不需要重啟,除非設定SVC_RESTART標誌來手動重啟該服務。
此類服務死了之後,就置為SVC_DISABLED
*/
if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
svc->flags |= SVC_DISABLED;
}
/* 通知屬性伺服器,將該服務屬性設定為stopped */
if (svc->flags & (SVC_DISABLED | SVC_RESET) ) {
notify_service_state(svc->name, "stopped");
return 0;
}
now = gettime();
/*
如果程序帶有SVC_CRITICA標誌,那麼此時如果程序在4分鐘內,死了>4次,則重啟進入到recovery模式。根據init.rc的配置可知,ueventd、healthd、servicemanager等服務享有此種特殊待遇。
*/
if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
ERROR("critical process '%s' exited %d times in %d minutes; "
"rebooting into recovery mode\n", svc->name,
CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
return 0;
}
} else {
svc->time_crashed = now;
svc->nr_crashed = 1;
}
}
svc->flags &= (~SVC_RESTART);
svc->flags |= SVC_RESTARTING;
/*
Execute all onrestart commands for this service.
Zygote將執行如下操作:
write /sys/android_power/request_state wake
write /sys/power/state on
restart media
restart netd
*/
list_for_each(node, &svc->onrestart.commands) {
cmd = node_to_item(node, struct command, clist);
cmd->func(cmd->nargs, cmd->args);
}
/* 重新通知屬性伺服器,將該服務屬性設定為restarting */
notify_service_state(svc->name, "restarting");
return 0;
}
通過上面的程式碼可以知道onrestart的作用了,但是上述程式碼只是設定了相應svc的一些狀態,flag、time_crashed、nr_crashed等等。那麼真正的svc啟動是在哪裡呢?在init程序的main函式中,有一個大的無限迴圈,程式碼如下:
for(;;) {
int nr, i, timeout = -1;
execute_one_command();
// 此處就是呼叫service_start函式來重啟那些flag帶有SVC_RESTARTING的服務程序
restart_processes();
….
}
至此,死去的服務又開始重新啟動了。