kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- 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`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully