Try   HackMD

Android 啟動過程 - init 進程

OverView of Content

Android 設備啟動必須經歷 3 個階段,1 BootLoader、2 Linux Kernel、3 Android 系統服務,嚴格來說 Android 系統實際上是運行在 Linux 內核之上的 服務進程,並不算真正的 操作系統

參考 Android 10 系統啟用

啟動概述

Android 的第一個啟動進程,init 的 PID 為 1,這個進程會 解析 init.rc 來建構出系統的初始運作型態,其他的系統才會相繼啟動

## 可使用 adb 查看 adb shell ps -A

  1. 啟動電源 & 系統啟動

    當電源啟動後,會從預定的地方 (PC 讀取 ROM),開始加載程序(BootLoader)到 RAM 中,並開始執行

  2. 引導程序 BootLoader

    引導程序 BootLoader 是在 Android 系統開始運行前的一個小程序,主要功能是把系統 OS 拉起 並 運行

    Loader 分為兩個部分 (Android 引導程式)

    • 第一階段,檢測外部的 RAM 以及加載第二階段的程式

    • 第二階段,設定網路、內存 等等 必要功能

      • 傳統的加載器包含兩個文件
        1. init.s 初始化堆棧,清空 BBS,調用 main() 函數

        2. main.c 初始化硬體、創建 Linux 標籤

  3. Linux 內核啟動

    當內核啟動時,設定緩存、保護儲存器、計畫列表、加載驅動 在內核完成系統設置後,它會 在系統中找 init.rc 文件啟動 init 進程

  4. init 進程啟動

    該進程主要是作 初始化 & 啟動系統屬性服務,也用來啟動 Zygote 進程

  5. Launcher App 應用(桌面應用程式)

    -啟動流程圖-

系統進程之間 - 關係

  • 先了解進程之間的關係,因為接下來會接觸到多個進程 (initzygoteserver managerservice_manager(Android DNS)、app 等等),每個進程之間 fork 的方案也不同

  • 這個章節著重講解的是 init 進程,但了解它的來源也是相當重要的,請參考下圖

init 進程概述

  • init 作為 Android 系統啟動的第一個進程,通過 解析 init.rc 來陸續啟動關鍵的系統服務進程,其有三個重點服務進程

    1. ServiceManager (類似 DNS 功能,用來查找 SystemServer 中的服務控制碼)

    2. Zygote (初始進程,加載必要 Anroid resource、啟動虛擬機 等等)

    3. SystemServer (啟動系統級別服務,像是 AMS、WMS 服務)

  • init 進程會啟動 Zygote、SystemServer 兩個進程,而 Zygote 使用 Socket 監聽 AMS、ServiceManager 使用 Binder 通訊 (詳見下圖)

    1. 創建、掛載啟動所需要的文件目錄

    2. 初始化、啟動性服務

    3. 解析 init.rc 配置文件並 啟動 Zygote 進程

      以下是每個進程使用的通訊方式,大部分都使用 binder 通訊

init 進程 - main.cpp 分析

  • 已往啟動入口是 init.cpp#main 函數,Android 10 後改為 main.cpp 啟動,接下來用 Android 10 並分為 2 個階段來說明

    1. FirstStageMain (第一階段)

    2. SecondStageMain (第二階段)

    • 來源是由 BootLoader 拉起 Kernel 後,再啟動 /init/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; ​​​​... 省略部分 ​​​​// argc: 是參數數量 ​​​​// argv: 參數 ​​​​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")) { ​​​​ // 初始化 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,但帶入不同參數)
  1. 掛載的文件都在記憶體中 (RAM),並實體文件

    使用指令 掛載目錄/文件名、檔案管理 特性 說明
    mount tmpfs 存在 RAM 中,並不持久 (tmp 的取名) 虛擬內存文件系統,它將所有的 文件存在虛擬內存中,將 tmpfs 卸載後,內部的資料就會全部消失
    mount /dev/devpts 動態掛載 devpts 為偽終端提供了一個標準,只要 pty 的主復合設備 /dev/ptmx 被開啟,就會去 /dev/pts 下動態創建一個新的 pty 設備文件
    mount proc 可在運行時修改內核參數 虛擬文件系統,可以看做內核內部數據結構的接口~
    mount sysfs Linux 2.6 內核引入 (主要針對 硬體相關參數,這就有關於 devpts 設備) 虛擬文件系統,通常被掛載在 /sys 目錄下
    • CHECKCALL

      用來檢查執行函數的返回,若執行的函數有錯誤 (返回不為 0) 就會執行 errors.emplace_back 函數,並將錯誤記錄下來

    ​​​​// 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) ​​​​ // 掛在虛擬進行信息 ​​​​ 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 用於掛載特定於供應商的分區 ​​​​ // 供應商分區的一部分,例如因為它們是以讀寫方式安裝的。 ​​​​ 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. ​​​​ // 創建產品可供讀寫的掛載(mnt) 資料夾 (product) ​​​​ 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); ​​​​ ​​​​ // 現在 tmpfs 已經掛載到 /dev 並且我們有了 /dev/kmsg,我們實際上可以 ​​​​ // 與外界對話... ​​​​ 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; ​​​​}
  2. 掛載特定分區設備

    ​​​​// first_stage_init.cpp ​​​​int FirstStageMain(int argc, char** argv) { ​​​​ ​​​​ ... 省略部分 ​​​​ ​​​​ // 掛載特定分區設備 ​​​​ if (!DoFirstStageMount(!created_devices)) { ​​​​ LOG(FATAL) << "Failed to mount required partitions early ..."; ​​​​ } ​​​​ ​​​​ ... 省略部分 ​​​​}
  3. 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); ​​​​ ... 省略部分 ​​​​}
  4. 透過 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 方法 ​​​​}
    • execv 函數

      會覆蓋當前 ELF 檔案,執行制定 ELF 檔案。當前的狀況是掛載完後,並設定主要參數,再次啟動 main.cpp

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; ​​​​... 省略部分 ​​​​// argc: 是參數數量 ​​​​// argv: 參數 ​​​​ ​​​​// 再次進入後,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(); ​​​​ } ​​​​ //載入 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; ​​​​... 省略部分 ​​​​// argc: 是參數數量 ​​​​// argv: 參數 ​​​​ ​​​​// 再次進入後,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")) { // 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); ​​​​}
  • 開始分析 init.cpp#SecondStageMain,分為 10 個階段,詳細請看註解

    1. 初始化屬性域,主要就是做 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; ​​​​​​​​}
      • 屬性服務
        它實現在 property_service.cpp 類,之後會透過 StartPropertyService 函數 啟動 (後面會說名)
    2. 清空環境變量: 清除之前寫到系統屬性中的環境變數

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

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

        第一階段主要是做載入安全策略 (SetInitAvbVersionInRecovery)

        第二階段是為了註冊一些處理器

    4. 創建 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(); ​​​​​​​​ } ​​​​​​​​ ... 省略部分 ​​​​​​​​}
      • epoll
        epoll 是 Linux 核心的可擴展 I/O 事件通知機制,其實現與 poll 類似,都是 監聽多個檔案描述子的事件 (透過紅黑樹搜尋監聽的描述子),收到事件後通知 Server (目前就是指 init 進程)
    5. 裝載子進程信號處理器: init 是一個 守護進程 (可理解為 守護線程),以該進程為主,若該 init 進程結束,其他子進程都會結束

      ​​​​​​​​// /init/init.cpp ​​​​​​​​int SecondStageMain(int argc, char** argv) { ​​​​​​​​ ... 省略部分 ​​​​​​​​ // 創建 handler 處理子進程終止信號,註冊一個信號到 epoll 監聽 (若有所改變就會對 epoll) ​​​​​​​​ // epoll 會先收到通知,再用 socket 讀取資料 ​​​​​​​​ ​​​​​​​​ // 為了防止 init 進程的子進程變成殭屍進程 ​​​​​​​​ InstallSignalFdHandler(&epoll); ​​​​​​​​ InstallInitNotifier(&epoll); ​​​​​​​​ ... 省略部分 ​​​​​​​​}
      • 殭屍進程 (zombie process)
        簡單來說就是沒在運作,卻仍佔資源的進程
        當 init 進程的子進程結束時會發送 Signal 通知,收到通知後就會移除該進程資料,若是沒有移除,則會造成資源浪費 & 到達進程限制上限時就無法再創建進程 (每個系統規定不同)

      • 系統在 init 子進程暫停、終止的時候,讓 init 會收到 SIGCHLD 信號

        ​​​​​​​​​​​​## 查看訊號 ​​​​​​​​​​​​man sigaction

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

      ​​​​​​​​// /init/init.cpp ​​​​​​​​static int property_fd = -1; ​​​​​​​​int SecondStageMain(int argc, char** argv) { ​​​​​​​​ ... 省略部分 ​​​​​​​​ // 啟動其他屬性服務 ​​​​​​​​ StartPropertyService(&property_fd); ​​​​​​​​ unsetenv("INIT_AVB_VERSION"); // 環境變數移除 INIT_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(); ​​​​​​​​ ... 省略部分 ​​​​​​​​}

      下圖透過 adb 取得目前裝置所有的屬性數量

    7. 匹配命令 & 函數之間對應關係: 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"; ​​​​​​​​ } ​​​​​​​​ ... 省略部分 ​​​​​​​​}

      GetBuiltinFunctionMap 截圖

    8. 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); ​​​​​​​​ } ​​​​​​​​}
    9. 添加觸發事件: 這邊可以看到 rc 檔的一些設定,其中常見的指令包括,1 early-init2 init3 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"); ​​​​​​​​ ... 省略部分 ​​​​​​​​}
    10. 進入循環: 不斷處理接收到的命令 & 執行 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 ​​​​​​​​ } ​​​​​​​​ ... 省略部分 ​​​​​​​​ } ​​​​​​​​}

init 進程任務

讀取 & 運行 rc 檔案

啟動屬性服務 property_service.cpp#StartPropertyService

  • StartPropertyService 方法

    1. 初始化 ro.property_service.version 屬性服務,將其值設定為 2

    2. 創建兩個 Socket

      • init.cpp 使用
      • property_service.cpp 使用
    3. 透過 CreateSocket 方法,創建 "property_service" Socket,並監聽該文件,如果有事件就會呼叫 PropertyServiceThread 方法

      • CreateSocket 方法

        /dev/socket 中創建 Socket,Socket 取名會用 ANDROID_SOCKET_{名稱}

      ​​​​​​​​// property_service.cpp ​​​​​​​​static int init_socket = -1; ​​​​​​​​static int property_set_fd = -1; ​​​​​​​​void StartPropertyService(int* epoll_socket) { ​​​​​​​​ // 設定為 2 ​​​​​​​​ InitPropertySet("ro.property_service.version", "2"); ​​​​​​​​ int sockets[2]; ​​​​​​​​ if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) { ​​​​​​​​ PLOG(FATAL) << "Failed to socketpair() between property_service and init"; ​​​​​​​​ } ​​​​​​​​ *epoll_socket = from_init_socket = sockets[0]; ​​​​​​​​ init_socket = sockets[1]; ​​​​​​​​ StartSendingMessages(); ​​​​​​​​ // 創建非堵塞 Socket (SOCK_NONBLOCK) ​​​​​​​​ if (auto result = ​​​​​​​​ CreateSocket(PROP_SERVICE_NAME, // "property_service" ​​​​​​​​ SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, ​​​​​​​​ false, 0666, 0, 0, {}); ​​​​​​​​ result.ok()) { ​​​​​​​​ // 取得 property_service FD ​​​​​​​​ property_set_fd = *result; ​​​​​​​​ } else { ​​​​​​​​ ... 創建失敗 Log ​​​​​​​​ } ​​​​​​​​ // 監聽創建的 Socket,最多為 8 個設置屬性服務的用戶提供服務 ​​​​​​​​ listen(property_set_fd, 8); ​​​​​​​​ // 創建 thread && 在需要時執行 PropertyServiceThread 方法 ​​​​​​​​ auto new_thread = std::thread{PropertyServiceThread}; ​​​​​​​​ property_service_thread.swap(new_thread); ​​​​​​​​}
    • InitPropertySet 初始化屬性服務

      ​​​​​​​​// property_service.cpp ​​​​​​​​uint32_t InitPropertySet(const std::string& name, const std::string& value) { ​​​​​​​​ uint32_t result = 0; ​​​​​​​​ // 設定 pid、uid、gid ​​​​​​​​ ucred cr = {.pid = 1, .uid = 0, .gid = 0}; ​​​​​​​​ std::string error; ​​​​​​​​ // 寫入指定屬性 ​​​​​​​​ // @ 分析 HandlePropertySet 方法 ​​​​​​​​ result = HandlePropertySet(name, ​​​​​​​​ value, ​​​​​​​​ kInitContext, ​​​​​​​​ cr, nullptr, ​​​​​​​​ &error); ​​​​​​​​ if (result != PROP_SUCCESS) { ​​​​​​​​ LOG(ERROR) << "Init cannot set '" << name << "' to '" << value << "': " << error; ​​​​​​​​ } ​​​​​​​​ return result; ​​​​​​​​}
    • 透過 adb getprop 讀取 ro.property 屬性

  • 在創建出 Socket 後,會透過 epoll 來監聽 Socket 事件 (property_set_fd),當 epoll 收到事件後會執行 PropertyServiceThread 函數

    1. 檢查 epoll#Open 結果

    2. 透過 RegisterHandler 函數 設定事件的處理函數為 handle_property_set_fd (該函數會處理用戶請求)

    3. 進入迴圈,處理等待事件

      ​​​​​​​​// property_service.cpp ​​​​​​​​ static void PropertyServiceThread() { ​​​​​​​​ Epoll epoll; ​​​​​​​​ // 1. 檢查 epoll#Open 結果 ​​​​​​​​ if (auto result = epoll.Open(); !result.ok()) { ​​​​​​​​ LOG(FATAL) << result.error(); ​​​​​​​​ } ​​​​​​​​ // 2. handle_property_set_fd 是函數指針 ​​​​​​​​ if (auto result = epoll.RegisterHandler(property_set_fd, ​​​​​​​​ handle_property_set_fd); ​​​​​​​​ !result.ok()) { ​​​​​​​​ LOG(FATAL) << result.error(); ​​​​​​​​ } ​​​​​​​​ if (auto result = epoll.RegisterHandler(init_socket, ​​​​​​​​ HandleInitSocket); !result.ok()) { ​​​​​​​​ LOG(FATAL) << result.error(); ​​​​​​​​ } ​​​​​​​​ // 3. 在 looop 中的無限循環 ​​​​​​​​ while (true) { ​​​​​​​​ auto pending_functions = epoll.Wait(std::nullopt); ​​​​​​​​ if (!pending_functions.ok()) { ​​​​​​​​ LOG(ERROR) << pending_functions.error(); ​​​​​​​​ } else { ​​​​​​​​ for (const auto& function : *pending_functions) { ​​​​​​​​ // 執行發生事件的函數指標 ​​​​​​​​ (*function)(); ​​​​​​​​ } ​​​​​​​​ } ​​​​​​​​ } ​​​​​​​​ }
    • Linux 內核中,epoll 用來替換 select

      方式 數據結構 特色
      epoll 紅黑數 查找快
      select Array 查找慢
      • epoll 概述

        是 Linux 用來處理大批量文件而改進的 poll,它能顯著提高程序在大量併發連接中,只有少量活躍的強況下的系統 CPU

設定屬性服務 property_service.cpp#handle_property_set_fd

  • 在屬性啟動後,就可以透過 epoll 監聽 Socket 的改變,並進行相對應的處理,而這裡處理的函數就是 handle_property_set_fd

    ​​​​// property_service.cpp ​​​​ static void handle_property_set_fd() { ​​​​ ​​​​ ...省略部分 ​​​​ ​​​​ SocketConnection socket(s, cr); ​​​​ ​​​​ ...省略部分 ​​​​ ​​​​ switch (cmd) { ​​​​ case PROP_MSG_SETPROP: { ​​​​ ​​​​ char prop_name[PROP_NAME_MAX]; /// 屬性名 ​​​​ char prop_value[PROP_VALUE_MAX]; // 設定值 ​​​​ ​​​​ ... 檢查屬性名、值的上限限制 ​​​​ ​​​​ prop_name[PROP_NAME_MAX-1] = 0; ​​​​ prop_value[PROP_VALUE_MAX-1] = 0; ​​​​ ​​​​ // 取得 socket 的上下文 && 檢查 ​​​​ std::string source_context; ​​​​ if (!socket.GetSourceContext(&source_context)) { ​​​​ PLOG(ERROR) << "Unable to set property '" << prop_name << "': getpeercon() failed"; ​​​​ return; ​​​​ } ​​​​ const auto& cr = socket.cred(); ​​​​ std::string error; ​​​​ ​​​​ // 透過 HandlePropertySet 設定屬性 ​​​​ // @ 查看 HandlePropertySet 方法 ​​​​ uint32_t result = ​​​​ HandlePropertySet(prop_name, prop_value, source_context, cr, nullptr, &error); ​​​​ ​​​​ // ...檢查屬性設定結果 ​​​​ break; ​​​​ } ​​​​ ​​​​ case PROP_MSG_SETPROP2: { ​​​​ ... 省略 ​​​​ break; ​​​​ } ​​​​ }
  • HandlePropertySet 函數:判斷使用者設定的屬性,並執行對應的行為,主要分為 兩種

    1. 控制屬性:屬性用 ctl 開頭

    2. 普通屬性

      ​​​​​​​​// property_service.cpp ​​​​​​​​uint32_t HandlePropertySet(const std::string& name, const std::string& value, ​​​​​​​​ const std::string& source_context, const ucred& cr, ​​​​​​​​ SocketConnection* socket, std::string* error) { ​​​​​​​​ ... 省略部分 ​​​​​​​​ // 控制屬性 `ctl` ​​​​​​​​ if (StartsWith(name, "ctl.")) { ​​​​​​​​ return SendControlMessage(name.c_str() + 4, value, cr.pid, socket, error); ​​​​​​​​ } ​​​​​​​​ // sys.powerctl 是用於使設備重新啟動的特殊屬性。 我們要登錄 ​​​​​​​​ // 任何設置此屬性以能夠準確歸咎於關閉原因的進程。 ​​​​​​​​ if (name == "sys.powerctl") { ​​​​​​​​ ... 省略部分 ​​​​​​​​ } ​​​​​​​​ ... 省略部分 ​​​​​​​​ // 普通屬性 ​​​​​​​​ // @ 分析 PropertySet 方法 ​​​​​​​​ return PropertySet(name, value, error); ​​​​​​​​}
  • PropertySet 函數:首先檢查屬性名、數據是否合法,最後再設定屬性

    • ro. 也就是 read only,ro. 開頭屬性 只能設定一次,如果進入這是 .ro 屬性則會返回錯誤
    ​​​​// property_service.cpp ​​​​static uint32_t PropertySet(const std::string& name, const std::string& value, std::string* error) { ​​​​ size_t valuelen = value.size(); ​​​​ // 判斷是否是合法的屬性名 ​​​​ if (!IsLegalPropertyName(name)) { ​​​​ *error = "Illegal property name"; ​​​​ return PROP_ERROR_INVALID_NAME; ​​​​ } ​​​​ // 判斷設定的數據是否合法 ​​​​ if (auto result = IsLegalPropertyValue(name, value); !result.ok()) { ​​​​ *error = result.error().message(); ​​​​ return PROP_ERROR_INVALID_VALUE; ​​​​ } ​​​​ // 從屬性儲存空間只到指定屬性 ​​​​ prop_info* pi = (prop_info*) __system_property_find(name.c_str()); ​​​​ if (pi != nullptr) { ​​​​ // ro. 開頭屬性只能寫入一次 ​​​​ if (StartsWith(name, "ro.")) { ​​​​ *error = "Read-only property was already set"; ​​​​ // 返回錯誤 ​​​​ return PROP_ERROR_READ_ONLY_PROPERTY; ​​​​ } ​​​​ // 設定屬性 ​​​​ __system_property_update(pi, value.c_str(), valuelen); ​​​​ } else { ​​​​ // 嘗試添加屬性 ​​​​ int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen); ​​​​ if (rc < 0) { ​​​​ *error = "__system_property_add failed"; ​​​​ return PROP_ERROR_SET_FAILED; ​​​​ } ​​​​ } ​​​​ // 以 persist. 開頭的屬性另外處理 ​​​​ if (persistent_properties_loaded && StartsWith(name, "persist.")) { ​​​​ WritePersistentProperty(name, value); ​​​​ } ​​​​ ... 省略部分 ​​​​ return PROP_SUCCESS; ​​​​}

Appendix & FAQ

tags: Android 系統 Android.bp