安卓系統(tǒng)從開機到桌面顯示是一個長而復(fù)雜的流程,本文參考安卓源碼記錄安卓啟動流程的梳理學(xué)習(xí)。(文章涉及的源碼基于 Android 10.0)由于 Android 啟動流程很長,所以分幾篇來記錄,本篇記錄安卓第一個用戶態(tài)進程 Init 進程的啟動過程。
[TOC]
Init 進程是安卓系統(tǒng)啟動的第一個用戶態(tài)進程,其 PID 為1,是由內(nèi)核態(tài)進程0 idle 啟動,Init 是啟動眾多安卓系統(tǒng)服務(wù)進程的源頭。
啟動 Init 進程的流程包括以下幾步:
- 用戶長按電源鍵,固化在ROM中的
BOOT加載引導(dǎo)程序Bootloader到 RAM 中 - 運行
Bootloader啟動 Linux 內(nèi)核系統(tǒng),啟動第一個內(nèi)核態(tài)進程idle,并初始化內(nèi)核驅(qū)動程序。 - 由
idle進程啟動Init進程,具體是執(zhí)行到system/core/Init目錄下的main.cpp
下面將梳理下 system/core/Init/main.cpp 的執(zhí)行流程。
Init 入口函數(shù)
int main(int argc, char** argv) {
56 if (!strcmp(basename(argv[0]), "ueventd")) {
57 return ueventd_main(argc, argv);
58 }
60 if (argc > 1) {
61 if (!strcmp(argv[1], "subcontext")) {
62 android::base::InitLogging(argv, &android::base::KernelLogger);
63 const BuiltinFunctionMap function_map;
64
65 return SubcontextMain(argc, argv, &function_map);
66 }
68 if (!strcmp(argv[1], "selinux_setup")) {
69 return SetupSelinux(argv);
70 }
72 if (!strcmp(argv[1], "second_stage")) {
73 return SecondStageMain(argc, argv);
74 }
75 }
77 return FirstStageMain(argc, argv);
78 }
- 傳入?yún)?shù)為
ueventd, 走eventd_main流程。 - 傳入?yún)?shù)為
subcontext, 走SubcontextMain流程。 - 傳入?yún)?shù)為
selinux_setup, 走SetupSelinux流程。 - 傳入?yún)?shù)為
second_stage, 走SecondStageMain流程。 - 默認無參數(shù), 走
FirstStageMain流程。
從 main 函數(shù)可以看出 Init 流程分為好幾個過程,由函數(shù)的入?yún)⒖刂啤?idle 進程執(zhí)行到 Init.main 時,不帶入?yún)?,即默認首先執(zhí)行 FirstStageMain 流程。(當然從函數(shù)名也能看出來)
FirstStageMain
從 main.cpp 執(zhí)行到 first_state_init.cpp 中的 FirstStageMain 函數(shù),進行 Init 的第一個過程。這個過程主要工作包括:
- 掛載設(shè)備節(jié)點
- 創(chuàng)建系統(tǒng)關(guān)鍵目錄
- 初始化 Log 系統(tǒng)
這一階段代碼代碼較多,可以自行查看源碼FirstStageMain 函數(shù)源碼。在 FirstStageMain 函數(shù)執(zhí)行結(jié)束的時候,通過 execv 命令啟動了 Init 流程第二個過程 SetupSelinux。
int FirstStageMain(int argc, char** argv) {
103 ...
116 CHECKCALL(clearenv());
117 CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
118 // Get the basic filesystem setup we need put together in the initramdisk
119 // on / and then we'll let the rc file figure out the rest.
120 CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
121 CHECKCALL(mkdir("/dev/pts", 0755));
...
168 #undef CHECKCALL
173 InitKernelLogging(argv);
236 setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
238 const char* path = "/system/bin/init";
239 const char* args[] = {path, "selinux_setup", nullptr};
240 execv(path, const_cast<char**>(args));
246 return 1;
247 }
SetupSelinux
SetupSelinux 函數(shù)在 selinux.cpp文件中,這個過程主要工作是初始化selinux、加載 selinux 規(guī)則。selinux 是 Linux 中的一個安全控制模塊,在這種訪問控制體系的限制下, 進程只能訪問那些在他的任務(wù)中所需要的文件。在 SetupSelinux 函數(shù)執(zhí)行結(jié)束的時候同樣通過 execv 命令啟動了 Init 的第三個過程 SecondStageMain。
int SetupSelinux(char** argv) {
520 InitKernelLogging(argv);
522 if (REBOOT_BOOTLOADER_ON_PANIC) {
523 InstallRebootSignalHandlers();
524 }
527 SelinuxSetupKernelLogging();
528 SelinuxInitialize();
538 const char* path = "/system/bin/init";
539 const char* args[] = {path, "second_stage", nullptr};
540 execv(path, const_cast<char**>(args));
546 return 1;
547 }
SecondStageMain
Init 第三個過程在 init.cpp 文件中,這個過程主要做了以下幾類工作:
- 初始化并啟動屬性服務(wù)
- 初始化子進程終止處理函數(shù)
- 解析
init.rc文件,在這期間啟動了system server進程 - 通過
ActionManager執(zhí)行一些開機前的準備工作,如顯示靜態(tài)的 Android 開機畫面 - 循環(huán)等待新的
Action的到來
至此,init 進程啟動過程結(jié)束。
int SecondStageMain(int argc, char** argv) {
...
623 SetStdioToDevNull(argv);
624 InitKernelLogging(argv);
643 property_init();
...
679 Epoll epoll;
680 if (auto result = epoll.Open(); !result) {
681 PLOG(FATAL) << result.error();
682 }
684 InstallSignalFdHandler(&epoll);
686 property_load_boot_defaults(load_debug_prop);
687 UmountDebugRamdisk();
688 fs_mgr_vendor_overlay_mount_all();
689 export_oem_lock_status();
690 StartPropertyService(&epoll);
691 MountHandler mount_handler(&epoll);
692 set_usb_controller();
706 LoadBootScripts(am, sm);
...
730 am.QueueBuiltinAction(
731 [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
732 for (const auto& svc : ServiceList::GetInstance()) {
733 keychords.Register(svc->keycodes());
734 }
735 keychords.Start(&epoll, HandleKeychord);
736 return Success();
737 },
738 "KeychordInit");
739 am.QueueBuiltinAction(console_init_action, "console_init");
741 // Trigger all the boot actions to get us started.
742 am.QueueEventTrigger("init");
...
744
765 while (true) {
766 // By default, sleep until something happens.
767 auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
768
769 if (do_shutdown && !shutting_down) {
770 do_shutdown = false;
771 if (HandlePowerctlMessage(shutdown_command)) {
772 shutting_down = true;
773 }
774 }
775
776 if (!(waiting_for_prop || Service::is_exec_service_running())) {
777 am.ExecuteOneCommand();
778 }
779 if (!(waiting_for_prop || Service::is_exec_service_running())) {
780 if (!shutting_down) {
781 auto next_process_action_time = HandleProcessActions();
782
783 // If there's a process that needs restarting, wake up in time for that.
784 if (next_process_action_time) {
785 epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
786 *next_process_action_time - boot_clock::now());
787 if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
788 }
789 }
790
791 // If there's more work to do, wake up again immediately.
792 if (am.HasMoreCommands()) epoll_timeout = 0ms;
793 }
794
795 if (auto result = epoll.Wait(epoll_timeout); !result) {
796 LOG(ERROR) << result.error();
797 }
798 }
799
800 return 0;
801 }
屬性服務(wù)
屬性服務(wù)類似于 Windows 中的注冊表功能,提供給上層應(yīng)用記憶一些設(shè)置屬性,并在啟動應(yīng)用時讀取并生效。由于所有進程的屬性鍵值對都存在一塊內(nèi)存中,所以對于讀寫權(quán)限的控制至關(guān)重要,避免跨進程修改屬性。
Android 將屬性鍵值對的管理統(tǒng)一交由 Init進程,其他進程不能直接修改屬性,而只能通過和 Init 進程通信來修改,這樣 Init 進程就可以根據(jù)消息來源進行權(quán)限控制。
-
Init進程通過非阻塞式Socket接收其他進程修改屬性的消息 -
Init進程通過property_set函數(shù)修改屬性 - 系統(tǒng)屬性分為普通屬性和控制屬性兩種,控制屬性用來執(zhí)行一些命令,以 ctl. 開頭。
-
Init進程調(diào)用property_set函數(shù)時,會先從屬性存儲空間查找該屬性,如果有則更新內(nèi)容,如果沒有則新增屬性。但是如果屬性是以ro.開頭,則表明是只讀屬性,函數(shù)會直接返回。如果屬性是以persist.開頭,則寫入持久化屬性。
init.rc 腳本解析
Init.rc 是由 Android 初始化語言編寫的配置腳本文件,主要有 Action、Service、Command、Option、Import 五類語句。具體語法不是本文重點。

從 init.rc 代碼中可以看到,這里會根據(jù) ro.zygote 屬性 import 不同的 init.zygotexx.rc 腳本。

在 system/core/rootdir 中可以看到總共有四個 zygote init 腳本文件,系統(tǒng)會根據(jù) ro.zygote 屬性解析執(zhí)行相應(yīng) 的文件,以啟動 zygote 進程。
總結(jié)

Init 進程啟動流程概括為以下幾點:
- Linux 內(nèi)核
idle進程在啟動過程中,調(diào)用到Init進程的main()方法。 -
main()方法分為三個過程:- 過程①,調(diào)用
first_state_init.cpp中的FirstStageMain()方法,完成設(shè)備掛載、關(guān)鍵系統(tǒng)目錄創(chuàng)建、Log初始化。 - 過程②,調(diào)用
selinux.cpp中的SetupSelinux()方法,初始化 Selinux,加載 Selinux 規(guī)則。 - 過程③,調(diào)用
init.cpp中的SecondStageMain()方法,初始化并啟動屬性服務(wù)、加載啟動相關(guān).rc腳本。在這個過程中,通過init.zygotexx.rc啟動了安卓系統(tǒng)中進程之父zygote進程。
- 過程①,調(diào)用