init進(jìn)程的執(zhí)行過程分析

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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容