---
title: 'Android 啟動過程 - RC 檔案解析'
disqus: kyleAlien
---
Android 啟動過程 - RC 檔案解析
===
## OverView of Content
這個篇章專門描述 Rc 檔案的解析,如果要知道系統是在 何時開始分析 RC 檔案請看 [**系統啟動篇**](https://hackmd.io/pJpAeKAeRea0VIW3oI-mXw?view)
[TOC]
## 概述
在 init.cpp 檔案開始執行後,就會開始分析 rc 檔案
```cpp=
// /init/init.cpp
int SecondStageMain(int argc, char** argv) {
... 省略部分
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
// 從這裡開始分析
LoadBootScripts(am, sm);
... 省略部分
}
```
### [init](https://cs.android.com/android/platform/superproject/+/master:system/core/init/init.cpp;l=1?q=init.cpp&sq=) - 讀取 init.rc 檔案
* 在上面的啟動流程中有提到會呼叫 **==LoadBootScripts== 函數,而就是這個函數來執行分析 init.rc 檔** (有多個 .rc 檔需要執行)
:::info
* init.rc 是一個相當重要的配置文件, `.rc` 是 Android 自身的語言。透過分析 init.rc 腳本來啟動不同的命令 (其他小節會說明 RC 語法)
:::
| rc 相關文件/路徑 | 功能 | 其它 |
| -------- | -------- | - |
| `/init.${ro.hardware}.rc` | 硬體廠商提供的主配置文件 | `/system/etc/init/hw/init.rc` |
| `/system/etc/init` | 核心系統模塊配置 | |
| `/vendor/etc/init` | 廠商提供的配置文件 | |
| `/odm/etc/init` | 設備製造商提供的配置文件 | |
```cpp=
// /init/init.cpp 檔案
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
// 創建解析器
Parser parser = CreateParser(action_manager, service_list);
// 取得 bootscript
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
// /system/etc/init/hw/init.rc 硬體廠商提供的主配置文件
parser.ParseConfig("/system/etc/init/hw/init.rc");
// /system/etc/init/ 用於核心系統項,例如 SurfaceFlinger、MediaService 和 logd。
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
// late_import is available only in Q and earlier release. As we don't
// have system_ext in those versions, skip late_import for system_ext.
parser.ParseConfig("/system_ext/etc/init");
// /vendor/etc/init/
// 用於 SoC 供應商項目,例如核心 SoC 功能所需的操作或守護進程。
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
// /odm/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);
}
}
```
1. **創建解析器 CreateParser 物件**
```cpp=
// /init/init.cpp 檔案
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
// 創建 Parser 物件
Parser parser;
// 分析 service & on & import
// ServiceParser 解析 service
parser.AddSectionParser("service", std::make_unique<ServiceParser>(
&service_list, GetSubcontext(), std::nullopt));
// ActionParser 解析 on
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
// ImportParser 解析 import
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;
}
```
2. 開始解析 rc 檔案: [**ParseConfig**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/parser.cpp) 會分辨路徑是檔案還是資料夾,**最終都會都透 `ParseConfigFile` 函數分析**
```cpp=
// /init/parser.cpp
bool Parser::ParseConfig(const std::string& path) {
// 判斷 path 是否為資料夾
if (is_dir(path.c_str())) {
// 解析資料夾,最終仍要調用 ParseConfigFile 函數
return ParseConfigDir(path);
}
// @ 分析 ParseConfigFile 方法
return ParseConfigFile(path);
}
// 解析資料夾並抓取需要檔案進行分析
bool Parser::ParseConfigDir(const std::string& path) {
LOG(INFO) << "Parsing directory " << path << "...";
std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);
if (!config_dir) {
PLOG(INFO) << "Could not import directory '" << path << "'";
return false;
}
dirent* current_file;
std::vector<std::string> files; /// 遞迴找到需要處理的文件,找到後放入 vector 對列
while ((current_file = readdir(config_dir.get()))) {
// Ignore directories and only process regular files.
// 處理指定類型檔案
if (current_file->d_type == DT_REG) {
std::string current_path =
android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name);
files.emplace_back(current_path);
}
}
// Sort first so we load files in a consistent order (bug 31996208)
std::sort(files.begin(), files.end());
for (const auto& file : files) {
// 將取得的檔案進行分析
// @ 查看 ParseConfigFile
if (!ParseConfigFile(file)) {
LOG(ERROR) << "could not import file '" << file << "'";
}
}
return true;
}
```
3. **ParseConfigFile 方法**:解析指定檔案
```java=
// /init/parser.cpp
// 解析指定檔案
bool Parser::ParseConfigFile(const std::string& path) {
LOG(INFO) << "Parsing file " << path << "...";
android::base::Timer t;
// 讀取路近的文件內容
auto config_contents = ReadFile(path);
if (!config_contents.ok()) {
// 讀取失敗
LOG(INFO) << "Unable to read config file '" << path << "': " << config_contents.error();
return false;
}
// 解析獲取的字串
// @ 追蹤 ParseData 方法
ParseData(path, &config_contents.value());
LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";
return true;
}
```
### 解析 rc 文本 - [ParseData](https://cs.android.com/android/platform/superproject/+/master:system/core/init/parser.cpp)
* ParseData 透過判斷符號來解析 rc 文檔,
> 
```cpp=
// /init/parser.cpp
void Parser::ParseData(const std::string& filename, std::string* data) {
data->push_back('\n');
data->push_back('\0');
parse_state state;
state.line = 0;
state.ptr = data->data();
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
int section_start_line = -1;
std::vector<std::string> args;
// If we encounter a bad section start, there is no valid parser object to parse the subsequent
// sections, so we must suppress errors until the next valid section is found.
bool bad_section_found = false;
auto end_section = [&] {
bad_section_found = false;
if (section_parser == nullptr) return;
if (auto result = section_parser->EndSection(); !result.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
}
section_parser = nullptr;
section_start_line = -1;
};
for (;;) {
switch (next_token(&state)) {
// EOF 文件結束符號
case T_EOF:
end_section();
for (const auto& [section_name, section_parser] : section_parsers_) {
section_parser->EndFile();
}
return;
// 換行
case T_NEWLINE: {
state.line++;
// 無參數返回
if (args.empty()) break;
// If we have a line matching a prefix we recognize, call its callback and unset any
// current section parsers. This is meant for /sys/ and /dev/ line entries for
// uevent.
auto line_callback = std::find_if(
line_callbacks_.begin(), line_callbacks_.end(),
[&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });
if (line_callback != line_callbacks_.end()) {
end_section();
if (auto result = line_callback->second(std::move(args)); !result.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
}
} else if (section_parsers_.count(args[0])) {
// 分析 action、service
end_section();
section_parser = section_parsers_[args[0]].get();
section_start_line = state.line;
// 分析到 Section
// @ 1. 追蹤 ParseSection 方法
if (auto result =
section_parser->ParseSection(std::move(args), filename, state.line);
!result.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
section_parser = nullptr;
bad_section_found = true;
}
} else if (section_parser) {
// 分析 section 中的參數
// @ 2. 查看 ParseLineSection
if (auto result = section_parser->ParseLineSection(std::move(args), state.line);
!result.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
}
} else if (!bad_section_found) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line
<< ": Invalid section keyword found";
}
args.clear();
break;
}
case T_TEXT:
args.emplace_back(state.text);
break;
}
}
}
```
### ParseSection 解析並創建 Service
* [**service_parser**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/service_parser.cpp)#**ParseSection** 方法:分析 `init.rc` 檔案中的 Service 標籤,Service 必須參數,並創建 Service 對象
* 最後會在 Native 層創建 [**Service**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/service.h) 對象,並保存到 **service_parser** 中
```cpp=
// /init/service_parser.cpp
Result<void> ServiceParser::ParseSection(
std::vector<std::string>&& args,
const std::string& filename,
int line)
{
// 基礎要有三個以上的參數 service、name、path、options...
if (args.size() < 3) {
return Error() << "services must have a name and a program";
}
// 服務名稱
const std::string& name = args[1];
if (!IsValidName(name)) {
return Error() << "invalid service name '" << name << "'";
}
filename_ = filename;
Subcontext* restart_action_subcontext = nullptr;
if (subcontext_ && subcontext_->PathMatchesSubcontext(filename)) {
restart_action_subcontext = subcontext_;
}
// 取得參數
std::vector<std::string> str_args(args.begin() + 2, args.end());
// P & Q 版本有特殊的設置
if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_P__) {
if (str_args[0] == "/sbin/watchdogd") {
str_args[0] = "/system/bin/watchdogd";
}
}
if (SelinuxGetVendorAndroidVersion() <= __ANDROID_API_Q__) {
if (str_args[0] == "/charger") {
str_args[0] = "/system/bin/charger";
}
}
// 創建 Service 對象
service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args, from_apex_);
return {};
}
```
### ParseLineSection 處理 section 參數 - 添加 Socket
* 舉例:以下為 Zygoto 啟動的 .rc 檔案,其中的 Section 就是 `priority`、`class`、`priority`、`user`、`socket`、`group` ... 等等
```shell=
# /rootdir/init.zygote64_32.rc
# 1. Service Name = zygote
# 2. Path = system/bin/app_process64,app_process64 是資料夾
# 3. Argument:
# -Xzygote /system/bin --zygote --start-system-server
# 主 zygote
service zygote /system/bin/app_process64 \
-Xzygote /system/bin --zygote --start-system-server
# 4. 分類為 main 群組
class main
priority -20
user root
group root readproc reserved_disk
## 啟動兩個 Socket,權限為 660
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
...
```
* ParseLineSection 函數是用來解析 Section 中的參數,並在 Map 中尋找、執行它們對應的函數
```cpp=
// service_parser.cpp
Result<void> ServiceParser::ParseLineSection(std::vector<std::string>&& args, int line) {
if (!service_) {
return {};
}
// @ 查看 GetParserMap
auto parser = GetParserMap().Find(args); // 尋找 string 對應數據
if (!parser.ok()) return parser.error();
// 調用函數
return std::invoke(*parser, this, std::move(args));
}
```
* 該類中保存一個函數對照 Map 表 (`GetParserMap`),其中就包含常見的 **Socket**
```cpp=
// service_parser.cpp
const KeywordMap<ServiceParser::OptionParser>& ServiceParser::GetParserMap() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
// clang-format off
static const KeywordMap<ServiceParser::OptionParser> parser_map = {
{"capabilities", {0, kMax, &ServiceParser::ParseCapabilities}},
{"class", {1, kMax, &ServiceParser::ParseClass}},
{"console", {0, 1, &ServiceParser::ParseConsole}},
{"critical", {0, 2, &ServiceParser::ParseCritical}},
{"disabled", {0, 0, &ServiceParser::ParseDisabled}},
{"enter_namespace", {2, 2, &ServiceParser::ParseEnterNamespace}},
{"file", {2, 2, &ServiceParser::ParseFile}},
{"group", {1, NR_SVC_SUPP_GIDS + 1, &ServiceParser::ParseGroup}},
{"interface", {2, 2, &ServiceParser::ParseInterface}},
{"ioprio", {2, 2, &ServiceParser::ParseIoprio}},
{"keycodes", {1, kMax, &ServiceParser::ParseKeycodes}},
{"memcg.limit_in_bytes", {1, 1, &ServiceParser::ParseMemcgLimitInBytes}},
{"memcg.limit_percent", {1, 1, &ServiceParser::ParseMemcgLimitPercent}},
{"memcg.limit_property", {1, 1, &ServiceParser::ParseMemcgLimitProperty}},
{"memcg.soft_limit_in_bytes",
{1, 1, &ServiceParser::ParseMemcgSoftLimitInBytes}},
{"memcg.swappiness", {1, 1, &ServiceParser::ParseMemcgSwappiness}},
{"namespace", {1, 2, &ServiceParser::ParseNamespace}},
{"oneshot", {0, 0, &ServiceParser::ParseOneshot}},
{"onrestart", {1, kMax, &ServiceParser::ParseOnrestart}},
{"oom_score_adjust", {1, 1, &ServiceParser::ParseOomScoreAdjust}},
{"override", {0, 0, &ServiceParser::ParseOverride}},
{"priority", {1, 1, &ServiceParser::ParsePriority}},
{"reboot_on_failure", {1, 1, &ServiceParser::ParseRebootOnFailure}},
{"restart_period", {1, 1, &ServiceParser::ParseRestartPeriod}},
{"rlimit", {3, 3, &ServiceParser::ParseProcessRlimit}},
{"seclabel", {1, 1, &ServiceParser::ParseSeclabel}},
{"setenv", {2, 2, &ServiceParser::ParseSetenv}},
{"shutdown", {1, 1, &ServiceParser::ParseShutdown}},
{"sigstop", {0, 0, &ServiceParser::ParseSigstop}},
// 查看 socket 對應函數 ParseSocket
{"socket", {3, 6, &ServiceParser::ParseSocket}},
{"stdio_to_kmsg", {0, 0, &ServiceParser::ParseStdioToKmsg}},
{"task_profiles", {1, kMax, &ServiceParser::ParseTaskProfiles}},
{"timeout_period", {1, 1, &ServiceParser::ParseTimeoutPeriod}},
{"updatable", {0, 0, &ServiceParser::ParseUpdatable}},
{"user", {1, 1, &ServiceParser::ParseUser}},
{"writepid", {1, kMax, &ServiceParser::ParseWritepid}},
};
// clang-format on
return parser_map;
}
```
* **ParseSocket 函數**:創建 Socket 描述符,傳入 `ParseSocket` 的參數格式如 `name type perm [ uid gid context ]`
> 我們假設傳入的 String 為 `socket zygote stream 660 root system`
| 位置 | 參數 | Socket 儲存元素 | 其他 |
| - | -------- | -------- | -------- |
| [1] | zygote | `socket.name` | |
| [2] | stream | `socket.type` | 後面可使用 `+` 號串接其他設定 |
| [3] | 660 | `socket.perm` | perm 是權限 permission |
| [4] | root | `socket.uid` | |
| [5] | system | `socket.gid` | |
:::warning
* Socket 描述符在同一個服務中不可重複 !
:::
```cpp=
// name type perm [ uid gid context ]
Result<void> ServiceParser::ParseSocket(std::vector<std::string>&& args) {
// 創建一個 Socket 檔案描述符號
SocketDescriptor socket;
socket.name = std::move(args[1]); // 命名為 "socket"
// 判斷第一個 type
auto types = Split(args[2], "+"); // 使用 "+" 號區分
if (types[0] == "stream") {
socket.type = SOCK_STREAM; // 設定該 Socket 類型
} else if ...省略部分
//循環處理第一個之後的 type 設定 (當前沒有... 可忽略
for (size_t i = 1; i < types.size(); i++) {
if (types[i] == "passcred") {
socket.passcred = true;
} else if (types[i] == "listen") {
socket.listen = true;
} else {
... 省略 err 提示
}
}
errno = 0;
char* end = nullptr;
// 權限設定
socket.perm = strtol(args[3].c_str(), &end, 8);
... 省略部分
if (args.size() > 4) {
auto uid = DecodeUid(args[4]);
... 省略錯誤
socket.uid = *uid;
}
if (args.size() > 5) {
auto gid = DecodeUid(args[5]);
... 省略錯誤
socket.gid = *gid;
}
// 當前沒有
socket.context = args.size() > 6 ? args[6] : "";
auto old = std::find_if(service_->sockets_.begin(), service_->sockets_.end(),
[&socket](const auto& other) { return socket.name == other.name; });
// 判斷是否重複 Socket 描述符
if (old != service_->sockets_.end()) {
return Error() << "duplicate socket descriptor '" << socket.name << "'";
}
// 儲存為 Service 中的 Socket
service_->sockets_.emplace_back(std::move(socket));
return {};
}
```
到這裡 Service 就添加了一個 Socket (**尚未創建 ! 要等到 `Start` 函數才會創建**)
> 
## init.rc 語法
init.rc 語法主要可以透過兩個方式了解,^1^ [**Readme.txt**](https://android.googlesource.com/platform/system/core/+/master/init/README.md)、^2^ [**Init_parser.c**](https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1.2/init/init_parser.c) (用來解析 init.rc 語法),以下會說明幾個重點,若是還有問題應該要去看官方的 Readme 檔案
### rc 檔案 - 基礎規則
一個完整個 init.rc 腳本是由 **4 種類型的聲明組成**
| 關鍵字 | 概述 |
| -------- | -------- |
| Actions | 動作 |
| Commands | 命令 |
| Services | 服務 |
| Options | 選項 |
通用語法規則
1. 註釋使用 `#` 開頭
2. 關鍵字 與 參數用 `空格` 分隔,句以行為單位
3. 反向 `/` 可以用來為參數添加空格
4. 如果有字符串(並且該字符串中有空格),必須使用 `雙引號` 將其包含在內
5. 在尾端添加 `/` 是代表與下一行,同行 (如同 C 語言)
6. Actions 與 Services 暗示著新語句的開始,這兩個關鍵字後面跟著 commands 或是 options 則都是屬於這個新語句的
7. **Actions 與 Services 有唯一的名子**,若有重名,則會當成 `錯誤忽略掉`
### Actions - on 動作
* 一個 Action 就是響應事件的流程,**只要發現符合條件的 Actions 就會把它加入命令執行列隊的尾部(依照順序執行 cmd)**,而系統則會依序執行 cmd
```shell=
# Action 格式
on <trigger> # 觸發條件
<command1> # 觸發後執行的命令
<command2> # 可執行多個命令
<command3>
...
```
* Action 會配合著 `on` 使用,`on` 代表這著 trigger 的時機(條件)
| 常見配合 on 指令 | 說明 |
| -------- | -------- |
| on `early-init` | 在 init 之前執行 |
| on `init` | 在 init 時執行 |
| on `late-init` | 在 init 之後執行 |
| on `boot/charger` | 在系統啟動 or 充電 or 其他情況時被觸發 (下面列表會舉幾個常見的) |
| on `property` | 當滿足某個條件時 |
> 
* 以下列出幾個 Actions 常見的事件
| Trigger | Describe |
| -------- | -------- |
| boot | init 程序啟動後觸發的第一個事件 |
| <name\>=<value\> | 像是 if/else 的判斷,當 name 滿足了 value 的設定時就會被觸發 |
| device-added-<path\> | 當設備添加節點時,觸發事件 |
| device-removed-<path\> | 當設備移除節點時,觸發事件 |
| service-exited-<name\> | 當指定服務 name 存在時,觸發事件 |
### Action Commands - 命令
* **每個 Action 都會有不同的 Command 控制**,命令將會在所屬的 Actions 發生時一個個的執行,這些命另有一些跟 Linux cmd 很像,所以可讀性較高
| Command | 描述 | 備註 |
| -------- | -------- | -------- |
| exec <path\> [<argument\>]* | Fork 並執行一個程序,該程序路徑為 path | 該指令會造成 init 進程 **堵塞** |
| **exprot** <name\> <value\> | 設定環境變量,<name\> 的值為 <value\> | **對全局有效,之後的所有進程都會繼承該變量** |
| ifup <interface\> | 使網路接口 interface 成功連結 | |
| import <filename\> | 解析令一個配置文件,文件名為 filename | 拓展當前配置 |
| hostname <name\> | 設定主機名 <name\> | |
| chdir <directory\> | 更改工作目錄為 <chdir\> | change |
| chmod <octal-mode\> <path\> | 同 Linux,改變文件訪問權限 | |
| chown <owner\> <group\> <path\> | 改變所有者 & 群組 | [**文件權限-ch**](https://hackmd.io/MtOap5UaR7KcJpdTisc75g?view#%E6%96%87%E4%BB%B6%E6%AC%8A%E9%99%90-ch) |
| chroot <directory\> | 更改根目錄位置 | |
| class_start <serviceclass\> | 啟動由 <serviceclass\> 類名所指定的所有相關服務,也就是啟動 **多個服務** | 若已經啟動的則不管 |
| class_stop <serviceclass\> | 同上,只是改為停止 | 若尚未啟動的則不管 |
| domainname <path\> | 設定域名 | |
| insmod <path\> | 在 <path\> 路徑上安裝一個模塊 | |
| mkdir <path\> [mode] [owner] [group] | 在 <path\> 路徑上安裝一個目錄 | |
| mount <type\> <device\> <dir\> [<mountoptions\>]* | 嘗試是一個路徑上掛載設備 | |
| setprop <name\> <value\> | 設定系統屬性 | property |
| setrlimit <resource\> <cur\> <max\> | 限定資源的使用,<cur\> 是軟限制,<max\> 是硬限制 | set resource limit |
| start <service\> | 啟動單一個服務 | 若已啟動則不管 |
| stop <service\> | 停止單一個服務 | 若已停止則不管 |
| syslink <target\> <path\> | 創建一個 <path\> 路徑的連結,該連結目標為 <target\> | 像是點擊 path 會跳置 target |
| sysclktz <mins_west_of_gmt> | 設定基準時間 | 若當前時間是 GMT,這個值為 0 |
| trgger <event\> | 觸發一個事件 | |
| write <path\> <string\> [<string\>]* | 打開 path 路徑的文件,寫入字串 | 就是 IO 操作 |
### Services - 服務
* Services 其實是可執行程序,在一些約束下會被 init 程序 `運行` or `重起` (Service 可以再配至中指定是否需要退出時重起,這樣當 Serviced 出現異常 crush 時就有機會復原)
* **Service 一般運行在 init 的子進程 (通過 fork 的方式生成子進程)**,所以啟動 Service 前需要判斷對應的可執行文件是否存在
```shell=
# Services 格式
# name: service 名稱
# pathname: service 所在路徑
# argument: service 所需參數
# option: 對 service 的約束控制
service <name> <pathname> [<argument>]*
<option1>
<option2>
<option3>
...
```
### Service Options
* Option 對 service 的約束控制,針對 Options 說明幾個命令
| Options | 描述 | 備註 |
| -------- | -------- | -------- |
| critical | 當一個設備在 4 分鐘內退出超過 4 次,則重起進入恢復模式 | 危急 |
| disabled | 服務不會自動啟動,而通過顯示調用服務名,才會啟動 | |
| setenv <name\> <value\> | 設定環境變量 <name\> 為 <value\> 值 | |
| socket <name\> <type\> <perm\> [<user\>[<group\>]] | 創建一個名為 /dev/socket/<name\> 的 unix domain socket,並將其 fd 值傳給啟動它的進程 | 有效的 <type\> 包括 dgram、stream、seqpacket,user 跟 group 默認為 0 |
| user <username\> | 啟動服務前將用戶切換到指定 user | 默認為 root |
| group <groupname\> [<groupname\>]* | 啟動服務前將用戶切換到指定 groupname | |
| oneshot | 當服務退出時,不主動重起 | |
| class <name\> | 為該服務指定 class 名稱 | **同一個 class 的服務必須同時停止 or 啟動**,默認 class 名為 default |
| onrestart | 當服務重起時,執行指定命令 | |
:::success
* **Actions vs. Service 差異**
1. Actions 是有必須條件才會觸發命令; Service 是一定執行命令,並且是通過 Options 限制條件
2. Actions 創建需要的目錄並給定權限; Service 用來啟動子進程服務
:::
### import - 加載其他 rc 檔案
* import 是一個關鍵字,不是命令,**用來加載其他 `.rc` 文件,或是加載一個 `資料夾`**(如果是一個資料夾,那所有文件都會被導入)
```shell=
import path
```
:::info
不會循環加載
:::
## [init.rc](https://cs.android.com/android/platform/superproject/+/master:system/core/rootdir/init.rc) 分析
以下為 `init.rc` 檔案片段
1. import 關鍵字引入其他 `.rc` 文件
2. Action 設定 `early-init`、`init`、`boot`... 代表它們要啟動的時機
```shell=
# /rootdir/init.rc
import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /system/etc/init/hw/init.usb.configfs.rc
import /system/etc/init/hw/init.${ro.zygote}.rc
on early-init
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Android doesn't need kernel module autoloading, and it causes SELinux
# denials. So disable it by setting modprobe to the empty string. Note: to
# explicitly set a sysctl to an empty string, a trailing newline is needed.
write /proc/sys/kernel/modprobe \n
# Set the security context of /adb_keys if present.
restorecon /adb_keys
...
on init
sysclktz 0
# Mix device-specific information into the entropy pool
copy /proc/cmdline /dev/urandom
copy /system/etc/prop.default /dev/urandom
...
on boot
# basic network init
ifup lo # 連接網路接口
hostname localhost # 設定 hostname
domainname localdomain # 設定 domainname
# IPsec SA default expiration length
write /proc/sys/net/core/xfrm_acq_expires 3600 # 對 /proc/sys/net/core/xfrm_acq_expires 文件寫入 3600 字串
...
# System server manages zram writeback
chown root system /sys/block/zram0/idle # 改變文件所有者為 root
chmod 0664 /sys/block/zram0/idle # 改變文件權限為 0664
chown root system /sys/block/zram0/writeback
chmod 0664 /sys/block/zram0/writeback
...
# Define default initial receive window size in segments.
setprop net.tcp_def_init_rwnd 60 # 設定 net.tcp_def_init_rwnd 的屬性
# Start standard binderized HAL daemons
class_start hal # 啟動所有標誌為 hal 的服務進程
class_start core # 啟動所有標誌為 core 的服務進程
...
# Actions
on nonencrypted
class_start main # 符合 nonencrypted 條件則啟動 main 相關服務
class_start late_start
...
# Services 匿名內存共享
service ueventd /system/bin/ueventd # 啟動 ueventd 服務進程
class core # 啟動 core 服務進程
critical # 若失敗 4 次以上,進入恢復狀態
seclabel u:r:ueventd:s0
shutdown critical
```
### 啟動 Service 服務進程
```shell=
# /rootdir/init.rc 檔案
on boot
# Actions
on nonencrypted
# class_start 是一個命令,對應到 builtins.cpp#do_class_start
class_start main
class_start late_start
...
```
* class_start 是一個命令,對應到 [**builtins**](https://cs.android.com/android/platform/superproject/+/master:system/core/init/;bpv=0;bpt=1)#do_class_start 函數:該函數會透過 `StartIfNotDisabled` 方法 啟動 class
```cpp=
// /init/builtins.cpp
static Result<void> do_class_start(const BuiltinArguments& args) {
// Do not start a class if it has a property persist.dont_start_class.CLASS set to 1.
if (android::base::GetBoolProperty("persist.init.dont_start_class." + args[1], false))
return {};
// Starting a class does not start services which are explicitly disabled.
// They must be started individually.
for (const auto& service : ServiceList::GetInstance()) {
if (service->classnames().count(args[1])) {
// 判斷該 Service 是否可以啟動,可以的話就啟動
if (auto result = service->StartIfNotDisabled(); !result.ok()) {
LOG(ERROR) << "Could not start service '" << service->name()
<< "' as part of class '" << args[1] << "': " << result.error();
}
}
}
return {};
}
```
* C++ 啟動 Service 的過程如下:創建必要 Socket,再來主要是透過 `fork` + `exec` SystemCall 在 init 進程開啟一個新進程
```cpp=
// /init/service.cpp
Result<void> Service::StartIfNotDisabled() {
// 判斷是否 Disable
if (!(flags_ & SVC_DISABLED)) {
return Start();
} else {
flags_ |= SVC_DISABLED_START;
}
return {};
}
Result<void> Service::Start() {
... 省略部分判斷
pid_t pid = -1;
if (namespaces_.flags) {
pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr);
} else {
// 使用 fork 創建子進程
pid = fork();
}
std::vector<Descriptor> descriptors;
for (const auto& socket : sockets_) {
// 透過 Create 函數創建 Socket 物件
if (auto result = socket.Create(scon); result.ok()) {
descriptors.emplace_back(std::move(*result));
} else {
LOG(INFO) << "Could not create socket '" << socket.name << "': " << result.error();
}
}
... 省略部分
if (pid == 0) {
umask(077);
... 省略部分
// 執行 ececv 函數,並啟動 Service 子進程
if (!ExpandArgsAndExecv(args_, sigstop_)) {
PLOG(ERROR) << "cannot execv('" << args_[0]
<< "'). See the 'Debugging init' section of init's README.md for tips";
}
_exit(127);
}
}
static bool ExpandArgsAndExecv(const std::vector<std::string>& args, bool sigstop) {
// 收集參數 & Service 名稱
std::vector<std::string> expanded_args;
std::vector<char*> c_strings;
expanded_args.resize(args.size());
c_strings.push_back(const_cast<char*>(args[0].data()));
for (std::size_t i = 1; i < args.size(); ++i) {
auto expanded_arg = ExpandProps(args[i]);
if (!expanded_arg.ok()) {
LOG(FATAL) << args[0] << ": cannot expand arguments': " << expanded_arg.error();
}
expanded_args[i] = *expanded_arg;
c_strings.push_back(expanded_args[i].data());
}
c_strings.push_back(nullptr);
if (sigstop) {
kill(getpid(), SIGSTOP);
}
// 啟動服務
return execv(c_strings[0], c_strings.data()) == 0;
}
```
:::success
* **fork + execv**
從上面可以看出透過分析 init.rc 檔案,會透過 `fork` + `execv` 複製 init 進程並啟動多個不同進程,並不只有我們常見的 `ServiceManager`、`Zygote`... 等等
:::
## 透過 init.rc 啟動的服務
從上面我們知道 init 進程 透過分析 init.rc 來達成啟動不同服務,那我們就來看看它啟動了哪些重點服務
### ServiceManager 服務
* 從 [**init.rc**](https://android.googlesource.com/platform/system/core/+/master/rootdir/init.rc#448) 的 `on init` action 可以看到它最後啟動了 ServiceManager 服務進程,而啟動 ServiceManager 會先啟動到 [**servicemanager.rc**](https://android.googlesource.com/platform/frameworks/native/+/master/cmds/servicemanager/servicemanager.rc) (真正啟動服務)
```shell=
# /rootdir/init.rc 檔案
on init
...
# Start essential services. # essential 基礎
start servicemanager # 啟動 srvicemanager.rc
start hwservicemanager
start vndservicemanager
# -----------------------------------------------------------------------
# /cmds/servicemanager/servicemanager.rc
# service 格式:service <name> <pathname>
service servicemanager /system/bin/servicemanager # 啟動 servicemanager
class core animation # 所屬的 class 為 animation
user system
group system readproc
critical # 若失敗 4 次則進入恢復模式
onrestart restart apexd # 若重新啟動則需要 重起以下服務
onrestart restart audioserver
onrestart restart gatekeeperd
onrestart class_restart main
onrestart class_restart hal
onrestart class_restart early_hal
writepid /dev/cpuset/system-background/tasks
shutdown critical
```
* ServiceManager 有設定 class(animation 組),也就是說若停止 animation 組,那其他有關的 animation 組都會停止
* ServiceManager 重新啟動時,`apexd`、`audioserver`、`gatekeeperd`、`main`、`hal`、`early_hal` 都會重新啟動
> 
### Zygote 服務
1. Zygote 啟動在 **`late-init` 時** (init 進程會決定要不要觸發 late-init)
```shell=
# /rootdir/init.rc
on late-init
trigger early-fs
trigger fs
trigger post-fs
trigger late-fs
trigger post-fs-dataograms
trigger zygote-start # 觸發 zygote-start 相關的 on
trigger firmware_mounts_complete
trigger early-boot
trigger boot
```
2. 觸發 `zygote-start` 相關的 `on`:on 底下有一個 start 關鍵字,是用來啟動指定 service,而 `zygote-start` 就是啟動以下兩個服務
* zygote (主要)
* zygote_secondary (備用)
```shell=
# /rootdir/init.rc
on zygote-start && property:ro.crypto.state=unencrypted
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=unsupported
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
wait_for_prop odsign.verification.done 1
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start statsd
start netd
start zygote
start zygote_secondary
```
> 
## Zygote 相關 rc 檔案
1. 從 [**init.rc**](https://android.googlesource.com/platform/system/core/+/master/rootdir/init.rc) 開始看,在開頭可以看到 Zygote 設定檔被 import 進來
```shell=
// /rootdir/init.rc
import /init.${ro.hardware}.rc # 硬體是 32 or 64 位元
import /system/etc/init/hw/init.${ro.zygote}.rc
```
2. **從 ++硬體設置++ 來決定要引用哪個 `init.zygote.rc` 檔案**,以下用 `init.zygote64_32.rc` 為例,init 進程就會啟動兩個 Zytoge 進程 (主要、備用)
> 
:::info
* **若是 zygote32_64、zygote64_32 則會啟動++兩個 Zygote 進程++**,前面那個就是主要 zygote,後面的則是 zygote_secondary
> ex. zygote32_64、zygote 就是 32 bit、zygote_secondary 就是 64 bit
:::
### init.zygote64_32.rc
* 透過 init 進程對 service 的分析,就可以知道 `zygote64_32` 會 `fork` init 進程並 `execv` 啟動兩個服務
1. **zygote** 服務:路徑 `/system/bin/app_process64`
2. **zygote_secondary** 服務:路徑 `/system/bin/app_process32`
```shell=
# /rootdir/init.zygote64_32.rc
# 1. Service Name = zygote
# 2. Path = system/bin/app_process64,app_process64 是資料夾
# 3. Argument:
# -Xzygote /system/bin --zygote --start-system-server
# 主 Socket
service zygote /system/bin/app_process64 \
-Xzygote /system/bin --zygote --start-system-server
# 4. 分類為 main 群組
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
socket usap_pool_primary stream 660 root system
onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
...
# zygote_secondary 為輔助
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
class main
priority -20
user root
group root readproc reserved_disk
socket zygote_secondary stream 660 root system
socket usap_pool_secondary stream 660 root system
onrestart restart zygote
task_profiles ProcessCapacityHigh MaxPerformance
```
> 其他相關 rc 檔案就不再多舉例
## Appendix & FAQ
:::info
:::
###### tags: `Android 系統`