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 New
    • 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 Note Insights Versions and GitHub Sync 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
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- 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 系統`

    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