fwrite

fwrite

好好生活
twitter
github
email

Init過程

啟動概述#

Android 的第一個啟動過程,init 的 PID 為 1,這個過程會解析 init.rc 來建構出系統的初始運作型態,其他的系統才會相繼啟動
啟動電源&系統啟動
當電源啟動後,會從預定的地方(PC 讀取 ROM),開始加載程序(BootLoader)到 RAM 中,並開始執行
引導程序 BootLoader
引導程序 BootLoader 是在 Android 系統開始運行前的一個小程序,主要功能是把系統 OS 拉起並運行
Linux 內核啟動
當內核啟動時,設定快取、保護儲存器、計畫列表、加載驅動… 在內核完成系統設定後,它會在系統中找 init.rc 文件,並啟動 init 過程
init 過程啟動
該過程主要是作初始化&啟動系統屬性服務,也用來啟動 Zygote 過程
Launcher App 應用(桌面應用程序)

init 過程概述#

init 作為 Android 系統啟動的第一個過程,通過解析 init.rc 來陸續啟動關鍵的系統服務過程,其有三個重點服務過程
ServiceManager(類似 DNS 功能,用來查找 SystemServer 中的服務句柄)
Zygote(初始過程,加載必要的 Android 資源、啟動虛擬機… 等等)
SystemServer(啟動系統級別服務,像是 AMS、WMS… 服務)
init 過程會啟動 Zygote、SystemServer 兩個過程,而 Zygote 使用 Socket 監聽 AMS、ServiceManager 使用 Binder 通信
創建、掛載啟動所需要的文件目錄
初始化、啟動性服務
解析 init.rc 配置文件並啟動 Zygote 過程

init 過程 -  main.cpp  分析#

  // /init/main.cpp
  
  #include "builtins.h"
  #include "first_stage_init.h"
  #include "init.h"
  #include "selinux.h"
  #include "subcontext.h"
  #include "ueventd.h"
  
  using namespace android::init;
  
  ... 省略部分
  
  int main(int argc, char** argv) {    
      ... 省略部分
  
      // Boost prio which will be restored later
      setpriority(PRIO_PROCESS, 0, -20);
  
  
      if (!strcmp(basename(argv[0]), "ueventd")) {    // ueventd 負責創建設備節點、權限、匿名共享內存... 等等
          return ueventd_main(argc, argv);
      }
  
      if (argc > 1) {
          if (!strcmp(argv[1], "subcontext")) {
              // 初始化 Logger
              android::base::InitLogging(argv, &android::base::KernelLogger);
              const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
              return SubcontextMain(argc, argv, &function_map);
          }
          // Selinux 安全相關
          if (!strcmp(argv[1], "selinux_setup")) {
              return SetupSelinux(argv);
          }
  
          // init 過程 (第二階段)
          if (!strcmp(argv[1], "second_stage")) {
              return SecondStageMain(argc, argv);
          }
      }
  
      // 啟動 init 過程 (第一階段)
      return FirstStageMain(argc, argv);
  }

init 過程第一階段 - first_stage_init#FirstStageMain文件掛載#

第一階段主要在 1. 創建並掛載相關的文件,這裡列出幾個較為重要的文件系統,詳細功能可看註解、2. 設定傳入參數、3 執行 execv 函數(再次加載 main.cpp,但帶入不同參數)
掛載的文件都在內存中(RAM),並實體文件

使用指令挂載目錄 / 文件名、檔案管理特性說明
mounttmpfs存在 RAM 中,並不持久 (tmp 的取名)虛擬內存文件系統,它將所有的 文件存在虛擬內存中,將 tmpfs 卸載後,內部的資料就會全部消失
mount/dev/devpts動態掛載 devpts為虛擬終端提供了一個標準,只要 pty 的主複合設備 /dev/ptmx 被開啟,就會去/dev/pts 下動態創建一個新的 pty 設備文件
mountproc可在運行時修改內核參數虛擬文件系統,可以看做內核內部數據結構的接口
mountsysfsLinux 2.6 內核引入(主要針對 硬件相關參數,這就有關於 devpts 設備虛擬文件系統,通常被掛載在 /sys 目錄下
  // first_stage_init.cpp
  
  int FirstStageMain(int argc, char** argv) {
  
      if (REBOOT_BOOTLOADER_ON_PANIC) {
          InstallRebootSignalHandlers();    // init 錯誤時重啟 Boot Loader
      }
  
      // 記錄啟動時間
      boot_clock::time_point start_time = boot_clock::now();
      // 記錄錯誤的列表
      std::vector<std::pair<std::string, int>> errors;
  
      
  #define CHECKCALL(x) \
      if ((x) != 0) errors.emplace_back(#x " failed", errno);
  
      // 清除 umask,之後創建的文件、資料夾擁有全部權限
      umask(0);
      CHECKCALL(clearenv());
      CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
      // 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.
      // mount 用來掛載 tmpfs 文件系統
      CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
  
      // 創建資料夾,並賦予權限
      CHECKCALL(mkdir("/dev/pts", 0755));
      CHECKCALL(mkdir("/dev/socket", 0755));
      CHECKCALL(mkdir("/dev/dm-user", 0755));
  
      CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
  
  #define MAKE_STR(x) __STRING(x)
      // 掛載proc
      CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
  #undef MAKE_STR
      // Don't expose the raw commandline to unprivileged processes.
      CHECKCALL(chmod("/proc/cmdline", 0440));
      std::string cmdline;
      android::base::ReadFileToString("/proc/cmdline", &cmdline);
      // Don't expose the raw bootconfig to unprivileged processes.
      chmod("/proc/bootconfig", 0440);
      std::string bootconfig;
      android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
  
      // 8.0 增加了用戶群組
      gid_t groups[] = {AID_READPROC};
      // 將群組加入到目前過程的設備中
      CHECKCALL(setgroups(arraysize(groups), groups));
      
      // 掛載 sys,使用sysfs 管理文檔
      CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
      CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
  
      // 創建節點 (mknod) 用於輸出 Log
      CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
      if constexpr (WORLD_WRITABLE_KMSG) {
          CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
      }
      CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
      CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
      // This is needed for log wrapper, which gets called before ueventd runs.
      CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
      CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
      
      // See storage config details athttp://source.android.com/devices/storage/
      // 掛載 /mnt/{vendor,product},使用 tmpfs 管理
      CHECKCALL(mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                      "mode=0755,uid=0,gid=1000"));
      
      // /mnt/vendor 用於掛載vendor
      CHECKCALL(mkdir("/mnt/vendor", 0755));
      // /mnt/product is used to mount product-specific partitions that can not be
      // part of the product partition, e.g. because they are mounted read-write.
      CHECKCALL(mkdir("/mnt/product", 0755));
      // /debug_ramdisk is used to preserve additional files from the debug ramdisk
      CHECKCALL(mount("tmpfs", "/debug_ramdisk", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                      "mode=0755,uid=0,gid=0"));
      // /second_stage_resources is used to preserve files from first to second
      // stage init
      CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
                      "mode=0755,uid=0,gid=0"))
  #undef CHECKCALL
      SetStdioToDevNull(argv);
    
      InitKernelLogging(argv);
  
      // 列出錯誤信息
      if (!errors.empty()) {
          for (const auto& [error_string, error_errno] : errors) {
              LOG(ERROR) << error_string << " " << strerror(error_errno);
          }
          LOG(FATAL) << "Init encountered errors starting first stage, aborting";
      }
  
      ... 省略部分
  
      return 1;
  }

掛載特定分區設備

  // first_stage_init.cpp
  
  int FirstStageMain(int argc, char** argv) {
      
      ... 省略部分
          
      // 掛載特定分區設備
      if (!DoFirstStageMount(!created_devices)) {
          LOG(FATAL) << "Failed to mount required partitions early ...";
      }
      
      ... 省略部分
  }

SELinux 相關工作

  // first_stage_init.cpp
  
  int FirstStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      struct stat new_root_info;
      if (stat("/", &new_root_info) != 0) {
          PLOG(ERROR) << "Could not stat(\"/\"), not freeing ramdisk";
          old_root_dir.reset();
      }
      if (old_root_dir && old_root_info.st_dev != new_root_info.st_dev) {
          FreeRamdisk(old_root_dir.get(), old_root_info.st_dev);
      }
      
      // 初始化安全框架: Android Verified Boot & AVB 主要用於防止系統文件被篡改
      // 也防止系統回滾的功能
      SetInitAvbVersionInRecovery();
      setenv(kEnvFirstStageStartedAt, std::to_string(start_time.time_since_epoch().count()).c_str(),
             1);
  
      ... 省略部分
  
  
  }

透過 execv 啟動 init 過程的 SecondStageMain 方法

  // first_stage_init.cpp
  
  int FirstStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      // 設定 init 過程參數
      const char* path = "/system/bin/init";
      const char* args[] = {path, "selinux_setup", nullptr};
      // Binder open (log 訊息)
      auto fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
      dup2(fd, STDOUT_FILENO);
      dup2(fd, STDERR_FILENO);
      close(fd);
  
      // 傳入上面的參數 args
      // 啟動 init 過程,傳入參數為 "selinux_setup"
      execv(path, const_cast<char**>(args));        // 透過 execv 啟動 init 過程 的 SecondStageMain 方法
  
  
  }

init 過程插曲 SE#

selinux.cpp#SetupSelinux
在第二階段之前會先進行 SE 設定
在經過first_stage_init#FirstStageMain文件掛載後,會透過 execv 函數再次啟動 main.cpp 行程,再次進入main.cpp#main方法

  // /init/main.cpp 檔案
  
  #include "builtins.h"
  #include "first_stage_init.h"
  #include "init.h"
  #include "selinux.h"
  #include "subcontext.h"
  #include "ueventd.h"
  
  using namespace android::init;
  
  ... 省略部分
  
  // 再次進入後,argv 就是帶 selinux_setup 字串
  int main(int argc, char** argv) {    
      ... 省略部分
  
      // Boost prio which will be restored later
      setpriority(PRIO_PROCESS, 0, -20);
  
      // 判斷傳入參數
      if (!strcmp(basename(argv[0]), "ueventd")) {    // ueventd 負責創建設備節點、權限、匿名共享內存等
          return ueventd_main(argc, argv);
      }
  
      // 當參數數量 > 1 時
      if (argc > 1) {
          if (!strcmp(argv[1], "subcontext")) {
              // 初始化 Log
              android::base::InitLogging(argv, &android::base::KernelLogger);
              const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
              return SubcontextMain(argc, argv, &function_map);
          }
          // Selinux 安全相關
          if (!strcmp(argv[1], "selinux_setup")) {
              return SetupSelinux(argv);
          }
  
          // init 過程 (第二階段)
          if (!strcmp(argv[1], "second_stage")) {
              return SecondStageMain(argc, argv);
          }
      }
  
      // 啟動 init 過程 (第一階段)
      return FirstStageMain(argc, argv);
  }

SetupSelinux 主要是在加載 SE 策略,加載 SE 完成後,就會設定新參數,再次透過 execv 啟動 main.cpp(如上一小節說明)

  // /init/selinux.cpp
  
  int SetupSelinux(char** argv) {
  
      SetStdioToDevNull(argv);
  
      // 1. 初始化核心的 Log
      InitKernelLogging(argv);
      if (REBOOT_BOOTLOADER_ON_PANIC) {
          InstallRebootSignalHandlers();
      }
      boot_clock::time_point start_time = boot_clock::now();
  
      // 2. 掛載遺失的系統區塊
      MountMissingSystemPartitions();
      SelinuxSetupKernelLogging();
      LOG(INFO) << "Opening SELinux policy";
      PrepareApexSepolicy();
  
      // Read the policy before potentially killing snapuserd.
      std::string policy;
      // 讀取策略
      ReadPolicy(&policy);
  
      CleanupApexSepolicy();
      auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
      if (snapuserd_helper) {
          // Kill the old snapused to avoid audit messages. After this we cannot
          // read from /system (or other dynamic partitions) until we call
          // FinishTransition().
          snapuserd_helper->StartTransition();
      }
  
      //Load SE 策略
      LoadSelinuxPolicy(policy);
  
      ... 省略部分
  
      const char* path = "/system/bin/init";
      const char* args[] = {path, "second_stage", nullptr};
  
      // 再次啟動該過程
      execv(path, const_cast<char**>(args));
  
      return 1;
  }

init 過程第二階段#

init.cpp#SecondStageMain 解析 init.rc
經過 first_stage_init 的初始化掛載設備後,就會執行到 main.cpp 類的 SecondStageMain 函數(可以看做 init 進城的第二階段)

  // /init/main.cpp 檔案
  
  #include "builtins.h"
  #include "first_stage_init.h"
  #include "init.h"
  #include "selinux.h"
  #include "subcontext.h"
  #include "ueventd.h"
  
  using namespace android::init;
  
  ... 省略部分
  
  // 再次進入後,argv 就是帶 second_stage 字串
  int main(int argc, char** argv) {    
      ... 省略部分
  
      // Boost prio which will be restored later
      setpriority(PRIO_PROCESS, 0, -20);
  
      // 判斷傳入參數
      if (!strcmp(basename(argv[0]), "ueventd")) {   
          return ueventd_main(argc, argv);
      }
  
      if (argc > 1) {
          if (!strcmp(argv[1], "subcontext")) {
              // 初始化 Log
              android::base::InitLogging(argv, &android::base::KernelLogger);
              const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
              return SubcontextMain(argc, argv, &function_map);
          }
          // Selinux 安全相關
          if (!strcmp(argv[1], "selinux_setup")) {
              return SetupSelinux(argv);
          }
  
          // init 過程 (第二階段)
          if (!strcmp(argv[1], "second_stage")) {
              return SecondStageMain(argc, argv);
          }
      }
  
      // 啟動 init 過程 (第一階段段)
      return FirstStageMain(argc, argv);
  }

開始分析init.cpp#SecondStageMain,分為 10 個階段,詳細請看註解
初始化屬性域,主要就是做 PropertyInit 函數

  // init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
      if (REBOOT_BOOTLOADER_ON_PANIC) {
          InstallRebootSignalHandlers();        // init 失敗時重新啟動 Boot Loader
      }
  
  
      // 啟動時間
      boot_clock::time_point start_time = boot_clock::now();
  
      trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
  
      // 把標準輸入、標準輸出、錯誤重新定向到空設備文件中 `/dev/null`
      SetStdioToDevNull(argv);
  
      // 初始化 kernel log 系統
      InitKernelLogging(argv);
      
      // 初始化系統 $PATH 路徑
      if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
          PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage";
      }
  
      // init 過程並不依賴其他過程,所以不會 Crush,所以不該收到信號
      // 所以這邊重新設定收到信號時要處理的函數為null
      {
          // sigaction 就是信號
          struct sigaction action = {.sa_flags = SA_RESTART};
          action.sa_handler = [](int) {};
          // 忽略 SIGPIPE 信號
          sigaction(SIGPIPE, &action, nullptr);    // 將 SIGPIPE 置 為 null
      }
      // Set init and its forked children's oom_adj.
      if (auto result =
                  WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));
          !result.ok()) {
          LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
                     << " to /proc/1/oom_score_adj: " << result.error();
      }
      // Set up a session keyring that all processes will have access to. It
      // will hold things like FBE encryption keys. No process should override
      // its session keyring.
      // 調用 syscall 設定相關參數
      keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
  
      // Indicate that booting is in progress to background fw loaders, etc.
      // 創建 /dev/.booting 文件,標記 booting 正在進行中
      close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
  
      ... 省略部分
  
      PropertyInit();        // 實現在 init/property_service.cpp
  
      ... 省略部分
  
      return 0;
  }

清空環境變數:清除之前寫到系統屬性中的環境變數

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
      ... 省略部分
  
      // 清除之前寫到系統屬性中的環境變數
      // Clean up our environment.
      unsetenv("INIT_AVB_VERSION");
      unsetenv("INIT_FORCE_DEBUGGABLE");
  
      ... 省略部分
  }

SELinux 相關工作

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
      ... 省略部分
  
      // Now set up SELinux for second stage.
      SelinuxSetupKernelLogging();        // 完成第二階段的 selinux 工作
      SelabelInitialize();                // 註冊部分處理器
      SelinuxRestoreContext();            // SELinux存儲的 Context
  
      ... 省略部分
  }

創建 epoll 事件通知

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      Epoll epoll;
      // epoll.Open() 創建子進程 I/O 監聽
      if (auto result = epoll.Open(); !result.ok()) {
          PLOG(FATAL) << result.error();
      }
  
      ... 省略部分
  
  }

裝載子進程信號處理器:init 是一個守護進程(可理解為守護線程),以該進程為主,若該 init 過程結束,其他子進程都會結束

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      // 創建 handler 處理子進程終止信號,註冊一個信號到 epoll 監聽(若有所改變就會 epoll)
      // epoll 會先收到通知,再用 socket 讀取資料
          
      // 為了防止 init 過程的子進程變成殭屍進程
      InstallSignalFdHandler(&epoll);
      InstallInitNotifier(&epoll);
  
      ... 省略部分
  
  }

啟動屬性服務:主要函數 StartPropertyService,啟動一個 socket 監聽屬性更改

  // /init/init.cpp
  static int property_fd = -1;
  
  int SecondStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      // 啟動其他屬性服務
      StartPropertyService(&property_fd);
  
      unsetenv("INIT_AVB_VERSION");    // 環境變數移除IT_AVB_VERSION 
      fs_mgr_vendor_overlay_mount_all();
      export_oem_lock_status();        // 最終決定"ro.boot.flash.locked" 的值
  
      MountHandler mount_handler(&epoll);
  
      // 為 USB 存儲設置 UDC Controller (sys/class/udc)
      SetUsbController();
  
      ... 省略部分
  }

匹配命令 & 函數之間對應關係:Cmd & Function Map,從這可以看出系統支持哪些 cmd 指令,還有指令實際運作的方法

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      // 創建 Function & Cmd 的 Map
      const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
  
      // 在 Action 中保存 function_map 對象
      // Action::set_function_map,`::` 相當於 static Function
      Action::set_function_map(&function_map);
      if (!SetupMountNamespaces()) {
          PLOG(FATAL) << "SetupMountNamespaces failed";
      }
  
      ... 省略部分
  }

LoadBootScripts: 解析 init.rc 檔案

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... 省略部分
      
      // 之後會使用該對象取得分析結果
      ActionManager& am = ActionManager::GetInstance();    
      ServiceList& sm = ServiceList::GetInstance();
  
      // 解析腳本
      LoadBootScripts(am, sm);
  
      ... 省略部分
  
  }
  
  // 解析 .rc 腳本
  static void LoadBootScripts(ActionManager& action_manager, 
                              ServiceList& service_list) {
      // 創建解析器
      Parser parser = CreateParser(action_manager, service_list);
  
      // 預設解析 ro.boot.init_rc
      std::string bootscript = GetProperty("ro.boot.init_rc", "");
  
      if (bootscript.empty()) {
          // 解析指定 init.rc 檔案
          parser.ParseConfig("/system/etc/init/hw/init.rc");
  
          if (!parser.ParseConfig("/system/etc/init")) {
              late_import_paths.emplace_back("/system/etc/init");
          }
  
          parser.ParseConfig("/system_ext/etc/init");
          if (!parser.ParseConfig("/vendor/etc/init")) {
              late_import_paths.emplace_back("/vendor/etc/init");
          }
          if (!parser.ParseConfig("/odm/etc/init")) {
              late_import_paths.emplace_back("/odm/etc/init");
          }
          if (!parser.ParseConfig("/product/etc/init")) {
              late_import_paths.emplace_back("/product/etc/init");
          }
      } else {
          parser.ParseConfig(bootscript);
      }
  }

添加觸發事件:這邊可以看到 rc 檔的一些設定,其中常見的指令包括,1 early-init、2 init、3 late-init

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      // ActionManager 解析完成
      am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
      am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
      am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
      am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
      
      // 觸發標籤 early-init
      am.QueueEventTrigger("early-init");
      // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
      am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
      // ... so that we can start queuing up actions that require stuff from /dev.
      am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
      Keychords keychords;
      am.QueueBuiltinAction(
              [&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
                  for (const auto& svc : ServiceList::GetInstance()) {
                      keychords.Register(svc->keycodes());
                  }
                  keychords.Start(&epoll, HandleKeychord);
                  return {};
              },
              "KeychordInit");
      // Trigger all the boot actions to get us started.
      am.QueueEventTrigger("init");
      // Don't mount filesystems or start core system services in charger mode.
      std::string bootmode = GetProperty("ro.bootmode", "");
      if (bootmode == "charger") {
          am.QueueEventTrigger("charger");
      } else {
          am.QueueEventTrigger("late-init");
      }
      // Run all property triggers based on current state of the properties.
      am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
  
      ... 省略部分
  }

進入循環:不斷處理接收到的命令 & 執行 Service

  // /init/init.cpp
  
  int SecondStageMain(int argc, char** argv) {
  
      ... 省略部分
  
      while(true) {
          // By default, sleep until something happens.
          auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
  
          // 確定是否有收到關機指令
          auto shutdown_command = shutdown_state.CheckShutdown();
          
          if (shutdown_command) {
              HandlePowerctlMessage(*shutdown_command);
              shutdown_state.set_do_shutdown(false);
          }
  
          ... 省略部分
  
          // 不必等待 && Service 沒有正在 running || Service 是運行完畢
          if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
              am.ExecuteOneCommand();    // 執行一個 cmd
          }
  
          ... 省略部分
      }
  
  }
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。