前言
我們之前在 Android 系統(tǒng)啟動流程簡析 中提到:Android 系統(tǒng)內(nèi)核啟動之后讲婚,除了初始化各種軟硬件環(huán)境忍些,加載驅(qū)動程序撬统,掛載根文件系統(tǒng)等操作之外缝呕,最重要的一個操作就是在系統(tǒng)中找到可執(zhí)行程序 init ,啟動 init 進程遮怜。
下面我們對 init 進程啟動源碼進行簡析佳魔。但在簡析之前,需要我們預(yù)先了解一些前置知識偿枕。
前置知識
本文 Android 源碼版本:android-6.0.1_r81
關(guān)鍵路徑:如下表所示:
File | Path |
---|---|
init 進程 | /system/core/init/init.cpp |
init.rc 腳本 | /system/core/rootdir/init.rc |
readme.txt(Android Init Language) | /system/core/init/readme.txt |
-
init.rc 文件格式:init 進程在啟動過程中璧瞬,會啟動許多關(guān)鍵的系統(tǒng)服務(wù),如果在源碼中一項項啟動會顯得很繁瑣渐夸,而且不易擴展嗤锉,因此 Android 引入了 init.rc。init.rc 是一個配置腳本墓塌,其內(nèi)部使用一種名為 Android Init Language 的語言編寫而成瘟忱,詳細(xì)內(nèi)容可在
/system/core/init/readme.txt
中查看。
?這里我們就簡單介紹下 init.rc 的一些主要配置語法:
?? init.rc 內(nèi)部主要包含五種類型語句:Action苫幢、Service酷誓、Command、Options 和 Import态坦。其中盐数,主要的配置選項為:Action 和 Service,Trigger 和 Command 是對 Action 的補充伞梯,Options 是對 Service 的補充玫氢。Import 用于導(dǎo)入其他 .rc 文件并進行解析。下面簡單介紹下 Action 和 Service 的配置語法:
?Action:以 on
開頭谜诫,trigger
是判斷條件漾峡,command
是具體執(zhí)行一些操作,當(dāng)滿足 trigger
條件時喻旷,執(zhí)行這些 command
生逸。
on <trigger> [&& <trigger>]* //設(shè)置觸發(fā)器
<command> //觸發(fā)器觸發(fā)之后要完成的操作
<command>
<command>
?Service:服務(wù)是 init 進程啟動的程序、當(dāng)服務(wù)退出時 init 進程會視情況重啟服務(wù)且预。其語法以 service
開頭槽袄,name
是指定這個服務(wù)的名稱,pathname
表示這個服務(wù)的執(zhí)行文件路徑锋谐,argument
表示傳遞給執(zhí)行文件的參數(shù)遍尺,option
表示這個服務(wù)的一些配置。
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
?? .rc 文件是由一個個 Section 組成涮拗。action 加上 trigger 以及一些 command 組成一個Section乾戏;service 加上一些 option,也組成一個Section 三热;
注:在 Android 7.0 以前鼓择,init 進程只解析根目錄下的 init.rc 文件,但是隨著版本的迭代就漾,init.rc 越來越臃腫呐能,所以在 7.0 以后,init.rc 一些業(yè)務(wù)被分拆到 /system/etc/init从藤,/vendor/etc/init催跪,/odm/etc/init 三個目錄下。
init 進程啟動流程 簡析
init 進程的入口函數(shù)為:main
夷野,其位于:/system/core/init/init.cpp懊蒸,具體內(nèi)容如下:
int main(int argc, char** argv) {
// 如果不是 ueventd,則進入
if (!strcmp(basename(argv[0]), "ueventd")) {
/* 該函數(shù)定義在在system/core/init/ueventd.cpp
* init 進程會創(chuàng)建子進程 ueventd悯搔,uevented 主要負(fù)責(zé)設(shè)備節(jié)點文件的創(chuàng)建骑丸,
* 創(chuàng)建方式有兩種:冷插拔(Cold Plug) 和 熱插拔(Hot Plug)
*/
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) {
/* 該函數(shù)定義在/system/core/init/watchdogd.cpp,主要用于啟動“看門狗”
*
* “看門狗”本身是一個定時器電路妒貌,內(nèi)部會不斷的進行計時(或計數(shù))操作,
* 計算機系統(tǒng)和”看門狗”有兩個引腳相連接通危,正常運行時每隔一段時間就會
* 通過其中一個引腳向”看門狗”發(fā)送信號,”看門狗”接收到信號后會將計時器
* 清零并重新開始計時,而一旦系統(tǒng)出現(xiàn)問題灌曙,進入死循環(huán)或任何阻塞狀態(tài)菊碟,
* 不能及時發(fā)送信號讓”看門狗”的計時器清零,當(dāng)計時結(jié)束時在刺,
* ”看門狗”就會通過另一個引腳向系統(tǒng)發(fā)送“復(fù)位信號”逆害,讓系統(tǒng)重啟。
*/
return watchdogd_main(argc, argv);
}
// Clear the umask.
umask(0);
// 注冊環(huán)境變量PATH
// _PATH_DEFPATH 定義在 bionic/libc/include/paths.h 中
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) {
// 掛載 tmpfs 到 /dev 目錄
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
// 創(chuàng)建文件夾 /dev/pts
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();
// 初始化內(nèi)核log蚣驼,位于節(jié)點/dev/kmsg
klog_init();
// 設(shè)置 log 級別
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));
// 定義在 /system/core/init/property_service.cpp
// 初始化屬性服務(wù)
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(); // 處理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 安全策略
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 };
// 調(diào)用 execv 重新執(zhí)行 main 函數(shù)魄幕,進入 second-stage 階段
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();
// 加載default.prop文件
property_load_boot_defaults();
// 啟動屬性服務(wù),開啟一個socket監(jiān)聽系統(tǒng)屬性的設(shè)置
start_property_service();
// 解析 init.rc
init_parse_config_file("/init.rc");
// 執(zhí)行rc文件中觸發(fā)器為 on early-init 的語句
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...
// 等待冷插拔設(shè)備初始化完成
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");
// 設(shè)備組合件的初始化操作
queue_builtin_action(keychord_init_action, "keychord_init");
// 屏幕顯示 Android 靜態(tài) Logo
queue_builtin_action(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
// 觸發(fā)rc文件中觸發(fā)器為on init的語句
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];
// 當(dāng)處于充電模式時颖杏,將 charger 加入執(zhí)行隊列纯陨;否則將 late-init 加入隊列
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;
// 循環(huán)等待事件發(fā)生
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;
}
從源碼中看到,init 進程分為兩個階段:first-stage 和 second-stage留储。
第一個階段 first-stage 主要做的事為:掛載一些必要的文件系統(tǒng):tmpfs翼抠,devpts,proc获讳,sysfs等机久,然后初始化 selinux 安全策略,在 selinux 初始化完成后就立即重新執(zhí)行main
函數(shù)赔嚎,進入第二階段 second-stage膘盖,也就是將 init 進程從內(nèi)核態(tài)轉(zhuǎn)化為用戶態(tài)執(zhí)行。
first-stage 和 second-stage 都會做一些同樣的工作尤误,比如設(shè)置環(huán)境變量侠畔,設(shè)置日志,設(shè)置不同階段的 selinux 策略损晤。對于 second-stage 來說软棺,其主要做的就是:初始化和啟動屬性服務(wù),解析 init.rc 文件尤勋,并按順序先后執(zhí)行觸發(fā)器: on early-init -> init -> late-init喘落。
屬性服務(wù) 的作用是讓 init 進程可以對其他程序進行權(quán)限控制茵宪。在 Android 中,對屬性的設(shè)置都統(tǒng)一交由 init 進程管理瘦棋,其他進程不能直接修改屬性稀火,而必須通過屬性服務(wù)申請。其實屬性服務(wù)的是一個跨進程通訊赌朋,使用的通訊機制是 socket凰狞。這里我們就不深入代碼講解了,感興趣的請自行查看沛慢。
對于 init.rc 文件的具體解析過程赡若,我們這里也不做講解。但簡單提一下团甲,init_parse_config_file("/init.rc");
該函數(shù)源碼位于:/system/core/init/init_parser.cpp 逾冬,主要做的就是對 init.rc 的文件內(nèi)容進行解析,將解析出的 services 和 actions 等添加到運行隊列中躺苦,等待 trigger 觸發(fā)器的觸發(fā)運行粉渠。
我們重點講下 Zygote 的啟動點。
啟動 Zygote 進程
我們知道圾另,Zygote 的啟動是在 init 進程解析 init.rc 后啟動的霸株,那我們先來看下 init.rc 文件的內(nèi)容:
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
···
這里看到 Zygote 的啟動腳本被 import 了進來:import /init.${ro.zygote}.rc
, ${ro.zygote} 會被替換成 ro.zyogte 的屬性值集乔,這個是由不同的硬件廠商自己定制的去件, 有四個值,zygote32扰路、zygote64尤溜、zygote32_64、zygote64_32 汗唱,也就是說可能有四種 .rc 文件宫莱。
這里我們選用 64 位處理器的版本來分析:/system/core/rootdir/init.zygote64.rc
service zygote /system/bin/app_process64 -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
writepid /dev/cpuset/foreground/tasks
service 選項用于通知 init 進程啟動 zygote 程序,zygote 的執(zhí)行程序路徑為:/system/bin/app_process64哩罪,init 進程啟動 zygote 時授霸,傳遞的參數(shù)為: -Xzygote /system/bin --zygote --start-system-server。
class 的作用是用于批量管理 service际插。因此這里class main
表示 zygote 服務(wù)屬于類main
碘耳。后續(xù)啟動 main
的時候,就會啟動 zygote 服務(wù)框弛。
前面我們說過辛辨,init 進程在解析完 init.rc 文件后,會依次執(zhí)行觸發(fā)器:on early-init -> init -> late-init。我們再來看下 init.rc 文件內(nèi)容:
on late-init
···
on nonencrypted
class_start main // zygote 屬于類 main
class_start late_start
···
也就是說在 late-init 觸發(fā)時斗搞,就會啟動類 main
管理的服務(wù)指攒,也就是會啟動 zygote。
class_start
對應(yīng)的處理方法是 do_class_start
,該方法定義在system\core\init\builtins.cpp 中:
int do_class_start(int nargs, char **args){
/* Starting a class does not start services
* which are explicitly disabled. They must
* be started individually.
*/
service_for_each_class(args[1], service_start_if_not_disabled);
return 0;
}
該方法內(nèi)部又會調(diào)用 service_for_each_class
:
/system/core/init/init_parser.cpp
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_list
鏈表僻焚,每找到一個 classname
為 main
的 service,就調(diào)用 service_start_if_not_disabled
來進一步處理允悦,service_start_if_not_disabled
方法源碼如下:
/system/core/init/builtins.cpp
static void service_start_if_not_disabled(struct service *svc)
{
if (!(svc->flags & SVC_DISABLED)) {
service_start(svc, NULL);
} else {
svc->flags |= SVC_DISABLED_START;
}
}
可以看到,最終是調(diào)用 service_start
來啟動服務(wù)溅呢。service_start
源碼如下:
/system/core/init/init.cpp
void service_start(struct service *svc, const char *dynamic_args)
{
···
// 如果Service已經(jīng)運行,則不啟動
if (svc->flags & SVC_RUNNING) {
return;
}
···
struct stat s;
// 判斷Service要啟動的執(zhí)行文件是否存在
if (stat(svc->args[0], &s) != 0) {
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED;
return;
}
···
NOTICE("Starting service '%s'...\n", svc->name);
// fork 函數(shù)創(chuàng)建子進程
pid_t pid = fork();
if (pid == 0) { // 運行在子進程當(dāng)中
struct socketinfo *si;
struct svcenvinfo *ei;
char tmp[32];
int fd, sz;
umask(077);
···
if (!dynamic_args) {
// 加載可執(zhí)行文件
if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) {
ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno));
}
···
_exit(127);
}
···
}
該函數(shù)會通過調(diào)用 fork
函數(shù)來創(chuàng)建子進程猿挚,并在子進程中調(diào)用 execve
來加載可執(zhí)行文件咐旧,對于 zygote 服務(wù)來說,也就是會執(zhí)行 /system/bin/app_process64绩蜻。那接下來該可執(zhí)行文件的 main
函數(shù)就會被調(diào)用:
framework/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{
···
// Process command line arguments
// ignore argv[0]
argc--;
argv++;
// --zygote : Start in zygote mode
// --start-system-server : Start the system server.
// --application : Start in application (stand alone, non zygote) mode.
// --nice-name : The nice name for this process.
···
// Parse runtime arguments. Stop at first unrecognized option.
···
// 解析參數(shù)
++i; // Skip unused "parent dir" argument.
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
···
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
return 10;
}
}
該函數(shù)會先對參數(shù)進行解析铣墨,對照 init.zygote64.rc 傳遞的參數(shù):-Xzygote /system/bin --zygote --start-system-server
,可以解析得出zygote=true,startSystemServer =true
办绝。于是最終會通過 AppRuntime.start
方法啟動 zygote伊约。
到此,init 進程就成功啟動了 zygote 進程孕蝉。
總結(jié)
init 進程啟動時屡律,總共經(jīng)歷兩個階段:內(nèi)核態(tài) 和 用戶態(tài)。
內(nèi)核態(tài) 階段下降淮,init 進程主要做掛載一些必要的設(shè)備節(jié)點(tmpfs超埋,devpts,proc佳鳖,sysfs····)霍殴,初始化 selinux 安全策略等操作,然后切換到 用戶態(tài)(通過傳遞參數(shù) --second-stage
系吩,并調(diào)用 execv
進行重啟切換)来庭;
用戶態(tài) 階段下,init 進程最主要的一個操作就是解析 init.rc 文件穿挨,并按照順序先后執(zhí)行觸發(fā)器:on early-init -> init -> late-init月弛。
init 進程每遇到一個服務(wù)(service)時,就會通過 fork
出一個子進程來啟動該服務(wù)科盛。
zygote 在 init.rc 中是作為一個服務(wù)(service)存在尊搬,當(dāng) init.rc 文件被解析完成后,init 進程最終會在執(zhí)行觸發(fā)器 late-init 的過程中啟動 zygote 服務(wù)(zygote 服務(wù)進程就是 init 進程 fork
出來的一個子進程土涝,在該進程最后佛寿,會通過 AppRuntime.start
真正啟動 zygote)。