1. 程式人生 > >Android O: init程序啟動流程分析(階段三)

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相比,這部分流程的變化不是很大。