--- 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 文檔, > ![](https://i.imgur.com/H0WPkH8.png) ```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` 函數才會創建**) > ![](https://i.imgur.com/p1Untzn.png) ## 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` | 當滿足某個條件時 | > ![](https://i.imgur.com/ZAzplSj.png) * 以下列出幾個 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` 都會重新啟動 > ![](https://i.imgur.com/AcB4K5r.png) ### 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 ``` > ![](https://i.imgur.com/Whb3LLE.png) ## 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 進程 (主要、備用) > ![](https://i.imgur.com/F98Wgt3.png) :::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 系統`