整個Android系統的啟動分為Linux kernel的啟動和Android系統的啟動。Linux kernel啟動起來後,然後就運作第一個使用者程式,在Android中,就是init程式,上一博文 android啟動之linux核心啟動已經介紹。
Init程序始終是第一個程序。Init程序的對應的代碼的main函數在目錄system/core/init/init.c,先來總體看一下這個main函數。
main函數
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;
//對傳入的argv[0]進行判斷,決定程式的執行分支
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);
/* 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.
*/
//建立各種使用者空間的目錄,挂載與核心空間互動的檔案
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);
/* indicate that booting is in progress to background fw loaders, etc */
//建立打開/dev/.booting檔案,然後關閉
close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
/* 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.
*/
//system/core/init/util.c。建立裝置節點/dev/__null__,利用dup2函數把标準輸入、标準輸出、标準錯誤輸出重定向到這個裝置檔案中(0标準輸入、1标準輸出、2标準錯誤輸出)。重定向操作完成後,就關閉掉fd。
open_devnull_stdio();
//system/core/libcutils/klog.c。初始化log系統/dev/__kmsg__
klog_init();
//system/core/init/property_service.c。初始化資源/dev/__properties__,主要是映射一些記憶體空間
property_init();
//取得硬體名,hardware通過核心指令行提供或/proc/cpuinfo檔案中提供。import /init.${ro.hardware}.rc
get_hardware_name(hardware, &revision);
//使用import_kernel_cmdline函數導入核心變量,調用export_kernel_boot_props函數通過屬性設定核心變量。就是來回設定一些屬性值,包括hardware變量,在此處又通過ro.boot.hardware屬性設定了一次值。
process_kernel_cmdline();
#ifdef HAVE_SELINUX
//SELinux(Security-Enhanced Linux) 是美國國家安全局(NSA)對于強制通路控制的實作,它是個經過安全強化的Linux作業系統,很多時候是被關閉的。
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);
INFO("loading selinux policy\n");
if (selinux_enabled) {
if (selinux_android_load_policy() < 0) {
selinux_enabled = 0;
INFO("SELinux: Disabled due to failed policy load\n");
} else {
selinux_init_all_handles();
}
} else {
INFO("SELinux: Disabled by command line option\n");
}
/* 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");
#endif
is_charger = !strcmp(bootmode, "charger");
INFO("property init\n");
if (!is_charger)
property_load_boot_defaults();
INFO("reading config file\n");
//system/core/init/init_parser.c。讀取并且解析init.rc檔案
init_parse_config_file("/init.rc");
//system/core/init/init_parser.c。觸發在init腳本檔案中名字為early-init的action,并且執行其commands,其實是.rc檔案中的: on early-init
action_for_each_trigger("early-init", action_add_queue_tail);
//system/core/init/init_parser.c。内建action并添加到action_queue中,到execute_one_command()中檢測執行。
queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
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 */
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);
}
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");
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
//進入一個無限循環 to wait for device/property set/child process exit events.例如, 如果SD卡被插入,init會收到一個裝置插入事件,它會為這個裝置建立節點。系統中比較重要的程序都是由init來fork的,是以如果他們誰崩潰 了,那麼init 将會收到一個 SIGCHLD 信号,把這個信号轉化為子程序退出事件, 是以在loop中,init 會操作程序退出事件并且執行 *.rc 檔案中定義的指令。
for(;;) {
int nr, i, timeout = -1;
//執行action_queue中的action,并将此action移除
execute_one_command();
//檢查service_list是否有程序需要重新開機
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;
// bootchart是一個性能統計工具,用于搜集硬體和系統的資訊,并将其寫入磁盤,以便其他程式使用
#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();
}
}
}
.rc腳本語言淺析
init.rc檔案在core/rootdir/init.rc,我挑部分内容展示如下:
......
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
......
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
......
init腳本語言的規範:
A、Android init.rc檔案由系統第一個啟動的init程式解析,此檔案由語句組成;
B、在init.rc檔案中一條語句通常是占據一行;
C、單詞之間是通過空格符來相隔的,如果需要在單詞内使用空格,那麼得使用轉義字元"\";
D、如果在一行的末尾有一個反斜杠,那麼是換行折疊符号,應該和下一行合并成一起來處理,這樣做主要是為了避免一行的字元太長,與C語言中的含義是一緻的;
E、注釋是以#号開頭(允許以空格開頭)。
首先我們先了解一個概念:Section(語句塊),相當于C語言中大括号内的一個塊。
一個Section語句塊以Service或On開頭。而以On開頭的叫做動作(Action),以Service開頭的Section叫做服務。Actions和Services有唯一的名字。如果有重名的情況,第二個申明的将會被作為錯誤忽略。上面列出的正好一個是action, 一個是service。
另外還有兩個概念:Commands(指令)和Options(選項)。
Action和services顯式聲明了一個語句塊,而commands和options屬于最近聲明的語句塊。在第一個語句塊之前的commands和options會被忽略。
Actions(動作)
Actions(動作)形式如下:
on <trigger>
<command1>
<command2>
<command3>
Actions其實就是用on關鍵字開頭的一序列的Commands(指令)。on後面有一個trigger(觸發器),它被用于決定action的執行時間。當一個符合action觸發條件的事件發生時,action會被加入到執行隊列的末尾,除非它已經在隊列裡 了。隊列中的每一個action都被依次提取出,而這個action中的每個command(指令)都将被依次執行,直到下一個Action或下一個Service。
Triggers(觸發器)是一個用于比對特定事件類型的字元串,用于使Actions發生,有如下形式:
簡單的字元串如early-init:
這是init執行後的第一個被觸發的Triggers(觸發器)。(在 /init.conf (啟動配置檔案)被裝載之後)
<name>=<value>:
這種形式的Triggers(觸發器)會在屬性<name>被設定為指定的<value>時被觸發。
device-added-<path>:
device-removed-<path>:
這種形式的Triggers(觸發器)會在一個裝置節點檔案被增删時觸發。
service-exited-<name>:
這種形式的Triggers(觸發器)會在一個特定的服務退出時觸發。
Services(服務)
Services(服務)是一個程式,它在初始化時啟動,并在退出時可選擇讓其重新開機。
Services(服務)的形式如下:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
name:服務名
pathname:目前服務對應的程式位置
option:目前服務設定的選項
Options(選項)
Options(選項)是一個Services(服務)的修正者。他們影響Services(服務)在何時,并以何種方式運作。
critical:說明這是一個對于裝置關鍵的服務。如果他四分鐘内退出大于四次,系統将會重新開機并進入recovery(恢複)模式。
disabled:說明這個服務不會同與他同trigger(觸發器)下的服務自動啟動。他必須被明确的按名啟動。
setenv <name> <value> (設定環境變量)
在程序啟動時将環境變量<name>設定為<value>。
socket <name> <type> <perm> [ <user> [ <group> ] ]
建立一個Uinx域的名為/dev/socket/<name> 的套接字,并傳遞它的檔案描述符給已啟動的程序。<type> 必須是 "dgram"或"stream"。User 和 group預設為0。
user <username>
在啟動這個服務前改變該服務的使用者名。此時預設為root。(???有可能的話應該預設為nobody)。目前,如果你的程序要求Linux capabilities(能力),你無法使用這個指令。即使你是root,你也必須在程式中請求
capabilities(能力)。然後降到你想要的 uid。
group <groupname> [ <groupname> ]*
在啟動這個服務前改變該服務的組名。除了(必需的)第一個組名,附加的組名通常被用于設定程序的補充組(通過setgroups())。此時預設為root。(???有可能的話應該預設為nobody)。
oneshot
服務退出時不重新開機。
class <name>
指定一個服務類。所有同一類的服務可以同時啟動和停止。如果不通過class選項指定一個類,則預設為"default"類服務。
onrestart
當服務重新開機,執行一個指令(下詳)。
Commands(指令)
exec <path> [ <argument> ]*
建立和執行一個程式(<path>)。在程式完全執行前,init将會阻塞。由于它不是内置指令,應盡量避免使用exec,它可能會引起init卡死。(??? 是否需要一個逾時設定?)
export <name> <value>
在全局環境變量中設在環境變量 <name>為<value>。(這将會被所有在這指令之後運作的程序所繼承)
ifup <interface>
啟動網絡接口<interface>
import <filename>
解析一個init配置檔案,擴充目前配置。
hostname <name>
設定主機名。
chmod <octal-mode> <path>
更改檔案通路權限。
chown <owner> <group> <path>
更改檔案的所有者群組。
class_start <serviceclass>
啟動所有指定服務類下的未運作服務。
class_stop <serviceclass>
停止指定服務類下的所有已運作的服務。
domainname <name>
設定域名。
insmod <path>
加載<path>中的子產品。
mkdir <path> [mode] [owner] [group]
建立一個目錄<path>,可以選擇性地指定mode、owner以及group。如果沒有指定,預設的權限為755,并屬于root使用者和root組。
mount <type> <device> <dir> [ <mountoption> ]*
試圖在目錄<dir>挂載指定的裝置。<device> 可以是以 [email protected] 的形式指定一個mtd塊裝置。<mountoption>包括 "ro"、"rw"、"remount"、"noatime"、 ...
setprop <name> <value>
設定系統屬性 <name> 為 <value>值。
setrlimit <resource> <cur> <max>
設定<resource>的rlimit(資源限制)。
start <service>
啟動指定服務(如果此服務還未運作)。
stop <service>
停止指定服務(如果此服務在運作中)。
symlink <target> <path>
建立一個指向<path>的軟連接配接<target>。
sysclktz <mins_west_of_gmt>
設定系統時鐘基準(0代表時鐘滴答以格林威治平均時(GMT)為準)
trigger <event>
觸發一個事件。用于将一個action與另一個 action排列。
write <path> <string> [ <string> ]*
打開路徑為<path>的一個檔案,并寫入一個或多個字元串。
Properties(屬性)
Init更新一些系統屬性以提供對正在發生的事件的監控能力:
init.action
此屬性值為正在被執行的action的名字,如果沒有則為""。
init.command
此屬性值為正在被執行的command的名字,如果沒有則為""。
init.svc.<name>
名為<name>的service的狀态("stopped"(停止), "running"(運作), "restarting"(重新開機))
.rc檔案的解析
重要的資料結構有兩個清單,一個隊列。
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);
*.rc 腳本中所有 service關鍵字定義的服務将會添加到 service_list 清單中。
*.rc 腳本中所有 on 關鍵開頭的動作将會被會添加到 action_list 清單中。
main函數中解析init.rc檔案的代碼是:
init_parse_config_file("/init.rc");
該函數在system/core/init/init_parser.c中,如下:
int parse_config_file(const char *fn)
{
char *data;
data = read_file(fn, 0);
if (!data) return -1;
parse_config(fn, data);
DUMP();
return 0;
}
其中的核心方法是parse_config(fn, data);,如下:
static void parse_config(const char *fn, char *s)
{
...
case T_NEWLINE:
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;
}
...
}
parse_config會逐行對腳本進行解析,如果關鍵字類型為 SECTION ,就是上一子產品中說的Section(語句塊),那麼将會執行 parse_new_section() 。
parse_new_section()中再分别對 service 或者 on 關鍵字開頭的内容進行解析,如下:
...
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;
}
...
對 on 關鍵字開頭的内容進行解析
static void *parse_action(struct parse_state *state, int nargs, char **args)
{
...
act = calloc(1, sizeof(*act));
act->name = args[1];
list_init(&act->commands);
list_add_tail(&action_list, &act->alist);
...
}
對 service 關鍵字開頭的内容進行解析
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
struct service *svc;
if (nargs name = args[1];
svc->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);
//添加該服務到 service_list 清單
list_add_tail(&service_list, &svc->slist);
return svc;
}
解析後的服務表現形式是一個service結構體。例如:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
服務名稱為: zygote
啟動該服務執行的指令: /system/bin/app_process
指令的參數: -Xzygote /system/bin --zygote --start-system-server
socket zygote stream 666: 建立一個名為:/dev/socket/zygote 的 socket ,類型為:stream