https://jsonchao.github.io/2019/02/18/Android%E7%B3%BB%E7%BB%9F%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B%E4%B9%8Binit%E8%BF%9B%E7%A8%8B%E5%90%AF%E5%8A%A8/
--------------------正義的分割線--
Init進(jìn)程,它是Linux內(nèi)核啟動之后運(yùn)行的第一個進(jìn)程。它的進(jìn)程號是1,并且生命周期貫穿整個linux 內(nèi)核運(yùn)行的始終。
linux中所有其它的進(jìn)程的共同祖先均為init進(jìn)程,可以通過“adb shell ps | grep init”查看進(jìn)程號。
Android init進(jìn)程的入口文件在system/core/init/init.cpp中,由于init是命令行程序,所以分析init.cpp首先應(yīng)從main函數(shù)開始:
時序圖:

main()
system/core/init/init.cpp
1 "ueventd"和"watchdogd"執(zhí)行的是另外的入口
2 掛載文件系統(tǒng)
3 屏蔽標(biāo)準(zhǔn)的輸入輸出/初始化內(nèi)核log系統(tǒng),初始化log系統(tǒng)。
4 selinux初始化
5 重新設(shè)置屬性
6 處理子進(jìn)程kill時的情況
7 加載default.prop中的屬性
8 解析init.rc
int main(int argc, char** argv) {
? ? // (1) "ueventd"和"watchdogd"執(zhí)行的是另外的入口
? ? if (!strcmp(basename(argv[0]), "ueventd")) {
? ? ? ? return ueventd_main(argc, argv);
? ? }
? ? if (!strcmp(basename(argv[0]), "watchdogd")) {
? ? ? ? return watchdogd_main(argc, argv);
? ? }
? ? /* umask是Linux函數(shù),用來控制權(quán)限。文件的默認(rèn)權(quán)限是644,目錄是755。umask(0)表示賦予文件和目錄所有的默認(rèn)權(quán)限,即不去除任何權(quán)限。*/
? ? // Clear the umask.
? ? umask(0);
? ? // 添加PATH=_PATH_DEFPATH _PATH_DEFPATH="/usr/bin:/bin"
? ? 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.
? ? // (2) 掛載文件系統(tǒng)
? ? if (is_first_stage) {
? ? ? ? // 掛載tmpfs文件系統(tǒng)
? ? ? ? mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
? ? ? ? mkdir("/dev/pts", 0755);
? ? ? ? mkdir("/dev/socket", 0755);
? ? ? ? // 掛載devpts文件系統(tǒng)
? ? ? ? mount("devpts", "/dev/pts", "devpts", 0, NULL);
? ? ? ? #define MAKE_STR(x) __STRING(x)
? ? ? ? // 掛載proc文件系統(tǒng)
? ? ? ? mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
? ? ? ? // 掛載sysfs文件系統(tǒng)
? ? ? ? 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.
? ? // (3) 屏蔽標(biāo)準(zhǔn)的輸入輸出/初始化內(nèi)核log系統(tǒng),初始化log系統(tǒng)。
? ? open_devnull_stdio();
? ? klog_init(); // 創(chuàng)建/dev/kmsg,保存內(nèi)核log。
? ? klog_set_level(KLOG_NOTICE_LEVEL); // 設(shè)置log級別為5
? ? // 首次啟動is_first_stage=first stage,再次啟動is_first_stage=second stage。
? ? NOTICE("init %s started!\n", is_first_stage ? "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));
? ? ? ? // 初始化屬性(具體實(shí)現(xiàn),還有待探究)
? ? ? ? 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();
? ? ? ? // Propagate 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.
? ? // (4) selinux初始化(待探究)
? ? /* selinux有兩種工作模式:
? ? 1、"permissive",所有的操作都被允許(即沒有MAC),但是如果違反權(quán)限的話,會記錄日志
? ? 2、"enforcing",所有操作都會進(jìn)行權(quán)限檢查。在一般的終端中,應(yīng)該工作于enforing模式
? ? adb shell getenforce 查看selinux模式
? ? adb shell setenforce 0 命令進(jìn)入permissive模式
? ? adb shell setenforce 1 命令進(jìn)入Enforcing模式 */
? ? 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.
? ? // (5) 重新設(shè)置屬性
? ? if (is_first_stage) {
? ? ? ? if (restorecon("/init") == -1) { // restorecon命令用來恢復(fù)SELinux文件屬性
來自: http://man.linuxde.net/restorecon
? ? ? ? ? ? ERROR("restorecon failed: %s\n", strerror(errno));
? ? ? ? ? ? security_failure();
? ? ? ? }
? ? ? ? char* path = argv[0];
? ? ? ? char* args[] = { path, const_cast<char*>("--second-stage"), nullptr }; //設(shè)置參數(shù)--second-stage
? ? ? ? // 執(zhí)行init進(jìn)程,重新進(jìn)入main函數(shù)
? ? ? ? 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.
? ? NOTICE("Running restorecon...\n");
? ? restorecon("/dev");
? ? restorecon("/dev/socket");
? ? restorecon("/dev/__properties__");
? ? restorecon("/property_contexts");
? ? restorecon_recursive("/sys");
? ? // 創(chuàng)建epoll句柄(暫時不清楚用途)
? ? epoll_fd = epoll_create1(EPOLL_CLOEXEC);
? ? if (epoll_fd == -1) {
? ? ? ? ERROR("epoll_create1 failed: %s\n", strerror(errno));
? ? ? ? exit(1);
? ? }
? ? // (6) signal_handler_init函數(shù)就是處理子進(jìn)程kill時的情況
? ? signal_handler_init();
? ? // (7) 加載default.prop中的屬性
? ? property_load_boot_defaults();
? ? // 讀取"ro.oem_unlock_supported"屬性值
? ? export_oem_lock_status();
? ? // start_property_service函數(shù)創(chuàng)建了socket,然后監(jiān)聽,并且調(diào)用register_epoll_handler函數(shù)把socket的fd放入了epoll中。
? ? start_property_service();
? ? const BuiltinFunctionMap function_map;
? ? Action::set_function_map(&function_map);
? ? // (8) 解析init.rc
? ? Parser& parser = Parser::GetInstance();
? ? parser.AddSectionParser("service",std::make_unique<ServiceParser>());
? ? parser.AddSectionParser("on", std::make_unique<ActionParser>());
? ? parser.AddSectionParser("import", std::make_unique<ImportParser>());
? ? parser.ParseConfig("/init.rc");
? ? // ...
? ? return 0;
}
open_devnull_stdio()
Util.cpp
void open_devnull_stdio(void)
{
? ? // Try to avoid the mknod() call if we can. Since SELinux makes
? ? // a /dev/null replacement available for free, let's use it.
? ? int fd = open("/sys/fs/selinux/null", O_RDWR);
? ? if (fd == -1) {
? ? ? ? // OOPS, /sys/fs/selinux/null isn't available, likely because
? ? ? ? // /sys/fs/selinux isn't mounted. Fall back to mknod.
? ? ? ? static const char *name = "/dev/__null__";
? ? ? ? if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
? ? ? ? ? ? fd = open(name, O_RDWR);
? ? ? ? ? ? unlink(name);
? ? ? ? }
? ? ? ? if (fd == -1) {
? ? ? ? ? ? exit(1);
? ? ? ? }
? ? }
? ? dup2(fd, 0); // 復(fù)制文件描述符fd到0(標(biāo)準(zhǔn)輸入)
? ? dup2(fd, 1); // 復(fù)制文件描述符fd到1(標(biāo)準(zhǔn)輸出)
? ? dup2(fd, 2); // 復(fù)制文件描述符fd到2(錯誤輸出)
? ? if (fd > 2) {
? ? ? ? close(fd);
? ? }
}
這個函數(shù)調(diào)用dup函數(shù)把標(biāo)準(zhǔn)輸入,輸出,錯誤輸出都重定位到/dev/null,如果需要在后面的程序中看到打印的話需要屏蔽這個函數(shù)。
property_init()
property_service.cpp
void property_init() {
? ? if (__system_property_area_init()) {
? ? ? ? ERROR("Failed to initialize property area\n");
? ? ? ? exit(1);
? ? }
}
bionic\libc\bionic\System_properties.cpp
int __system_property_area_init()
{
? ? free_and_unmap_contexts();
? ? mkdir(property_filename, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
? ? if (!initialize_properties()) {
? ? ? ? return -1;
? ? }
? ? bool open_failed = false;
? ? bool fsetxattr_failed = false;
? ? list_foreach(contexts, [&fsetxattr_failed, &open_failed](context_node* l) {
? ? ? ? if (!l->open(true, &fsetxattr_failed)) {
? ? ? ? ? ? open_failed = true;
? ? ? ? }
? ? });
? ? if (open_failed || !map_system_property_area(true, &fsetxattr_failed)) {
? ? ? ? free_and_unmap_contexts();
? ? ? ? return -1;
? ? }
? ? initialized = true;
? ? return fsetxattr_failed ? -2 : 0;
}
signal_handler_init()
Note:init是一個守護(hù)進(jìn)程,為了防止init的子進(jìn)程成為僵尸進(jìn)程(zombie process),需要init在子進(jìn)程結(jié)束時獲取子進(jìn)程的結(jié)束碼,通過結(jié)束碼將程序表中的子進(jìn)程移除,防止成為僵尸進(jìn)程的子進(jìn)程占用程序表的空間(程序表的空間達(dá)到上限時,系統(tǒng)就不能再啟動新的進(jìn)程了,會引起嚴(yán)重的系統(tǒng)問題)。
system/core/init/Singal_handler.cpp
void signal_handler_init() {
? ? // // 在linux當(dāng)中,父進(jìn)程是通過捕捉SIGCHLD信號來得知子進(jìn)程運(yùn)行結(jié)束的情況
? ? // Create a signalling mechanism for SIGCHLD.
? ? int s[2];
? ? if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
? ? ? ? ERROR("socketpair failed: %s\n", strerror(errno));
? ? ? ? exit(1);
? ? }
? ? signal_write_fd = s[0];
? ? signal_read_fd = s[1];
? ? // Write to signal_write_fd if we catch SIGCHLD.
? ? struct sigaction act;
? ? memset(&act, 0, sizeof(act));
? ? act.sa_handler = SIGCHLD_handler;
? ? act.sa_flags = SA_NOCLDSTOP;
? ? sigaction(SIGCHLD, &act, 0);
? ? ServiceManager::GetInstance().ReapAnyOutstandingChildren();
? ? register_epoll_handler(signal_read_fd, handle_signal);
}
Android init.rc文件解析過程詳解
一、init.rc文件結(jié)構(gòu)介紹
init.rc文件基本組成單位是section, section分為三種類型,分別由三個關(guān)鍵字(所謂關(guān)鍵字即每一行的第一列)來區(qū)分,這三個關(guān)鍵字是on、service、import。
on類型的section表示一系列命令的組合,?例如:
on init
?export PATH /sbin:/system/sbin:/system/bin
?export ANDROID_ROOT /system
?export ANDROID_DATA /data
這樣一個section包含了三個export命令,命令的執(zhí)行是以section為單位的,所以這三個命令是一起執(zhí)行的,不會單獨(dú)執(zhí)行,?那什么時候執(zhí)行呢??這是由init.c的main()所決定的,main()里在某個時間會調(diào)用
action_for_each_trigger("init", action_add_queue_tail);
這就把on init開始的這樣一個section里的所有命令加入到一個執(zhí)行隊列,在未來的某個時候會順序執(zhí)行隊列里的命令,所以調(diào)用action_for_each_trigger的先后決定了命令執(zhí)行的先后。
service類型的section表示一個可執(zhí)行程序,例如:
service surfaceflinger /system/bin/surfaceflinger
?class main
?user system
?group graphics drmrpc
?onrestart restart zygote
surfaceflinger作為一個名字標(biāo)識了這個service,?/system/bin/surfaceflinger表示可執(zhí)行文件的位置,?class、user、group、onrestart這些關(guān)鍵字所對應(yīng)的行都被稱為options, options是用來描述的service一些特點(diǎn),不同的service有著不同的options。
service類型的section標(biāo)識了一個service(或者說可執(zhí)行程序),?那這個service什么時候被執(zhí)行呢?是在class_start這個命令被執(zhí)行的時候,class_start命令行總是存在于某個on類型的section中,“class_start core”這樣一條命令被執(zhí)行,就會啟動類型為core的所有service。
所以可以看出android的啟動過程主要就是on類型的section被執(zhí)行的過程。
import類型的section表示引入另外一個.rc文件,例如:
import init.test.rc
相當(dāng)包含另外一些section,?在解析完init.rc文件后繼續(xù)會調(diào)用init_parse_config_file來解析引入的.rc文件。
二、init.rc文件解析過程
?我們已經(jīng)知道init.rc的結(jié)構(gòu),應(yīng)該可以想到解析init.rc的過程就是識別一個個section的過程,將各個section的信息保存下來,然后在init.c的main()中去執(zhí)行一個個命令。?android采用雙向鏈表(關(guān)于雙向鏈表詳解見本文第三部分)來存儲section的信息,解析完成之后,會得到三個雙向鏈表action_list、service_list、import_list來分別存儲三種section的信息上。
1、init.c中調(diào)用init_parse_config_file(“/init.rc”),?代碼如下:
int init_parse_config_file(const char *fn)
{
?char *data;
?data = read_file(fn, 0);?//read_file()調(diào)用open\lseek\read?將init.rc讀出來
?if (!data) return -1;
?parse_config(fn, data);??//調(diào)用parse_config開始解析
?DUMP();
?return 0;
}
2、parse_config()代碼如下:
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;
?list_init(&import_list);
?state.priv = &import_list;
?for (;;) {
?switch (next_token(&state)) {?//next_token()根據(jù)從state.ptr開始遍歷
?case T_EOF:??//遍歷到文件結(jié)尾,然后goto解析import的.rc文件
?state.parse_line(&state, 0, 0);
?goto parser_done;
?case T_NEWLINE:?//到了一行結(jié)束
?state.line++;
?if (nargs) {
?int kw = lookup_keyword(args[0]);?//找到這一行的關(guān)鍵字
?if (kw_is(kw, SECTION)) {??//如果這是一個section的第一行??????????????????????????????????????????
?state.parse_line(&state, 0, 0);
?parse_new_section(&state, kw, nargs, args);
?} else {?//如果這不是一個section的第一行
?state.parse_line(&state, nargs, args);
?}
?nargs = 0;
?}
?break;
?case T_TEXT:??//遇到普通字符
?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);
?}
}
next_token()?解析完init.rc中一行之后,會返回T_NEWLINE,這時調(diào)用lookup_keyword函數(shù)來找出這一行的關(guān)鍵字, lookup_keyword返回的是一個整型值,對應(yīng)keyword_info[]數(shù)組的下標(biāo),keyword_info[]存放的是keyword_info結(jié)構(gòu)體類型的數(shù)據(jù),
struct {
?const char *name;?//關(guān)鍵字的名稱
?int (*func)(int nargs, char **args);?//對應(yīng)的處理函數(shù)
?unsigned char nargs;?//參數(shù)個數(shù)
?unsigned char flags;?//flag標(biāo)識關(guān)鍵字的類型,
?包括COMMAND、OPTION、SECTION
}? keyword_info
因此keyword_info[]中存放的是所有關(guān)鍵字的信息,每一項對應(yīng)一個關(guān)鍵字。
根據(jù)每一項的flags就可以判斷出關(guān)鍵字的類型,如新的一行是SECTION,就調(diào)用parse_new_section()來解析這一行,?如新的一行不是一個SECTION的第一行,那么調(diào)用state.parseline()來解析(state.parseline所對應(yīng)的函數(shù)會根據(jù)section類型的不同而不同),在parse_new_section()中進(jìn)行動態(tài)設(shè)置。
三種類型的section: service、on、import,??service對應(yīng)的state.parseline為parse_line_service,
on對應(yīng)的state.parseline為parse_line_action, import section中只有一行所以沒有對應(yīng)的state.parseline。