安卓啟動流程梳理之 Init 進程

安卓系統(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 進程的流程包括以下幾步:

  1. 用戶長按電源鍵,固化在ROM中的 BOOT 加載引導(dǎo)程序Bootloader到 RAM 中
  2. 運行 Bootloader 啟動 Linux 內(nèi)核系統(tǒng),啟動第一個內(nèi)核態(tài)進程 idle ,并初始化內(nèi)核驅(qū)動程序。
  3. 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ī)則。selinuxLinux 中的一個安全控制模塊,在這種訪問控制體系的限制下, 進程只能訪問那些在他的任務(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 五類語句。具體語法不是本文重點。

zygote 腳本解析入口

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

zygote 腳本

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

總結(jié)

Init 進程啟動粗略時序圖

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

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

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