owned this note
owned this note
Published
Linked with GitHub
---
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
```
> 
:::
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 標籤
> 
:::
3. **Linux 內核啟動**
當內核啟動時,設定緩存、保護儲存器、計畫列表、加載驅動... 在內核完成系統設置後,它會 **在系統中找 `init.rc` 文件**,**並 ==啟動 init 進程==**
4. **init 進程啟動**
該進程主要是作 初始化 & 啟動系統屬性服務,也用來啟動 Zygote 進程
5. Launcher App 應用(桌面應用程式)
**-啟動流程圖-**
> 
### 系統進程之間 - 關係
* 先了解進程之間的關係,因為接下來會接觸到多個進程 (`init`、`zygote`、`server manager`、`service_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](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
:::
> 
### 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;
}
```
> 
### 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
```
> 
:::
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 取得目前裝置所有的屬性數量
> 
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 截圖
>
> 
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
}
... 省略部分
}
}
```
> 
## 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` 屬性
> 
:::
* 在創建出 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`