Android O: init程序啟動流程分析(階段三)
本篇部落格我們就來看看init程序啟動時,解析init.rc檔案相關的工作。
一、建立Parser並決定解析檔案
int main(int argc, char** argv) {
..............
//定義Action中的function_map_為BuiltinFuntionMap
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
//構造出解析檔案用的parser物件
Parser& parser = Parser::GetInstance ();
//為一些型別的關鍵字,建立特定的parser
parser.AddSectionParser("service",std::make_unique<ServiceParser>());
parser.AddSectionParser("on", std::make_unique<ActionParser>());
parser.AddSectionParser("import", std::make_unique<ImportParser>());
//判斷是否存在bootscript
std::string bootscript = GetProperty("ro.boot.init_rc", "");
//如果沒有bootscript,則解析init.rc檔案
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser. ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
} else {
//若存在bootscript, 則解析bootscript
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(true);
parser.set_is_vendor_etc_init_loaded(true);
parser.set_is_odm_etc_init_loaded(true);
}
.........
}
........
從上面的程式碼來看,8.0引入了bootScript的概念,
個人感覺這個應該是方便廠商的定製吧。
如果沒有定義bootScript,那麼init程序還是會解析init.rc檔案。
init.rc檔案是在init程序啟動後執行的啟動指令碼,檔案中記錄著init程序需執行的操作。
此處解析函式傳入的引數為“/init.rc”,解析的是執行時與init程序同在根目錄下的init.rc檔案。
該檔案在編譯前,定義於system/core/rootdir/init.rc中。
init.rc檔案大致分為兩大部分,一部分是以“on”關鍵字開頭的動作列表(action list):
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_score_adj -1000
.........
start ueventd
另一部分是以“service”關鍵字開頭的服務列表(service list):
service ueventd /sbin/ueventd
class core
critical
seclabel u:r:ueventd:s0
藉助系統環境變數或Linux命令,動作列表用於建立所需目錄,以及為某些特定檔案指定許可權,
而服務列表用來記錄init程序需要啟動的一些子程序。
如上面程式碼所示,service關鍵字後的第一個字串表示服務(子程序)的名稱,
第二個字串表示服務的執行路徑。
二、ParseConfig函式
接下來,我們從Parser的ParseConfig函式入手,逐步分析整個檔案解析的過程。
ParseConfig函式定義於system/core/init/ init_parser.cpp中:
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) {
//傳入引數為目錄地址
return ParseConfigDir(path);
}
//傳入引數為檔案地址
return ParseConfigFile(path);
}
我們先來看看ParseConfigDir函式:
bool Parser::ParseConfigDir(const std::string& path) {
...........
std::unique_ptr<DIR, int(*)(DIR*)> config_dir(opendir(path.c_str()), closedir);
..........
//遞迴目錄,得到需要處理的檔案
dirent* current_file;
std::vector<std::string> files;
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;
}
我們跟進一下ParseConfigFile函式:
bool Parser::ParseConfigFile(const std::string& path) {
........
Timer t;
std::string data;
//讀取路徑指定檔案中的內容,儲存為字串形式
if (!read_file(path, &data)) {
return false;
}
.........
//解析獲取的字串
ParseData(path, data);
.........
return true;
}
容易看出,ParseConfigFile只是讀取檔案的內容並轉換為字串。
實際的解析工作被交付給ParseData。
三、ParseData函式
ParseData函式定義於system/core/init/init_parser.cpp中,負責根據關鍵字解析出服務和動作。
動作與服務會以連結串列節點的形式註冊到service_list與action_list中,
service_list與action_list是init程序中宣告的全域性結構體。
ParseData的關鍵程式碼下所示:
void Parser::ParseData(const std::string& filename, const std::string& data) {
//TODO: Use a parser with const input and remove this copy
//copy一波資料
std::vector<char> data_copy(data.begin(), data.end());
data_copy.push_back('\0');
//解析用的結構體
parse_state state;
state.filename = filename.c_str();
state.line = 0;
state.ptr = &data_copy[0];
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
std::vector<std::string> args;
for (;;) {
//next_token獲取分割符,初始沒有分割符時,進入T_TEXT分支
switch (next_token(&state)) {
case T_EOF:
if (section_parser) {
//EOF,解析結束
section_parser->EndSection();
}
return;
case T_NEWLINE:
state.line++;
if (args.empty()) {
break;
}
//在前文建立parser時,我們為service,on,import定義了對應的parser
//這裡就是根據第一個引數,判斷是否有對應的parser
if (section_parsers_.count(args[0])) {
if (section_parser) {
//結束上一個parser的工作,
//將構造出的物件加入到對應的service_list與action_list中
section_parser->EndSection();
}
//獲取引數對應的parser
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
//呼叫實際parser的ParseSection函式
if (!section_parser->ParseSection(args, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
section_parser = nullptr;
}
} else if (section_parser) {
//如果新的一行,第一個引數不是service,on,import
//則呼叫前一個parser的ParseLineSection函式
//這裡相當於解析一個引數塊的子項
std::string ret_err;
if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
}
}
//清空本次解析的資料
args.clear();
break;
case T_TEXT:
//將本次解析的內容寫入到args中
args.emplace_back(state.text);
break;
}
}
}
上面的程式碼看起來比較複雜,但實際上就是面向物件,根據不同的關鍵字,
使用不同的parser物件進行解析。
我們現在回憶一下init程序main函式中,建立parser的程式碼:
...........
Parser& parser = Parser::GetInstance();
parser.AddSectionParser("service",std::make_unique<ServiceParser>());
parser.AddSectionParser("on", std::make_unique<ActionParser>());
parser.AddSectionParser("import", std::make_unique<ImportParser>());
...........
三種Parser均是繼承SectionParser,具體的實現各有不同。
接下來,我們以比較常用的ServiceParser和ActionParser為例,
看看解析的結果如何處理。
3.1、ServiceParser
ServiceParser定義於system/core/init/service.cpp中。
從前面的程式碼我們知道,解析一個service塊時,首先需要呼叫ParseSection函式,
接著利用ParseLineSection處理子塊,解析完所有資料後,最後呼叫EndSection。
因此,我們著重看看ServiceParser的這三個函式。
3.1.1、ParseSection
bool ServiceParser::ParseSection(.....) {
//引數檢驗
.......
//服務名
const std::string& name = args[1];
//服務名校驗
if (!IsValidName(name)) {
*err = StringPrintf("invalid service name '%s'", name.c_str());
return false;
}
std::vector<std::string> str_args(args.begin() + 2, args.end());
//構造出一個service物件
service_ = std::make_unique<Service>(name, str_args);
return true;
}
ParseSection主要校驗引數的有效性,並創建出Service結構體。
3.1.2、ParseLineSection
//注意這裡已經在解析子項了
bool ServiceParser::ParseLineSection(......) const {
//呼叫service物件的HandleLine
return service_ ? service_->ParseLine(args, err) : false;
}
我們跟進一下ParseLine函式:
bool Service::ParseLine(.....) {
//引數校驗
........
//OptionParserMap繼承自keywordMap<OptionParser>
static const OptionParserMap parser_map;
//根據子項的內容,找到對應的處理函式
//FindFunction利用OptionParserMap的map,根據引數找到對應的處理函式
auto parser = parser_map.FindFunction(args[0], args.size() - 1, err);
if (!parser) {
return false;
}
//呼叫對應的處理函式
return (this->*parser)(args, err);
}
為了瞭解這部分內容,我們需要看看OptionParserMap中的map函式:
class Service::OptionParserMap : public KeywordMap<OptionParser> {
...........
Service::OptionHandlerMap::Map& Service::OptionHandlerMap::map() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
//定義了各種關鍵子對應的處理函式
static const Map option_parsers = {
{"capabilities", {1, kMax, &Service::ParseCapabilities}},
{"class", {1, 1, &Service::ParseClass}},
.....................
};
return option_parsers;
}
.......
}
我們以class對應的處理函式為例,看看對應的程式碼:
bool Service::ParseClass(const std::vector<std::string>& args, std::string* err) {
classnames_ = std::set<std::string>(args.begin() + 1, args.end());
return true;
}
容易看出,這部分程式碼其實就是填充service物件對應的域。
因此,可以推斷ParseLineSection函式的作用,
就是根據關鍵字填充Service物件。
3.1.3、EndSection
最後,我們來看看EndSection函式的流程:
//注意此時service物件已經構造完畢
void ServiceParser::EndSection() {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_));
}
}
我們繼續跟進AddService函式:
void ServiceManager::AddService(std::unique_ptr<Service> service) {
Service* old_service = FindServiceByName(service->name());
//處理尷尬的重複定義
if (old_service) {
LOG(ERROR) << "ignored duplicate definition of service '" << service->name() << "'";
return;
}
//將service物件加入到services_裡
services_.emplace_back(std::move(service));
}
從上面的一系列程式碼,我們可以看出ServiceParser的工作流程就是:
首先,根據第一行的名字和引數創建出service物件;
然後,根據子項的內容填充service物件;
最後,將創建出的service物件加入到vector型別的service連結串列中。
3.2、ActionParser
ActionParser定義於system/core/init/action.cpp中。
Action的解析過程,其實與Service一樣,也是先後呼叫ParseSection, ParseLineSection和EndSection。
3.2.1、ParseSection
bool ActionParser::ParseSection(....) {
//構造trigger
std::vector<std::string> triggers(args.begin() + 1, args.end());
if (triggers.size() < 1) {
*err = "actions must have a trigger";
return false;
}
//創建出新的action物件
auto action = std::make_unique<Action>(false);
//根據引數,填充action的trigger域,不詳細分析了
if (!action->InitTriggers(triggers, err)) {
return false;
}
action_ = std::move(action);
return true;
}
與Service類似,Action的ParseSection函式用於構造出Action物件。
3.2.2、ParseLineSection
bool ActionParser::ParseLineSection(.....) const {
//構造Action物件的command域
return action_ ? action_->AddCommand(args, filename, line, err) : false;
}
ParseLineSection將解析Action的子項,構造出Action物件的command域。
我們進一步看看AddCommand函式:
bool Action::AddCommand(const std::vector<std::string>& args,
const std::string& filename, int line, std::string* err) {
........
//找出action對應的執行函式
auto function = function_map_->FindFunction(args[0], args.size() - 1, err);
........
//利用所有資訊構造出command,加入到action物件中
AddCommand(function, args, filename, line);
return true;
}
void Action::AddCommand(......) {
commands_.emplace_back(f, args, filename, line);
}
不難看出ParseLineSection主要是根據引數填充Action的command域。
一個Action物件中可以有多個command。
這裡還剩下一個問題:Action中的function_map_是什麼?
實際上,前文已經出現了過了,在init.cpp的main函式中:
.......
const BuiltinFunctionMap function_map;
Action::set_function_map(&function_map);
.......
因此,Action中呼叫function_map_->FindFunction時,
實際上呼叫的是BuiltinFunctionMap的FindFunction函式。
與查詢Service一樣,這裡也是根據鍵值查詢對應的資訊,
因此重點是看看BuiltinFunctionMap的map函式。
在system/core/init/builtins.cpp中:
BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
static const Map builtin_functions = {
{"bootchart", {1, 1, do_bootchart}},
{"chmod", {2, 2, do_chmod}},
..................
};
return builtin_functions;
}
上述程式碼的第四項就是Action每個command對應的執行函式。
3.2.3、EndSection
void ActionParser::EndSection() {
//Action有效時,才需要加入
if (action_ && action_->NumCommands() > 0) {
ActionManager::GetInstance().AddAction(std::move(action_));
}
}
跟進AddAction函式:
void ActionManager::AddAction(.....) {
//判斷之前是否有定義過的Action
//判斷的依據是Action的trigger域
auto auto old_action_it =
std::find_if(actions_.begin(), actions_.end(),
[&action] (std::unique_ptr<Action>& a) {
return action->TriggersEqual(*a);
});
//相對於Service,Action包容性強一些
//重複定義時,會合並Action
if (old_action_it != actions_.end()) {
//主要是合併command
(*old_action_it)->CombineAction(*action);
} else {
//加入到action連結串列中,型別也是vector,其中裝的是指標
actions_.emplace_back(std::move(action));
}
}
從上面的程式碼可以看出,載入action塊的邏輯和service一樣,不同的是需要填充trigger和command域。
當然,最後解析出的action也需要加入到action連結串列中。
四、向Action佇列中新增其它action
介紹完init程序解析init.rc檔案的過程後,
我們繼續將視角拉回到init程序的main函式:
...........
ActionManager& am = ActionManager::GetInstance();
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
m.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
am.QueueBuiltinAction(console_init_action, "console_init");
// Trigger all the boot actions to get us started.
am.QueueEventTrigger("init");
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn't ready immediately after wait_for_coldboot_done
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don't mount filesystems or start core system services in charger mode.
std::string bootmode = property_get("ro.bootmode");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
......................
從上面的程式碼可以看出,init程序中呼叫了大量的QueueBuiltinAction和QueueEventTrigger函式。
接下來,我們就來看看這兩個函式進行了哪些工作。
4.1、QueueBuiltinAction
QueueBuiltinAction的第一個引數作為新建action攜帶cmd的執行函式;
第二個引數既作為action的trigger name,也作為action攜帶cmd的引數。
void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) {
//建立action
auto action = std::make_unique<Action>(true);
std::vector<std::string> name_vector{name};
//保證trigger name的唯一性
if (!action->InitSingleTrigger(name)) {
return;
}
//建立action的cmd,指定執行函式和引數
action->AddCommand(func, name_vector);
trigger_queue_.push(std::make_unique<BuiltinTrigger>(action.get()));
actions_.emplace_back(std::move(action));
}
從上面的程式碼可以看出:
QueueBuiltinAction函式中將構造新的action加入到actions_連結串列中,
並將trigger事件加入到trigger_queue_中。
4.2、QueueEventTrigger
void ActionManager::QueueEventTrigger(const std::string& trigger) {
trigger_queue_.push(std::make_unique<EventTrigger>(trigger));
}
此處QueueEventTrigger函式就是利用引數構造EventTrigger,然後加入到trigger_queue_中。
後續init程序處理trigger事件時,將會觸發相應的操作。
五、處理新增到執行佇列的事件
前面的程式碼已經將Action、Service、Trigger等加入到資料結構中了。
最後就到了處理這些資料的時候了:
..............
while (true) {
// By default, sleep until something happens.
int epoll_timeout_ms = -1;
//當前沒有事件需要處理時
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
//依次執行每個action中攜帶command對應的執行函式
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
//重啟一些掛掉的程序
restart_processes();
// If there's a process that needs restarting, wake up in time for that.
if (process_needs_restart_at != 0) {
決定timeout的時間,將影響while迴圈的間隔
epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
}
// If there's more work to do, wake up again immediately.
// 有command等著處理的話,不等待
if (am.HasMoreCommands()) epoll_timeout_ms = 0;
}
epoll_event ev;
//沒有事件到來的話,最多阻塞epoll_timeout_ms時間
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
if (nr == -1) {
PLOG(ERROR) << "epoll_wait failed";
} else if (nr == 1) {
//有事件到來,執行對應處理函式
//根據上文知道,epoll控制代碼(即epoll_fd)主要監聽子程序結束,及其它程序設定系統屬性的請求。
((void (*)()) ev.data.ptr)();
}
}
.....................
從上面程式碼可以看出,最終init程序將進入無限迴圈中,
不斷處理執行佇列中的事件、完成重啟程序、監聽epoll_fd等操作。
接下來,我們關注一下其中比較關鍵的函式ExecuteOneCommand和restart_processes。
5.1、ExecuteOneCommand
ExecuteOneCommand中的主要部分如下圖所示。
void ActionManager::ExecuteOneCommand() {
// Loop through the trigger queue until we have an action to execute
//當有可執行的action或trigger queue為空時結束
while (current_executing_actions_.empty() && !trigger_queue_.empty()) {
//輪詢actions連結串列
for (const auto& action : actions_) {
//依次查詢trigger表
if (trigger_queue_.front()->CheckTriggers(*action)) {
//當action與trigger對應時,就可以執行當前action
//一個trigger可以對應多個action,均加入current_executing_actions_
current_executing_actions_.emplace(action.get());
}
}
//trigger event出隊
trigger_queue_.pop();
}
//上面的程式碼說明,執行的順序又trigger queue決定
//沒有可執行的action時,直接退出
if (current_executing_actions_.empty()) {
return;
}
//每次只執行一個action,下次init程序while迴圈時,接著執行
auto action = current_executing_actions_.front();
if (current_command_ == 0) {
std::string trigger_name = action->BuildTriggersString();
INFO("processing action (%s)\n", trigger_name.c_str());
}
//實際的執行過程,此處僅處理當前action中的一個cmd
//current_command_記錄當前command對應的編號
//實際上就是執行該command對應的處理函式
action->ExecuteOneCommand(current_command_);
//適當地清理工作,注意只有當前action中所有的command均執行完畢後,
//才會將該action從current_executing_actions_移除
// If this was the last command in the current action, then remove
// the action from the executing list.
// If this action was oneshot, then also remove it from actions_.
++current_command_;
if (current_command_ == action->NumCommands()) {
current_executing_actions_.pop();
current_command_ = 0;
if (action->oneshot()) {
auto eraser = [&action] (std::unique_ptr<Action>& a) {
return a.get() == action;
};
actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
}
}
}
從程式碼可以看出,當while迴圈不斷呼叫ExecuteOneCommand函式時,將按照trigger表的順序,
依次取出action連結串列中與trigger匹配的action。
每次均僅僅執行一個action中的一個command對應函式(一個action可能攜帶多個command)。
當一個action所有的command均執行完畢後,再執行下一個action。
當一個trigger對應的action均執行完畢後,再執行下一個trigger對應action。
5.2、restart_processes
restart_processes函式負責按需重啟程序,程式碼如下圖所示:
static void restart_processes() {
process_needs_restart = 0;
ServiceManager::GetInstance().ForEachServiceWithFlags( SVC_RESTARTING, [] (Service* s) {
s->RestartIfNeeded(process_needs_restart);
});
}
從上面可以看出,該函式將輪詢service對應的連結串列,
對於有SVC_RESTARING標誌的service執行RestartIfNeeded函式。
前文已經提到過,當子程序終止時,init程序會將可被重啟程序的服務標誌位置為SVC_RESTARTING。
我們進一步看看RestartIfNeeded函式:
void Service::RestartIfNeeded(time_t* process_needs_restart_at) {
boot_clock::time_point now = boot_clock::now();
boot_clock::time_point next_start = time_started_ + 5s;
//兩次服務啟動程序的間隔要大於5s
if (now > next_start) {
flags_ &= (~SVC_RESTARTING);
//滿足時間間隔的要求後,重啟程序
//Start將會重新fork服務程序,並做相應的配置
Start();
return;
}
//更新process_needs_restart_at的值,將影響前文epoll_wait的等待時間
time_t next_start_time_t = time(nullptr) +
time_t(std::chrono::duration_cast<std::chrono::seconds>(next_start - now).count());
if (next_start_time_t < *process_needs_restart_at || *process_needs_restart_at == 0) {
*process_needs_restart_at = next_start_time_t;
}
}
六、結束
至此,init程序的啟動流程分析完畢,
與Android 7.0相比,這部分流程的變化不是很大。