--- title: 'Android 啟動過程 - init 進程' disqus: kyleAlien --- Android 啟動過程 - init 進程 === ## OverView of Content Android 設備啟動必須經歷 3 個階段,^1^ BootLoader、^2^ Linux Kernel、^3^ Android 系統服務,嚴格來說 Android 系統實際上是運行在 Linux 內核之上的 **==服務進程==,並不算真正的 操作系統** [**參考 Android 10 系統啟用**](https://segmentfault.com/a/1190000023184321) [TOC] ## 啟動概述 **Android 的第一個啟動進程,==init 的 PID 為 1==**,這個進程會 **解析 `init.rc`** 來建構出系統的初始運作型態,其他的系統才會相繼啟動 :::success ```shell= ## 可使用 adb 查看 adb shell ps -A ``` > ![](https://i.imgur.com/zOV1W3O.png) ::: 1. **啟動電源 & 系統啟動** 當電源啟動後,會從預定的地方 (PC 讀取 ROM),開始加載程序(BootLoader)到 RAM 中,並開始執行 2. **引導程序 BootLoader** 引導程序 BootLoader 是在 Android 系統開始運行前的一個小程序,**主要功能是把系統 OS 拉起 並 運行** **Loader 分為兩個部分** ([**Android 引導程式**](https://android.googlesource.com/platform/bootable/bootloader/legacy/+/refs/heads/ics-mr0-release/usbloader/)) * 第一階段,檢測外部的 RAM 以及加載第二階段的程式 * 第二階段,設定網路、內存 ... 等等 必要功能 :::info * 傳統的加載器包含兩個文件 1. `init.s` 初始化堆棧,清空 BBS,調用 main() 函數 2. `main.c` 初始化硬體、創建 Linux 標籤 > ![](https://i.imgur.com/YkakI8D.png) ::: 3. **Linux 內核啟動** 當內核啟動時,設定緩存、保護儲存器、計畫列表、加載驅動... 在內核完成系統設置後,它會 **在系統中找 `init.rc` 文件**,**並 ==啟動 init 進程==** 4. **init 進程啟動** 該進程主要是作 初始化 & 啟動系統屬性服務,也用來啟動 Zygote 進程 5. Launcher App 應用(桌面應用程式) **-啟動流程圖-** > ![](https://i.imgur.com/pKDWpUI.png) ### 系統進程之間 - 關係 * 先了解進程之間的關係,因為接下來會接觸到多個進程 (`init`、`zygote`、`server manager`、`service_manager`(Android DNS)、`app`... 等等),**每個進程之間 fork 的方案也不同** * 這個章節著重講解的是 **++init 進程++**,但了解它的來源也是相當重要的,請參考下圖 > ![](https://i.imgur.com/2jh6Kcr.png) ## 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 通訊 > > ![](https://i.imgur.com/CNawnzY.png) ### init 進程 - [main.cpp](https://cs.android.com/android/platform/superproject/+/master:system/core/init/main.cpp;l=1?q=main.cpp) 分析 * 已往啟動入口是 [**init.cpp#main**](https://android.googlesource.com/platform/system/core/+/android-7.1.1_r24/init/init.cpp#587) 函數,**Android 10 後改為 [main.cpp](https://android.googlesource.com/platform/system/core/+/master/init/main.cpp) 啟動**,接下來用 Android 10 並分為 **2 個階段來說明** 1. FirstStageMain (第一階段) 2. SecondStageMain (第二階段) :::success * 來源是由 BootLoader 拉起 Kernel 後,再啟動 `/init/main.cpp` 檔案並呼叫 `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; ... 省略部分 // 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](https://android.googlesource.com/platform/system/core/+/master/init/first_stage_init.cpp)#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 目錄下** | :::info * `CHECKCALL` 宏 用來檢查執行函數的返回,若執行的函數有錯誤 (返回不為 0) 就會執行 `errors.emplace_back` 函數,並將錯誤記錄下來 ::: ```cpp= // 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. **掛載特定分區設備** ```java= // first_stage_init.cpp int FirstStageMain(int argc, char** argv) { ... 省略部分 // 掛載特定分區設備 if (!DoFirstStageMount(!created_devices)) { LOG(FATAL) << "Failed to mount required partitions early ..."; } ... 省略部分 } ``` 3. **SELinux 相關工作** ```java= // 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 方法** ```java= // 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 方法 } ``` :::info * **`execv` 函數** **會覆蓋當前 ELF 檔案,執行制定 ELF 檔案**。當前的狀況是掛載完後,並設定主要參數,再次啟動 main.cpp ::: > ![](https://i.imgur.com/Lf2Ej8u.png) ### init 進程插曲 SE - [selinux.cpp](https://android.googlesource.com/platform/system/core/+/master/init/selinux.cpp)#SetupSelinux * 在第二階段之前會先進行 SE 設定 * 在經過 [**first_stage_init**](https://android.googlesource.com/platform/system/core/+/master/init/first_stage_init.cpp)#FirstStageMain 文件掛載後,**會透過 `execv` 函數再次啟動 main.cpp 行程,再次進入 main.cpp#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; ... 省略部分 // 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** (如上一小節說明) ```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; } ``` > ![](https://i.imgur.com/tqNEubQ.png) ### init 進程第二階段 - [init.cpp](https://android.googlesource.com/platform/system/core/+/master/init/init.cpp#587)#SecondStageMain (解析 init.rc * 經過 first_stage_init 的初始化掛載設備後,就會執行到 main.cpp 類的 **SecondStageMain 函數** (可以看做 init 進城的第二階段) ```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; ... 省略部分 // 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 函數++** ```cpp= // 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; } ``` :::success * 屬性服務 它實現在 [**property_service.cpp**](https://android.googlesource.com/platform/system/core/+/master/init/property_service.cpp) 類,之後會透過 **StartPropertyService 函數** 啟動 (後面會說名) ::: 2. 清空環境變量: **清除之前寫到系統屬性中的環境變數** ```cpp= // /init/init.cpp int SecondStageMain(int argc, char** argv) { ... 省略部分 // 清除之前寫道系統屬性中的環境變數 // Clean up our environment. unsetenv("INIT_AVB_VERSION"); unsetenv("INIT_FORCE_DEBUGGABLE"); ... 省略部分 } ``` 3. SELinux 相關工作 ```cpp= // /init/init.cpp int SecondStageMain(int argc, char** argv) { ... 省略部分 // Now set up SELinux for second stage. SelinuxSetupKernelLogging(); // 完成第二階段的 selinux 工作 SelabelInitialize(); // 註冊部分處理器 SelinuxRestoreContext(); // 回復 SELinux 儲存的 Context ... 省略部分 } ``` :::info * 在第一階段時也有處理 SELinux 的部分工作 > 第一階段主要是做載入安全策略 (SetInitAvbVersionInRecovery) > 第二階段是為了註冊一些處理器 ::: 4. 創建 epoll 事件通知 ```cpp= // /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(); } ... 省略部分 } ``` :::success * [**epoll**](https://zh.wikipedia.org/wiki/Epoll) epoll 是 Linux 核心的可擴展 I/O 事件通知機制,其實現與 poll 類似,都是 **監聽多個檔案描述子的事件** (透過紅黑樹搜尋監聽的描述子),收到事件後通知 Server (目前就是指 init 進程) ::: 5. 裝載子進程信號處理器: `init` 是一個 **守護進程** (可理解為 [**守護線程**](https://hackmd.io/4itn0slyRMWe8B5Q0s8xKQ?view#Daemon)),以該進程為主,**若該 init 進程結束,其他子進程都會結束** ```cpp= // /init/init.cpp int SecondStageMain(int argc, char** argv) { ... 省略部分 // 創建 handler 處理子進程終止信號,註冊一個信號到 epoll 監聽 (若有所改變就會對 epoll) // epoll 會先收到通知,再用 socket 讀取資料 // 為了防止 init 進程的子進程變成殭屍進程 InstallSignalFdHandler(&epoll); InstallInitNotifier(&epoll); ... 省略部分 } ``` :::warning * **殭屍進程 (zombie process)** **簡單來說就是沒在運作,卻仍佔資源的進程** 當 init 進程的子進程結束時會發送 Signal 通知,收到通知後就會移除該進程資料,若是沒有移除,則會造成資源浪費 & 到達進程限制上限時就無法再創建進程 (每個系統規定不同) * 系統在 init 子進程暫停、終止的時候,讓 init 會收到 **`SIGCHLD`** 信號 ```shell= ## 查看訊號 man sigaction ``` > ![](https://i.imgur.com/hSPEtkt.png) ::: 6. **啟動屬性服務**: 主要函數 StartPropertyService,啟動一個 socket 監聽屬性更改 ```cpp= // /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 取得目前裝置所有的屬性數量 > ![](https://i.imgur.com/4IcKOd8.png) 7. 匹配命令 & 函數之間對應關係: Cmd & Function Map,從這可以看出系統支援哪些 cmd 指令,還有指令實際運作的方法 ```cpp= // /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 截圖 > > ![](https://i.imgur.com/nITolMC.png) 8. **LoadBootScripts**: **解析 `init.rc` 檔案** ```cpp= // /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-init`、^2^ `init`、^3^ `late-init` ```cpp= // /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** ```cpp= // /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 } ... 省略部分 } } ``` > ![](https://i.imgur.com/5ktcYeF.png) ## init 進程任務 ### 讀取 & 運行 rc 檔案 * 在 init 進程的第二階段我們有說到,init 進程會讀取 `.rc` 檔案並進行分析,關於這個我有另外寫一篇 [**Android 啟動過程 - RC 檔案解析**](https://hackmd.io/zhLTuxZ-RjewJEhDuC73uQ?view) ### 啟動屬性服務 [**property_service.cpp**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/property_service.cpp)#StartPropertyService * **StartPropertyService 方法**: 1. 初始化 `ro.property_service.version` 屬性服務,將其值設定為 2 2. 創建兩個 Socket * `init.cpp` 使用 * `property_service.cpp` 使用 3. 透過 CreateSocket 方法,創建 "`property_service`" Socket,並監聽該文件,如果有事件就會呼叫 **`PropertyServiceThread` 方法** :::success * CreateSocket 方法 在 `/dev/socket` 中創建 Socket,Socket 取名會用 `ANDROID_SOCKET_{名稱}` ::: ```cpp= // 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** 初始化屬性服務 ```cpp= // 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; } ``` :::info * 透過 adb `getprop` 讀取 `ro.property` 屬性 > ![](https://i.imgur.com/WTU2zJR.png) ::: * 在創建出 Socket 後,會透過 epoll 來監聽 Socket 事件 (property_set_fd),當 epoll 收到事件後會執行 PropertyServiceThread 函數 1. 檢查 epoll#Open 結果 2. 透過 **`RegisterHandler` 函數** 設定事件的處理函數為 **`handle_property_set_fd`** (該函數會處理用戶請求) 3. 進入迴圈,處理等待事件 ```cpp= // 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)(); } } } } ``` :::success * **Linux 內核中,epoll 用來替換 select** | 方式 | 數據結構 | 特色 | | -------- | -------- | - | | epoll | 紅黑數 | 查找快 | | select | Array | 查找慢 | :::info * **epoll 概述**: 是 Linux 用來處理大批量文件而改進的 poll,它能顯著提高程序在大量併發連接中,只有少量活躍的強況下的系統 CPU ::: ::: ### 設定屬性服務 [**property_service.cpp**](https://android.googlesource.com/platform/system/core/+/master/init/property_service.cpp)#handle_property_set_fd * 在屬性啟動後,就可以透過 epoll 監聽 Socket 的改變,並進行相對應的處理,而這裡處理的函數就是 handle_property_set_fd ```cpp= // 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. **普通屬性** ```cpp= // 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 函數:首先檢查屬性名、數據是否合法,最後再設定屬性 :::warning * `ro.` 也就是 read only,`ro.` 開頭屬性 **只能設定一次**,如果進入這是 `.ro` 屬性則會返回錯誤 ::: ```cpp= // 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 :::info ::: ###### tags: `Android 系統` `Android.bp`