IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 源码详解Android 9.0(P) 系统启动流程之init进程(init.rc解析) -> 正文阅读

[移动开发]源码详解Android 9.0(P) 系统启动流程之init进程(init.rc解析)

1. 概述

?? 建议如果对init.rc文件中的语法规则存在疑问的话,先观看上一篇文章源码详解Android 9.0§ 系统启动流程之init.rc语法规则简单了解一下init.rc的语法规则,而本文将分析如何解析rc文件,并对rc文件中的某一service启动过程进行分析。

2. init进程解析init.rc过程分析

?? 前一篇文章我们恶补了相关的rc语法知识,前面这些都是为了init进程解析init.rc铺垫的。
?? 之前我们在文档中看到.rc文件主要有根目录下的 /init.rc ,以及{system,vendor,odm}/etc/init/这三个目录下的 *.rc ,
然后就是如果有一个特殊目录被设置的话,就替代这些目录,明白这些,下面的代码就好理解了。
由于LoadBootScripts和CreateParser函数都定义于system/core/init/init.cpp中,因此合并展示。

int main(int argc, char** argv) {
    ...
    const BuiltinFunctionMap function_map;
    /*
    * C++中::表示静态方法调用,相当于java中static的方法
    */
    Action::set_function_map(&function_map);//将function_map存放到Action中作为成员属性

    subcontexts = InitializeSubcontexts();

    ActionManager& am = ActionManager::GetInstance();//单例模式
    ServiceList& sm = ServiceList::GetInstance();//单例模式

    LoadBootScripts(am, sm);
	......
}

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {//如果ro.boot.init_rc没有对应的值,则解析/init.rc以及/system/etc/init、/vendor/etc/init、/odm/etc/init这三个目录下的.rc文件
        parser.ParseConfig("/init.rc");
        if (!parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init");
        }
    } else {//如果ro.boot.init_rc属性有值就解析属性值
        parser.ParseConfig(bootscript);
    }
}

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;

   /*
    * 1.C++中std::make_unique相当于new,它会返回一个std::unique_ptr,即智能指针,可以自动管理内存
    * 2.unique_ptr持有对对象的独有权,两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作
    * 3.移动操作的函数是 p1=std::move(p) ,这样指针p指向的对象就移动到p1上了
    * 4.接下来的这三句代码都是new一个Parser(解析器),然后将它们放到一个map里存起来
    * 5.ServiceParser、ActionParser、ImportParser分别对应service action import的解析
    */
    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));

    return parser;
}

2.1 Parser

?? 可以看出在正式解析前,创建了一个Parser 对象(该类定义在system/core/init/parser.h中):

Parser parser = CreateParser(action_manager, service_list);

2.1.1 CreateParser

?? 全部代码在main函数一起展示,我们重点分析其中的代码:

parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));

?? 这段代码比较容易理解,分别初始化ServiceParser用来解析"service"块,初始化ActionParser用来解析"on"块,初始化ImportParser用来解析“import”块。

2.2 ParseConfig

?? 下面就要开始分析解析过程了,ParseConfig的代码定义在system/core/init/parser.cpp中。
?? 首先是判断传入的是目录还是文件,若是文件则调用ParseConfigFile;若是目录则递归调用ParseConfigDir遍历该目录下的所有文件,对文件进行调用ParseConfigFile。

bool Parser::ParseConfig(const std::string& path) {
    size_t parse_errors;
    return ParseConfig(path, &parse_errors);
}

bool Parser::ParseConfig(const std::string& path, size_t* parse_errors) {
    *parse_errors = 0;
    if (is_dir(path.c_str())) {//判断传入参数是否为目录地址
        return ParseConfigDir(path, parse_errors);//递归目录,最终还是ParseConfigFile来解析实际的文件
    }
    return ParseConfigFile(path, parse_errors);//传入参数为文件地址
}

2.2.1 ParseConfigDir

??按照顺序,我们先来分析ParseConfigDir函数的内容:
??ParseConfigDir和ParseConfigFile函数同样定义于system/core/init/parser.cpp中。

bool Parser::ParseConfigDir(const std::string& path, size_t* parse_errors) {
    LOG(INFO) << "Parsing directory " << path << "...";
    std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);
    if (!config_dir) {
        PLOG(ERROR) << "Could not import directory '" << path << "'";
        return false;
    }
    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, parse_errors)) {
            LOG(ERROR) << "could not import file '" << file << "'";
        }
    }
    return true;
}

2.2.2 ParseConfigFile

??刚刚在ParseConfigDir函数中,我们不难发现重点依旧是ParseConfigFile,让我们重点分析一下该函数:

bool Parser::ParseConfigFile(const std::string& path, size_t* parse_errors) {
    LOG(INFO) << "Parsing file " << path << "...";
    android::base::Timer t;
    /*
    * C++中auto关键词,可以让编译器根据初始值类型自动推断变量的类型
    */
    auto config_contents = ReadFile(path);//读取指定文件的内容,保存为string的形式
    if (!config_contents) {
        LOG(ERROR) << "Unable to read config file '" << path << "': " << config_contents.error();
        return false;
    }

    config_contents->push_back('\n');  // TODO: fix parse_config.
    ParseData(path, *config_contents, parse_errors);//解析获取的字符串
    for (const auto& [section_name, section_parser] : section_parsers_) {
        section_parser->EndFile();
    }

    LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";
    return true;
}

??通过解析代码可以看到ParseConfigFile的逻辑比较简单,就是读取文件的内容为字符串,然后调用ParseData进行解析。

2.3 ParseData

??在正式开始分析代码前,先来一个流程图,防止大伙在代码分析中迷失了自己,可以回过头找到自己在哪里。

在这里插入图片描述
??ParseData函数定义在system/core/init/parser.cpp中,负责根据关键字解析出服务和动作块。

void Parser::ParseData(const std::string& filename, const std::string& data, size_t* parse_errors) {
    // TODO: Use a parser with const input and remove this copy
    //将data的数据拷贝到data_copy中
    std::vector<char> data_copy(data.begin(), data.end());
    data_copy.push_back('\0');//追加一个结束符0

    parse_state state;//定义一个结构体
    state.line = 0;
    state.ptr = &data_copy[0];//存储要解析的数据
    state.nexttoken = 0;

    SectionParser* section_parser = nullptr;
    int section_start_line = -1;
    std::vector<std::string> args;

    auto end_section = [&] {
        if (section_parser == nullptr) return;

        if (auto result = section_parser->EndSection(); !result) {
            (*parse_errors)++;
            LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
        }

        section_parser = nullptr;
        section_start_line = -1;
    };

    for (;;) {
    	//遍历data_copy中每一个字符
        switch (next_token(&state)) {//next_token以行为单位分割参数传递过来的字符串,初始没有分割符时,最先走到T_TEXT分支
            case T_EOF:
                end_section();//如果文件结尾,则调用end_section
                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.
                for (const auto& [prefix, callback] : line_callbacks_) {
                    if (android::base::StartsWith(args[0], prefix)) {
                        end_section();

                        if (auto result = callback(std::move(args)); !result) {
                            (*parse_errors)++;
                            LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        }
                        break;
                    }
                }
               /*
             	* 1.section_parsers_是一个std:map
             	* 2.C++中std:map的count函数是查找key,相当于Java中Map的contains
             	* 3.section_parsers_中只有三个key,on service import,之前AddSectionParser函数加入
             	*/
             	//这里的section_parsers_是由前面的CreateParser添加的
                if (section_parsers_.count(args[0])) {//判断是否包含on,service,import关键词
                    end_section();
                    section_parser = section_parsers_[args[0]].get();//取出对应的parser,这里的Parser有三种,即前面CreateParser加入的ServiceParser,ActionParser和ImportParser
                    section_start_line = state.line;
                    if (auto result =
                            section_parser->ParseSection(std::move(args), filename, state.line);//解析对应的Section
                        !result) {
                        (*parse_errors)++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        section_parser = nullptr;
                    }
                } else if (section_parser) {//不包含 on service import则是command或option,则调用前一个parser的ParseLineSection函数,这里相当于解析一个参数块的子项
                    if (auto result = section_parser->ParseLineSection(std::move(args), state.line);//解析command或option
                        !result) {
                        (*parse_errors)++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                    }
                }
                args.clear();//清空本次解析的数据
                break;
            case T_TEXT://将读取的一行数据放到args中,args以空格或""作为分割,将一行数据拆分成单词放进数组中
                args.emplace_back(state.text);//将本次要解析的内容写入到args中
                break;
        }
    }
}

通过上面的代码我们可以看到,ParseData主要通过调用next_token函数遍历每一个字符,然后对不同的字符进行判断,采取不同的规则进行处理,其主要流程如下:

  • 以空格或""为分割将一行拆分成若干个单词,调用T_TEXT将单词放到args数组中
  • 当读到回车符就调用T_NEWLINE,在section_parsers_这个map中找到对应的on service import的解析器,执行ParseSection
  • 如果在map中找不到对应的key,就执行ParseLineSection,当读到0的时候,表示一个Section读取结束,调用T_EOF执行EndSection.

在上述流程中牵涉到一个非常重要的结构体parse_state ,next_token 处理的数据以 parse_state 结构体指针返回,以行为单位分隔传递的字符串,成员变量代表的意义如下:

//该段定义在system/core/init/tokenizer.h
#define T_EOF 0
#define T_TEXT 1
#define T_NEWLINE 2

struct parse_state
{
    char *ptr;			//将要解析的字符串
    char *text;			//解析得到的字符串,即解析返回的一行数据
    int line;  			//解析到init.rc字符串的多少行 
    int nexttoken;      //解析状态,共有三种分别是T_EOF ,T_TEXT ,T_NEWLINE 
};

??其中 T_EOF 表示字符串解析结束,T_NEWLINE 表示解析完一行的数据,T_TEXT 表示解析到一个单词,在代码中会填充到 args vector 向量中,用作后续的解析处理。

??这里其实涉及到on service import对应的三个解析器ActionParser、ServiceParser、ImportParser,它们是在之前加入到section_parsers_这个map中的,大家应该有印象,代码如下:

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));

    return parser;
}

??上述三个类都是SectionParser的子类,SectionParser有四个纯虚函数,分别是ParseSection、ParseLineSection、EndSection、EndFile

//代码定义在system/core/init/parser.h
class SectionParser {
  public:
    virtual ~SectionParser() {}
    /*
     * 1.C++中纯虚函数的定义格式是 virtual作为修饰符,然后赋值给0,相当于Java中的抽象方法
     * 2.如果不赋值给0,却以virtual作为修饰符,这种是虚函数,虚函数可以有方法体,相当于Java中父类的方法,主要用于子类的重载
     * 3.只要包含纯虚函数的类就是抽象类,不能new,只能通过子类实现,这个跟Java一样
     */
    virtual Result<Success> ParseSection(std::vector<std::string>&& args,
                                         const std::string& filename, int line) = 0;
    virtual Result<Success> ParseLineSection(std::vector<std::string>&&, int) { return Success(); };
    virtual Result<Success> EndSection() { return Success(); };
    virtual void EndFile(){};
};

??经过上面的这些步骤init.rc就彻底被解决为一个个的setcion,而每个Action或者Service则会别对应的SectionParser 来进一步处理,Service会别ServiceParser解析,而Action则会被ActionParser解析,Import则会被ImportParser解析。最后ParseSection 和 ParseLineSection 都是解析 args 参数填充一些链表(service_list_、_commands、_trigger等)记录所要配置或者执行触发的信息,在 init 程序的最后 while 循环中分别进行相应的配置操作。

2.4 ServiceParser

??该函数定义在system/core/init/service.h实现在system/core/init/service.cpp中,ServiceParser实现了对Service section的解析,下面让我们来分析其几个主要函数ParseSection,ParseLineSection和EndSection的实现。

2.4.1 ServiceParser::ParseSection

Result<Success> ServiceParser::ParseSection(std::vector<std::string>&& args,
                                            const std::string& filename, int line) {
    if (args.size() < 3) {//判断传入的单词个数至少为三个,譬如service console /system/bin/sh
        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 << "'";
    }

    Subcontext* restart_action_subcontext = nullptr;
    if (subcontexts_) {
        for (auto& subcontext : *subcontexts_) {
            if (StartsWith(filename, subcontext.path_prefix())) {
                restart_action_subcontext = &subcontext;
                break;
            }
        }
    }

    std::vector<std::string> str_args(args.begin() + 2, args.end());
    service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args);//构造service对象
    return Success();
}

ParseSection的函数处理逻辑如下:

  • 首先判断传入进来的参数args单词个数是否至少有三个,因为至少要有一个服务名称和路径
  • 然后判断名称是否合法,主要是检测长度和内容
  • 经过上面的检测以后,就构造一个Service对象出来了

2.4.2 ServiceParser::ParseLineSection

??前面通过ParseSection定位到了Service section,接着调用ParseLineSection解析Service section中的options选项,即类似的如下的options:

class core
console
disabled
user shell
group shell log readproc
seclabel u:r:shell:s0
setenv HOSTNAME console
//注意这里已经在解析子项了
Result<Success> ServiceParser::ParseLineSection(std::vector<std::string>&& args, int line) {
    return service_ ? service_->ParseLine(std::move(args)) : Success();
}

??ParseLine的思路就是根据option名称从map中找到对应的执行函数,然后执行这个函数。这些执行函数主要作用就是对传入参数做一些处理,然后将信息记录到Service对象中

Result<Success> Service::ParseLine(const std::vector<std::string>& args) {
    static const OptionParserMap parser_map;//OptionParserMap继承自keywordMap<OptionParser>
    auto parser = parser_map.FindFunction(args);//从map中查找命令对应的执行函数
												//FindFunction利用OptionParserMap的map,根据参数找到对应的处理函数
	
    if (!parser) return parser.error();

    return std::invoke(*parser, this, args);//执行找到的这个函数
}

??ParseLineSection直接执行Service的ParseLine函数,然后是调用FindFunction查找命令对应的执行函数,这里比较关键的函数就是FindFunction了,让我们先来分析一下这个函数,你会发现在OptionParserMap 中找不到这个函数,那就只能是定义在其父类KeywordMap中查找了。

??FindFunction函数是定在system/core/init/keyword_map.h中,为KeywordMap类的方法

class KeywordMap {
  public:
    using FunctionInfo = std::tuple<std::size_t, std::size_t, Function>;
    using Map = std::map<std::string, FunctionInfo>;

    virtual ~KeywordMap() {
    }

    const Result<Function> FindFunction(const std::vector<std::string>& args) const {
        using android::base::StringPrintf;

        if (args.empty()) return Error() << "Keyword needed, but not provided";

        auto& keyword = args[0];
        auto num_args = args.size() - 1;

        auto function_info_it = map().find(keyword);//找到keyword对应的entry
        if (function_info_it == map().end()) {// end是最后一个元素后的元素,表示找不到
            return Error() << StringPrintf("Invalid keyword '%s'", keyword.c_str());
        }

        auto function_info = function_info_it->second;//获取value

        auto min_args = std::get<0>(function_info);//获取参数数量最小值
        auto max_args = std::get<1>(function_info);//获取参数数量最大值
        if (min_args == max_args && num_args != min_args) {//将实际参数数量与最大值最小值比较
            return Error() << StringPrintf("%s requires %zu argument%s", keyword.c_str(), min_args,
                                           (min_args > 1 || min_args == 0) ? "s" : "");
        }

        if (num_args < min_args || num_args > max_args) {
            if (max_args == std::numeric_limits<decltype(max_args)>::max()) {
                return Error() << StringPrintf("%s requires at least %zu argument%s",
                                               keyword.c_str(), min_args, min_args > 1 ? "s" : "");
            } else {
                return Error() << StringPrintf("%s requires between %zu and %zu arguments",
                                               keyword.c_str(), min_args, max_args);
            }
        }

        return std::get<Function>(function_info);//这个是重点,返回命令对应的执行函数
    }

  private:
    // Map of keyword ->
    // (minimum number of arguments, maximum number of arguments, function pointer)
    virtual const Map& map() const = 0;
};

通过对上述代码分析我们可以知道,这个函数主要作用是通过命令查找对应的执行函数,譬如Service section中的option选项class,我们得找到ParseClass去执行那个函数。它的具体执行步骤如下:

  • 它首先是通过map()返回一个std:map
  • 接着调用其find函数,find相当于Java中的get,但是返回的是entry,可以通过entry ->first和entry ->second获取key-value.找到的value是一个结构体,里面有三个值,第一个是参数最小数目,第二个是参数最大数目,第三个就是执行函数,
  • 然后做些参数相关的检查,然后返回查找到的对应的处理函数

这里解析Service section的option的map()函数的实现在system/core/init/service.cpp中,就是直接构造一个map,然后返回,譬如{“class”, {1, kMax, &Service::ParseClass}},这里表示命令名称叫做class,对应的执行函数是ParseClass,允许传入的最小是1,最大的是kMax。

class Service::OptionParserMap : public KeywordMap<OptionParser> {
  public:
    OptionParserMap() {}

  private:
    const Map& map() const override;
};

const Service::OptionParserMap::Map& Service::OptionParserMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const Map option_parsers = {
        {"capabilities",
                        {1,     kMax, &Service::ParseCapabilities}},
        {"class",       {1,     kMax, &Service::ParseClass}},//设置service所属的类名,当所属类启动/退出时,服务也启动/停止,默认为default:常见的类名有“main, core, charge
        {"console",     {0,     1,    &Service::ParseConsole}},
        {"critical",    {0,     0,    &Service::ParseCritical}},//设备关键服务,4分钟内重启超过四次会进入recovery模式
        {"disabled",    {0,     0,    &Service::ParseDisabled}},//不跟随class启动,需要显示start启动
        {"enter_namespace",
                        {2,     2,    &Service::ParseEnterNamespace}},
        {"group",       {1,     NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}},//服务用户组设置
        {"interface",   {2,     2,    &Service::ParseInterface}},
        {"ioprio",      {2,     2,    &Service::ParseIoprio}},//io操作优先级设置
        {"priority",    {1,     1,    &Service::ParsePriority}},
        {"keycodes",    {1,     kMax, &Service::ParseKeycodes}},
        {"oneshot",     {0,     0,    &Service::ParseOneshot}},//service退出后不再重启
        {"onrestart",   {1,     kMax, &Service::ParseOnrestart}},//当服务重启时执行相关的command
        {"override",    {0,     0,    &Service::ParseOverride}},
        {"oom_score_adjust",
                        {1,     1,    &Service::ParseOomScoreAdjust}},
        {"memcg.swappiness",
                        {1,     1,    &Service::ParseMemcgSwappiness}},
        {"memcg.soft_limit_in_bytes",
                        {1,     1,    &Service::ParseMemcgSoftLimitInBytes}},
        {"memcg.limit_in_bytes",
                        {1,     1,    &Service::ParseMemcgLimitInBytes}},
        {"namespace",   {1,     2,    &Service::ParseNamespace}},
        {"rlimit",      {3,     3,    &Service::ParseProcessRlimit}},
        {"seclabel",    {1,     1,    &Service::ParseSeclabel}},
        {"setenv",      {2,     2,    &Service::ParseSetenv}},//设置service环境变量
        {"shutdown",    {1,     1,    &Service::ParseShutdown}},
        {"socket",      {3,     6,    &Service::ParseSocket}},
        {"file",        {2,     2,    &Service::ParseFile}},
        {"user",        {1,     1,    &Service::ParseUser}},//设置service的用户
        {"writepid",    {1,     kMax, &Service::ParseWritepid}},
    };
    // clang-format on
    return option_parsers;
}

??好了ParseLineSection就分析完了,下面我们对其总结一下,ParseLineSection主要是调用Service的ParseLine函数,然后根据传入的option名称从map中查找对应的执行函数,然后执行这个函数,这些函数的主要作用就是对传入的option参数做处理,然后将信息记录到Service对象中。

2.4.3 ServiceParser::EndSection

??通过前面的操作我们经过一系列猛虎般的操作,已经解析完了Service section了并且创建了Service对象了,那么我们怎么将其加入到init进程的ServiceList中呢,这里就得EndSection出马了。

Result<Success> ServiceParser::EndSection() {
    if (service_) {
        Service* old_service = service_list_->FindService(service_->name());//查找services_中是否已存在同名service
        if (old_service) {
            if (!service_->is_override()) {
                return Error() << "ignored duplicate definition of service '" << service_->name()
                               << "'";
            }

            service_list_->RemoveService(*old_service);
            old_service = nullptr;
        }

        service_list_->AddService(std::move(service_));//加入列表
    }

    return Success();
}

??接下来看一下其中的FindService和AddService函数

??FindService

??代码定义在system\core\init\service.h中。

 template <typename T, typename F = decltype(&Service::name)>
    Service* FindService(T value, F function = &Service::name) const {
        auto svc = std::find_if(services_.begin(), services_.end(),
                                [&function, &value](const std::unique_ptr<Service>& s) {
                                    return std::invoke(function, s) == value;
                                });//遍历列表进行比较,查找是否已经有保存同名的service
        if (svc != services_.end()) {
            return svc->get();//找到就返回
        }
        return nullptr;
    }

??AddService

??代码定义在system\core\init\service.cpp中。

void ServiceList::AddService(std::unique_ptr<Service> service) {
    services_.emplace_back(std::move(service));
}

EndSection的处理逻辑简单,主要做了如下几个操作:

  • 通过FindService查找是否已经有相同名字的Service存在
  • 调用AddService将Service添加到services_中,这个services_是属于init进程中的。是从init.cpp一路传过来的,这个千万要注意。
ServiceList& sm = ServiceList::GetInstance();  //这个是一个单例模式,这个非常重要
LoadBootScripts(am, sm);

2.4.4 ServiceParser::EndFile

??如果说前面的函数轰轰烈烈,那么EndFile就平淡无奇了,EndFile是一个空函数没有做任何事情。

2.5 ActionParser

??在 2.4章节中,我们完整介绍了ServiceParser是怎么完整解析Service section的流程,那么在这个章节将要介绍ActionParser怎么对Action section进行庖丁解牛一一分解的,过程依然还是首先需要调用 ParseSection函数,接着利用 ParseLineSection 处理子块,解析完所有数据后,调用 EndSection。这里举几个Action section例子,以供后面分析代码参考。

on property:ro.crypto.state=unencrypted && property:ro.persistent_properties.ready=true
    setprop ro.prop.load.end 1    

on load_persist_props_action
    load_persist_props
    start logd
    start logd-reinit

2.5.1 Action

??在正式开始ActionParser的解析前,首先得介绍一下Action,它定义在system/core/init/action.h中是对init中Action section的一个封装和Servcie的功能类似。它有几个非常重要的成员:

class Action {
	......
    std::map<std::string, std::string> property_triggers_;//存储属性触发信息
    std::string event_trigger_;//存储event_trigger_触发信息
    std::vector<Command> commands_;//存储具体的command命令
	......
};

??这里的Action类主要用于存放Action section相关内容即当属性变化或系统进行到程序的某个时候(event_trigger_)时触发 commands_ 向量中的一系列命令信息。ActionParser 的解析过程实际上就是解析填充这些信息。

2.5.2 ActionParser::ParseSection

??让我们按照Action section解析的顺序,先来看ParseSection,它定义在system/core/init/action_parser.cpp中。

Result<Success> ActionParser::ParseSection(std::vector<std::string>&& args,
                                           const std::string& filename, int line) {
    std::vector<std::string> triggers(args.begin() + 1, args.end());//将args复制到triggers中,除去下标0即"on"字符串
    if (triggers.size() < 1) {
        return Error() << "Actions must have a trigger";
    }

    Subcontext* action_subcontext = nullptr;
    if (subcontexts_) {
        for (auto& subcontext : *subcontexts_) {
            if (StartsWith(filename, subcontext.path_prefix())) {
                action_subcontext = &subcontext;
                break;
            }
        }
    }

    std::string event_trigger;
    std::map<std::string, std::string> property_triggers;

	//调用ParseTriggers解析Triggers触发条件
    if (auto result = ParseTriggers(triggers, action_subcontext, &event_trigger, &property_triggers);
        !result) {
        return Error() << "ParseTriggers() failed: " << result.error();
    }
	//构建Action
    auto action = std::make_unique<Action>(false, action_subcontext, filename, line, event_trigger,
                                           property_triggers);

    action_ = std::move(action);
    return Success();
}

ParseSection处理的逻辑比较简单,主要干了如下几件事情:

  • 将参数args中的内容拷贝到triggers中,并剔除字符串“on”
  • 调用ParseTriggers即系action触发条件
  • 将前面解析得到的触发条件为参数,构建Action

我们接着继续分析ParseTriggers:

Result<Success> ParseTriggers(const std::vector<std::string>& args, Subcontext* subcontext,
                              std::string* event_trigger,
                              std::map<std::string, std::string>* property_triggers) {
    const static std::string prop_str("property:");
    for (std::size_t i = 0; i < args.size(); ++i) {
        if (args[i].empty()) {
            return Error() << "empty trigger is not valid";
        }

        if (i % 2) {
            if (args[i] != "&&") {
                return Error() << "&& is the only symbol allowed to concatenate actions";
            } else {
                continue;
            }
        }

        if (!args[i].compare(0, prop_str.length(), prop_str)) {
        	// 属性变化时触发,在 ParsePropertyTrigger 函数中填充 property_triggers_
            if (auto result = ParsePropertyTrigger(args[i], subcontext, property_triggers);
                !result) {
                return result;
            }
        } else {
            if (!event_trigger->empty()) {
                return Error() << "multiple event triggers are not allowed";
            }
			//否则填充event_trigger 
            *event_trigger = args[i];
        }
    }

    return Success();
}

ParseTriggers的处理逻辑也不是很复杂,主要遵循如下几个处理逻辑:

  • 先比较args参数(这个是已经除去了字符串"on"的)是否是以“property:”开头的,如果是property_triggers就继续调用ParsePropertyTrigger解析
  • 其它的情况就只可能是event trigger,就将args的参数赋值给event_trigger_,类型是string

革命尚未成功,让我们再接再厉继续分析ParsePropertyTrigger:

Result<Success> ParsePropertyTrigger(const std::string& trigger, Subcontext* subcontext,
                                     std::map<std::string, std::string>* property_triggers) {
    const static std::string prop_str("property:");
    std::string prop_name(trigger.substr(prop_str.length()));//截取property:之后的内容
    size_t equal_pos = prop_name.find('=');
    if (equal_pos == std::string::npos) {
        return Error() << "property trigger found without matching '='";
    }

    std::string prop_value(prop_name.substr(equal_pos + 1));//取出value
    prop_name.erase(equal_pos);//删除下标为equal_pos的字符,也就是删除"="

    if (!IsActionableProperty(subcontext, prop_name)) {//判断是否合法
        return Error() << "unexported property tigger found: " << prop_name;
    }

	//将name-value键值存放到map中,emplace相当于Java中HashMap的put操作
    if (auto [it, inserted] = property_triggers->emplace(prop_name, prop_value); !inserted) {
        return Error() << "multiple property triggers found for same property";
    }
    return Success();
}

ParsePropertyTrigger函数将proerty触发字符串以"="分割为name-value,然后将name-value存入property_triggers_这个map中,例如如下的触发条件字符串:

ro.crypto.state=unencrypted

经过以上步骤ParseSection就分析完了,下面还是对其流程归纳总结一下:

  • ParseSection函数的作用就是构造一个Action对象
  • 将trigger条件记录到Action这个对象中,如果是event trigger就赋值给event_trigger_,如果是property trigger就存放到property_triggers_这个map中

2.5.3 ActionParser::ParseLineSection

??前面章节通过ActionParser::ParseSection解析了Action的触发触发条件和创建了Action,接下来就就得解析Action section的command了,所以我们的ParseLineSection要上场了。

??ParseLineSection函数简单明了的再不过了,就是调用Action的AddCommand添加command命令。

Result<Success> ActionParser::ParseLineSection(std::vector<std::string>&& args, int line) {
    return action_ ? action_->AddCommand(std::move(args), line) : Success();
}

接着继续分析Action的方法AddCommand,定义在system/core/init/action.cpp中,AddCommand顾名思义就是添加Action section的command指令,然后调用FindFunction查找对饮给的执行函数,最后将这些信息包装成Command对象存放到commands_s队列中,这里最关键的就是FindFunction和function_map_了。

Result<Success> Action::AddCommand(const std::vector<std::string>& args, int line) {
    if (!function_map_) {
        return Error() << "no function map available";
    }

    auto function = function_map_->FindFunction(args);
    if (!function) return Error() << function.error();

    commands_.emplace_back(function->second, function->first, args, line);
    return Success();
}

在前面的ServiceParser我们已经讲到了FindFunction的原理了,这里就不过多讲解了,有不清楚的可以回过头看看,这个函数主要就是通过命令查找对应的执行函数。FindFunction函数弄清楚了,那还有一个疑问function_map_是在哪里赋值的呢,这个得回到system/core/init/init.cpp里面去查看如下代码:

    const BuiltinFunctionMap function_map;//这个是重点
    Action::set_function_map(&function_map);

    subcontexts = InitializeSubcontexts();

    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();

通过跟进代码,我们发现function_map_是定义在system/core/init/builtins.cpp,这个实现比较简单就是直接构造一个map,然后返回. 比如{“chmod”, {2, 2, {true, do_chmod}}},
表示命令名称叫chmod,对应的执行函数是do_chmod,允许传入的最小和最大参数数量是2。

// Builtin-function-map start
const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
    constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
    // clang-format off
    static const Map builtin_functions = {
        {"bootchart",               {1,     1,    {false,  do_bootchart}}},
        {"chmod",                   {2,     2,    {true,   do_chmod}}},
        {"chown",                   {2,     3,    {true,   do_chown}}},
        {"class_reset",             {1,     1,    {false,  do_class_reset}}},
        {"class_restart",           {1,     1,    {false,  do_class_restart}}},
        {"class_start",             {1,     1,    {false,  do_class_start}}},
        {"class_stop",              {1,     1,    {false,  do_class_stop}}},
        {"copy",                    {2,     2,    {true,   do_copy}}},
        {"domainname",              {1,     1,    {true,   do_domainname}}},
        {"enable",                  {1,     1,    {false,  do_enable}}},
        {"exec",                    {1,     kMax, {false,  do_exec}}},
        {"exec_background",         {1,     kMax, {false,  do_exec_background}}},
        {"exec_start",              {1,     1,    {false,  do_exec_start}}},
        {"export",                  {2,     2,    {false,  do_export}}},
        {"hostname",                {1,     1,    {true,   do_hostname}}},
        {"ifup",                    {1,     1,    {true,   do_ifup}}},
        {"init_user0",              {0,     0,    {false,  do_init_user0}}},
        {"insmod",                  {1,     kMax, {true,   do_insmod}}},
        {"installkey",              {1,     1,    {false,  do_installkey}}},
        {"load_persist_props",      {0,     0,    {false,  do_load_persist_props}}},
        {"load_system_props",       {0,     0,    {false,  do_load_system_props}}},
        {"loglevel",                {1,     1,    {false,  do_loglevel}}},
        {"mkdir",                   {1,     4,    {true,   do_mkdir}}},
        // TODO: Do mount operations in vendor_init.
        // mount_all is currently too complex to run in vendor_init as it queues action triggers,
        // imports rc scripts, etc.  It should be simplified and run in vendor_init context.
        // mount and umount are run in the same context as mount_all for symmetry.
        {"mount_all",               {1,     kMax, {false,  do_mount_all}}},
        {"mount",                   {3,     kMax, {false,  do_mount}}},
        {"remount",                 {1,     1,    {false,  do_remount}}},
        {"umount",                  {1,     1,    {false,  do_umount}}},
        {"umount2",                 {1,     1,    {false,  do_umount2}}},
        {"readahead",               {1,     2,    {true,   do_readahead}}},
        {"restart",                 {1,     1,    {false,  do_restart}}},
        {"restorecon",              {1,     kMax, {true,   do_restorecon}}},
        {"restorecon_recursive",    {1,     kMax, {true,   do_restorecon_recursive}}},
        {"rm",                      {1,     1,    {true,   do_rm}}},
        {"rmdir",                   {1,     1,    {true,   do_rmdir}}},
        {"setprop",                 {2,     2,    {true,   do_setprop}}},
        {"setrlimit",               {3,     3,    {false,  do_setrlimit}}},
        {"start",                   {1,     1,    {false,  do_start}}},
        {"stop",                    {1,     1,    {false,  do_stop}}},
        {"swapon_all",              {1,     1,    {false,  do_swapon_all}}},
        {"symlink",                 {2,     2,    {true,   do_symlink}}},
        {"sysclktz",                {1,     1,    {false,  do_sysclktz}}},
        {"trigger",                 {1,     1,    {false,  do_trigger}}},
        {"verity_load_state",       {0,     0,    {false,  do_verity_load_state}}},
        {"verity_update_state",     {0,     0,    {false,  do_verity_update_state}}},
        {"wait",                    {1,     2,    {true,   do_wait}}},
        {"wait_for_prop",           {2,     2,    {false,  do_wait_for_prop}}},
        {"write",                   {2,     2,    {true,   do_write}}},
    };
    // clang-format on
    return builtin_functions;
}
// Builtin-function-map end

2.5.4 ActionParser::EndSection

经过万水千山,Action section终于解析完成并且封装到了Action里面去了,那么接下来需要将Action存储起来了,这就轮到了EndSection出场了。

Result<Success> ActionParser::EndSection() {
    if (action_ && action_->NumCommands() > 0) {
        action_manager_->AddAction(std::move(action_));
    }

    return Success();
}

EndSection比较简单就是将解析完成的Action保存到ActionManager的actions_里面去,和ServiceList中的services_有异曲同工之妙。

std::vector<std::unique_ptr<Action>> actions_;
std::vector<std::unique_ptr<Service>> services_;

这里的action_manager_是在哪里赋值的呢,这个得回到system/core/init/init.cpp里面去查看如下代码:

    const BuiltinFunctionMap function_map;
    Action::set_function_map(&function_map);

    subcontexts = InitializeSubcontexts();

    ActionManager& am = ActionManager::GetInstance();//这个是重点,
    ServiceList& sm = ServiceList::GetInstance();
	LoadBootScripts(am, sm);//通过参数引入,每次还得回头看看

到这里ActionParser解析Action section已经告一段落了,老规矩还是总结一下ActionParser中三个重要的方法都做了那些工作,这个也是对我们阶段性成果的鼓励不是:

  • ParseSection函数主要是解析trigger触发条件,然后将解析的trigger构造一个Action对象
  • ParseLineSection的作用主要解析Action section中的command,然后在map中查找command对应的执行函数,然后将其添加到前面构建的Action中
  • EndSection将前面构建的Action加入到ActionManager的成员队里actions_里面。

2.6 ImportParser

??ActionParser和ServiceParser已经被我们完美的解决掉了,剩下的只有ImportParser了。
??ImportParser比较简单,发现ParseLineSection、EndSection都是空实现,只实现了ParseSection和EndFile,那我们来看看它的ParseSection函。为啥这么简单,因为import就一句话的事情,就是这么简单。常见的import如下:

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
import /init.xxxdroid.common.rc

2.6.1 ImportParser::ParseSection

ParseSection函数处理逻辑比较简单,其主要流程如下:

  • 首先检查单词只能是两个,因为只能是import xxx 这种语法
  • 然后调用expand_props处理第二个参数,最后将结果放入数组imports_存起来

函数定义于system\core\init\import_parser.cpp。

Result<Success> ImportParser::ParseSection(std::vector<std::string>&& args,
                                           const std::string& filename, int line) {
    if (args.size() != 2) {//检查参数只能是两个
        return Error() << "single argument needed for import\n";
    }

    std::string conf_file;
    bool ret = expand_props(args[1], &conf_file);//处理第二个参数
    if (!ret) {
        return Error() << "error while expanding import";
    }

    LOG(INFO) << "Added '" << conf_file << "' to import list";
    if (filename_.empty()) filename_ = filename;
    imports_.emplace_back(std::move(conf_file), line);//存入数组
    return Success();
}

让我们接着继续解读代码,expand_props定义在system/core/init/util.cpp,其主要作用就是解析import加载的rc文件,为啥加载一个rc文件还要搞这么复杂呢。我猜测Google这么设计是为了兼容不同硬件配置和不同cpu位数的产品而为之。通过简单分析代码不难看出其作用就是找到x . y 或 {x.y}或x.y或x.y这种语法,将x.y取出来作为name,去属性系统中找对应的value,然后替换。

bool expand_props(const std::string& src, std::string* dst) {
    const char* src_ptr = src.c_str();

    if (!dst) {
        return false;
    }

    /* - variables can either be $x.y or ${x.y}, in case they are only part
     *   of the string.
     * - will accept $$ as a literal $.
     * - no nested property expansion, i.e. ${foo.${bar}} is not supported,
     *   bad things will happen
     * - ${x.y:-default} will return default value if property empty.
     */
    //这段英文大概的意思是 参数要么是$x.y,要么是${x.y},它们都是路径的一部分,$$表示字符 $ ,
    //${foo.${bar}}这种递归写法是不支持的,因为会发生一些糟糕的事情
    //${x.y:-default}会将default作为默认值返回,如果找不到对应的属性值的话
    while (*src_ptr) {
        const char* c;

        c = strchr(src_ptr, '$');
        if (!c) {//找不到$符号,即是直接引用rc文件,直接讲str_ptr返回,例如如下improtimport /init.environ.rc
            dst->append(src_ptr);
            return true;
        }

        dst->append(src_ptr, c);
        c++;

        if (*c == '$') {//跳过$
            dst->push_back(*(c++));
            src_ptr = c;
            continue;
        } else if (*c == '\0') {
            return true;
        }

        std::string prop_name;
        std::string def_val;
        if (*c == '{') {//找到 { 就准备找 }的下标,然后截取它们之间的字符串,对应${x.y}的情况,例如import /init.${ro.zygote}.rc
            c++;
            const char* end = strchr(c, '}');
            if (!end) {
                // failed to find closing brace, abort.
                LOG(ERROR) << "unexpected end of string in '" << src << "', looking for }";
                return false;
            }
            prop_name = std::string(c, end);//截取{}之间的字符串作为name,即ro.zygote
            c = end + 1;
            size_t def = prop_name.find(":-");//如果发现有 ":-" ,就将后面的值作为默认值先存起来
            if (def < prop_name.size()) {
                def_val = prop_name.substr(def + 2);
                prop_name = prop_name.substr(0, def);
            }
        } else {//对应$x.y的情况
            prop_name = c;
            LOG(ERROR) << "using deprecated syntax for specifying property '" << c << "', use ${name} instead";
            c += prop_name.size();
        }

        if (prop_name.empty()) {
            LOG(ERROR) << "invalid zero-length property name in '" << src << "'";
            return false;
        }

        std::string prop_val = android::base::GetProperty(prop_name, "");//通过name在属性系统中找对应的value,内部调用的是之前属性系统的__system_property_find函数,这里ro.zygote的取值是zygote64_32
        //没有找到值,如果有默认值就返回默认值
        if (prop_val.empty()) {
            if (def_val.empty()) {
                LOG(ERROR) << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'";
                return false;
            }
            prop_val = def_val;
        }

        dst->append(prop_val);
        src_ptr = c;
    }

    return true;
}

2.6.2 ImportParser::EndFile

??EndFile比较简单就是遍历imports_取出其中的import文件,然后调用ParseConfig解析完整的路径,即回到前面讲述的内容了。

void ImportParser::EndFile() {
    auto current_imports = std::move(imports_);
    imports_.clear();
    for (const auto& [import, line_num] : current_imports) {
        if (!parser_->ParseConfig(import)) {
            PLOG(ERROR) << filename_ << ": " << line_num << ": Could not import file '" << import
                        << "'";
        }
    }
}

2.7 SectionParser以及子类解析器总结

??通过前面的章节2.4, 2.5, 2.6三个章节我们将SectionParser的三个核心解析器ActionParser,ServiceParser,ImportParser都已经讲解完了,而这几个解析器分别实现了ParseSection、ParseLineSection、EndSection、EndFile四个函数。下面将这三个解析器放在一起总结一下:

  • ParseSection用于解析各种section的第一行,例如:

    service console /system/bin/sh #Service section第一行
    on early-init                  #Action section第一行
    import /init.${ro.zygote}.rc   #仅此一行,童叟无欺
    
  • ParseLineSection用于解析section的command和option,例如:

    #Service section的options
    class core
    console
    disabled
    user shell
    group shell log readproc
    seclabel u:r:shell:s0
    setenv HOSTNAME console
    
    #action section的command
    # Set init and its forked children's oom_adj.
    write /proc/1/oom_score_adj -1000
    
    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0
    
    # Set the security context of /adb_keys if present.
    restorecon /adb_keys
    
    # Set the security context of /postinstall if present.
    restorecon /postinstall
    
    # Mount cgroup mount point for cpu accounting
    mount cgroup none /acct nodev noexec nosuid cpuacct
    mkdir /acct/uid
    
    start ueventd
    
  • EndSection用于处理Action和Service同名的情况,以及将解析的对象存入数组备用

  • EndFile只有在ImportParser中有用到,主要是解析导入的.rc文件。

从代码层方面对init.rc的五类声明已经解析完成了,接下来上一个代码类图来总结一下init进程中五类声明的关系。
在这里插入图片描述

3. 加入一些事件和一些Action

??在前面的篇章中我们已经解析完了init.xx.rc中的Action section了,但是仅仅是将这些数据存储到了相对应的数据结构和_action 链表中,_action 链表中包含一些触发条件下(_trigger)的执行动作(_commands),那么这些触发条件是什么时候发生呢。这个还是需要一些额外的配置,也需要加入触发条件准备去触发。

int main(int argc, char** argv) {
	......
	// Turning this on and letting the INFO logging be discarded adds 0.2s to
    // Nexus 9 boot time, so it's disabled by default.
    if (false) DumpState();//打印当前Service section和Action section的相关信息,但是并没有执行,因为是flase

    am.QueueEventTrigger("init-env");
    am.QueueEventTrigger("early-init");//QueueEventTrigger用于触发Action,这里触发 early-init事件,这里并没有真正的触发,只是将EventTrigger信息加入到ActionManager的链表中了

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    //QueueBuiltinAction用于添加Action,即添加Action section并不止一种方式,其中第一个参数是Action要执行的command,第二个参数是Trigger
    am.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(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    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(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("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");
	......
}

3.1 QueueEventTrigger

??QueueEventTrigger的代码定义在system/core/init/action_manager.cpp中,注意这段代码并没有真正的去触发trigger,而是将trigger字符串添加到ActionManager的event_queue_的链表中,待后续处理。

void ActionManager::QueueEventTrigger(const std::string& trigger) {
    event_queue_.emplace(trigger);
}

//代码定义于system\core\init\action_manager.h
class ActionManager {
  public:
	......
    void QueueEventTrigger(const std::string& trigger);
	......

  private:
	......

    std::vector<std::unique_ptr<Action>> actions_;
    std::queue<std::variant<EventTrigger, PropertyChange, BuiltinAction>> event_queue_;
	......
};

3.2 QueueBuiltinAction

??QueueBuiltinAction的代码定义在system/core/init/action_manager.cpp中,这个函数有两个参数,第一个函数是一个函数指针,第二个参数是字符串。该函数的处理逻辑如下:

  • 首先通过第二个参数构建一个Action
  • 把第一个参数函数指针通过AddCommand添加到前面创建的Action里面
  • 最后将Action的触发条件加入到event_queue_触发队列中
void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) {
    auto action = std::make_unique<Action>(true, nullptr, "<Builtin Action>", 0, name,
                                           std::map<std::string, std::string>{});
    std::vector<std::string> name_vector{name};

    action->AddCommand(func, name_vector, 0);//往Action中添加Command

    event_queue_.emplace(action.get());//触发队列中加入触发条件
    actions_.emplace_back(std::move(action));//将Action加入到actions_列表
}

4. Trigger触发顺序以及监听各种触发

4.1 Trigger触发顺序以及内容

??通过QueueBuiltinAction 和QueueEventTrigger 上述两步,将需要处理的 trigger 添加到 trigger_queue_中,而 trigger_queue_本身就是一个队列,所以先加进去的,先执行,后加入的,后执行。也就是通过这种方式,init.rc 中所列 action的执行顺序得到的确认。

4.1.1 Trigger触发顺序

??我们知道trigger_queue_的触发顺序是以队列形式进行的,那么其具体流程是什么呢,这里给出一个流程图以供大家参考(这里的前提是Android源码选择了非加密模式)。
在这里插入图片描述

4.1.2 Trigger触发内容

??前面我们知道了Trigger的触发顺序,现在让我们看看每个Trigger主要做了那些事情。

Trigger启动阶段触发内容
early-init初始化第一阶段,设置 init 进程 score adj 值,重置安全上下文,启动 uevent 服务
wait_for_coldboot_donewait uevent_main – device_init 完成 coldboot_done 目录创建,Timeout 1s
mix_hwrng_into_linux_rng读取 512 bytes 硬件随机数,写入 linux RNG,不支持HWrandom 直接返回,不影响 init 启动
keychord_initkeychord 是组合按键,keychord 为每个服务配置组合键,在服务解析时为指定服务设置相应的键码值
console_init如果ro.boot.console 指定了控制台终端,那么优先使用这个控制台,如果没有指定,那么将使用默认控制台终端/dev/console
init创建文件系统, mount节点以及写内核变量
late-init触发各种trigger,trigger early-fs fs post-fs load_system_props_action post-fs load_persist_props_action firmware_mounts_complete early-boot boot
early-fs设置外部存储环境变量
fs专门用于加载各个分区,如mtd分区,创建adb设备目录,修改 adf 设备文件权限
post-fs修改productinfo用户群组,改变系统目录访问权限(kmsg、vmallcoinfo、cache等)
load_persist_props_action加载property 文件如 “/system/build.prop”"/vendor/build.prop" “/factory/factory.prop”
post-fs-data创建、改变/data 目录以及它的子目录的访问权限,启动 vold、debuggerd 服务,bootchart_init
load_persisit_props_action启动logd服务,load property file /data/property,"/data/local.prop"
firmware_mounts_complete:删除 dev/.booting 目录
early-boot修改 proc、sys/class 子目录访问权限
boot正常的启动命令,设置 usb 厂商参数、CPU 参数,修改 sensorhub、 bluetooth、gnss、thermal 目录访问权限,网络参数设置。 启动 Core class servic
charge当手机处于充电模式时(关机情况下充电), 需要执行的命令
nonencrypted启动 main、late_start class service(这里的前提条件是Android 源码编译选择了非加密模式)

4.2 监听各种触发

??如果说之前的所有工作都是往各种链表、队列里面存入信息,并没有真正去触发,而是做了重复的准备工作,前戏已经够了,那么接下来的工作就是真正去触发这些事件,以及用epoll不断监听新的事件。

int main(int argc, char** argv) {
	......
	while (true) {
        // By default, sleep until something happens.
        int epoll_timeout_ms = -1;//epoll超时时间,相当于阻塞时间

        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }
		/*
         * 1.waiting_for_prop和IsWaitingForExec都是判断一个Timer为不为空,相当于一个标志位
         * 2.waiting_for_prop负责属性设置,IsWaitingForExe负责service运行
         * 3.当有属性设置或Service开始运行时,这两个值就不为空,直到执行完毕才置为空
         * 4.其实这两个判断条件主要作用就是保证属性设置和service启动的完整性,也可以说是为了同步
         */
        //判断是否有事情需要处理
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
        	//执行每个action中携带的command命令
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            if (!shutting_down) {
                auto next_process_restart_time = RestartProcesses();//重启一些挂掉的进程

                // If there's a process that needs restarting, wake up in time for that.
                if (next_process_restart_time) {//当有进程需要重启时,设置epoll_timeout_ms为重启等待时间
                    epoll_timeout_ms = std::chrono::ceil<std::chrono::milliseconds>(
                                           *next_process_restart_time - boot_clock::now())
                                           .count();
                    if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;//当还有命令要执行时,将epoll_timeout_ms设置为0
                }
            }
            
			/*
             * 1.epoll_wait与上一篇中讲的epoll_create1、epoll_ctl是一起使用的
             * 2.epoll_create1用于创建epoll的文件描述符,epoll_ctl、epoll_wait都把它创建的fd作为第一个参数传入
             * 3.epoll_ctl用于操作epoll,EPOLL_CTL_ADD:注册新的fd到epfd中,EPOLL_CTL_MOD:修改已经注册的fd的监听事件,EPOLL_CTL_DEL:从epfd中删除一个fd;
             * 4.epoll_wait用于等待事件的产生,epoll_ctl调用EPOLL_CTL_ADD时会传入需要监听什么类型的事件,
             *   比如EPOLLIN表示监听fd可读,当该fd有可读的数据时,调用epoll_wait经过epoll_timeout_ms时间就会把该事件的信息返回给&ev
             */
            // If there's more work to do, wake up again immediately.
            //有 action 待处理,不等待
            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)();//当有event返回时,取出ev.data.ptr(之前epoll_ctl注册时的回调函数),直接执行
            						     //上一篇中在signal_handler_init和start_property_service有注册两个fd的监听,一个用于监听SIGCHLD(子进程结束信号),一个用于监听属性设置
        }
    }

    return 0;
}

4.2.1 ExecuteOneCommand

??该代码定义在system/core/init/action_manager.cpp中,从函数名字可以看出它是执行一个Command,该函数的处理逻辑如下:

  • 从EventTrigger触发队列event_queue_取出一个trigger,然后遍历所有action,找出满足trigger条件的action加入待执行列表current_executing_actions_中
  • 接着从这个列表中取出一个action,执行它的第一个命令,并将命令所在下标自加1。由于ExecuteOneCommand外部是一个无限循环,因此按照上面的逻辑一遍遍执行,将按照trigger表的顺序,依次执行满足trigger条件的action,然后依次执行action中的命令
void ActionManager::ExecuteOneCommand() {
    // Loop through the event queue until we have an action to execute
     //遍历所有Action
    while (current_executing_actions_.empty() && !event_queue_.empty()) {
        for (const auto& action : actions_) {
        	//判断是否有满足触发条件的Action
            if (std::visit([&action](const auto& event) { return action->CheckEvent(event); },
                           event_queue_.front())) {
                current_executing_actions_.emplace(action.get());
            }
        }
        event_queue_.pop();//从trigger_queue_中踢除一个trigger
    }

    if (current_executing_actions_.empty()) {
        return;
    }

    auto action = current_executing_actions_.front();//从满足trigger条件的action队列中取出一个action

    if (current_command_ == 0) {
        std::string trigger_name = action->BuildTriggersString();
        LOG(INFO) << "processing action (" << trigger_name << ") from (" << action->filename()
                  << ":" << action->line() << ")";
    }

    action->ExecuteOneCommand(current_command_);//执行该action中的第current_command_个命令,只执行一个

    // 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_;//下标加1
    if (current_command_ == action->NumCommands()) {//判断是否是最后一个command
        current_executing_actions_.pop();//将该action从current_executing_actions_中踢除
        current_command_ = 0;
        if (action->oneshot()) {//如果action只执行一次,将该action从数组actions_中踢除
            auto eraser = [&action](std::unique_ptr<Action>& a) { return a.get() == action; };
            actions_.erase(std::remove_if(actions_.begin(), actions_.end(), eraser));
        }
    }
}

从上面的分析可以看到ExecuteOneCommand函数每次从event_queue_链表中取出一个trigger,并判断是否有_action 的触发条件匹配,如果有则依次取出匹配的 action 对象中的一个 command 命令并执行(一个action可能携带多个command)。其执行循序遵从如下逻辑:

  • 当一个 action 对象所有的 command 均执行完毕后,再执行下一个action
  • 当一个 trigger 触发时间点对应的 action 对象均执行完毕后,再执行下一个 trigger 对应 action。

4.2.2 监听property变化触发

通过前面我们知道Trigger触发有两种情况,第一种是直接QueueEventTrigger然后另外一种就是满足property,我们通过前面篇章源码详解Android 9.0§ 系统启动流程之init进程(第二阶段)知道但是属性系统的写操作只能在 init 进程中进行,其它进程进行属性的写操作也需要通过 init 进程。最终会调用到HandlePropertySet进行处理。这个函数处理分两种情况:

  • 以ctl.开头的,会调用HandleControlMessage最终会调用到init.cpp中,通过ctl来处理ctl.start或者ctl.stop执行具体Service section,其逻辑如下

    struct ControlMessageFunction {
    ControlTarget target;
    std::function<Result<Success>(Service*)> action;
    };
    
    static const std::map<std::string, ControlMessageFunction>& get_control_message_map() {
        // clang-format off
        static const std::map<std::string, ControlMessageFunction> control_message_functions = {
            {"start",             {ControlTarget::SERVICE,   DoControlStart}},
            {"stop",              {ControlTarget::SERVICE,   DoControlStop}},
            {"restart",           {ControlTarget::SERVICE,   DoControlRestart}},
            {"interface_start",   {ControlTarget::INTERFACE, DoControlStart}},
            {"interface_stop",    {ControlTarget::INTERFACE, DoControlStop}},
            {"interface_restart", {ControlTarget::INTERFACE, DoControlRestart}},
        };
        // clang-format on
    
        return control_message_functions;
    }
    void HandleControlMessage(const std::string& msg, const std::string& name, pid_t pid) {
        const auto& map = get_control_message_map();
        const auto it = map.find(msg);
    
        if (it == map.end()) {
            LOG(ERROR) << "Unknown control msg '" << msg << "'";
            return;
        }
    
        std::string cmdline_path = StringPrintf("proc/%d/cmdline", pid);
        std::string process_cmdline;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            std::replace(process_cmdline.begin(), process_cmdline.end(), '\0', ' ');
            process_cmdline = Trim(process_cmdline);
        } else {
            process_cmdline = "unknown process";
        }
    
        LOG(INFO) << "Received control message '" << msg << "' for '" << name << "' from pid: " << pid
                  << " (" << process_cmdline << ")";
    
        const ControlMessageFunction& function = it->second;
    
        if (function.target == ControlTarget::SERVICE) {
            Service* svc = ServiceList::GetInstance().FindService(name);
            if (svc == nullptr) {
                LOG(ERROR) << "No such service '" << name << "' for ctl." << msg;
                return;
            }
            if (auto result = function.action(svc); !result) {
                LOG(ERROR) << "Could not ctl." << msg << " for service " << name << ": "
                           << result.error();
            }
    
            return;
        }
        if (function.target == ControlTarget::INTERFACE) {
            for (const auto& svc : ServiceList::GetInstance()) {
                if (svc->interfaces().count(name) == 0) {
                    continue;
                }
    
                if (auto result = function.action(svc.get()); !result) {//执行具体函数,可能是start,stop等
                    LOG(ERROR) << "Could not handle ctl." << msg << " for service " << svc->name()
                               << " with interface " << name << ": " << result.error();
                }
    
                return;
            }
    
            LOG(ERROR) << "Could not find service hosting interface " << name;
            return;
        }
    
        LOG(ERROR) << "Invalid function target from static map key '" << msg
                   << "': " << static_cast<std::underlying_type<ControlTarget>::type>(function.target);
    }
    
  • 对于其它的属性,最终设置成功后都会调用 property_changed函数来通知 init 进程属性进行了修改,该代码在/system/core/init/init.cpp中

    void property_changed(const std::string& name, const std::string& value) {
        // If the property is sys.powerctl, we bypass the event queue and immediately handle it.
        // This is to ensure that init will always and immediately shutdown/reboot, regardless of
        // if there are other pending events to process or if init is waiting on an exec service or
        // waiting on a property.
        // In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
        // commands to be executed.
        if (name == "sys.powerctl") {//处理sys.powerctl命令
            // Despite the above comment, we can't call HandlePowerctlMessage() in this function,
            // because it modifies the contents of the action queue, which can cause the action queue
            // to get into a bad state if this function is called from a command being executed by the
            // action queue.  Instead we set this flag and ensure that shutdown happens before the next
            // command is run in the main init loop.
            // TODO: once property service is removed from init, this will never happen from a builtin,
            // but rather from a callback from the property service socket, in which case this hack can
            // go away.
            shutdown_command = value;
            do_shutdown = true;
        }
    	
    	//通知ActionManager属性变化
        if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
    	
    	//当前正在设置属性,进行一些同步化的操作
        if (waiting_for_prop) {
            if (wait_prop_name == name && wait_prop_value == value) {
                LOG(INFO) << "Wait for property took " << *waiting_for_prop;
                ResetWaitForProp();
            }
        }
    }
    

从代码中可以看到,property_triggers_enabled 是 <属性变化触发 action> 的使能点,开启之后每次属性发生变化都会调用 ActionManager.QueuePropertyChange(name, value) 函数

//代码定义于system\core\init\action_manager.cpp
void ActionManager::QueuePropertyChange(const std::string& name, const std::string& value) {
    event_queue_.emplace(std::make_pair(name, value));//添加触发条件
}

这个函数比较简单就是event_queue_列表添加触发条件,将已改变属性的键值对作为参数。运行队列中已经添加了该属性的变化触发条件,同样通过 am.ExecuteOneCommand() 函数遍历所有的 _actions 链表,执行相应的 commands。

4.2.3 疑问点

??通过前面的分析完了监听各种触发service,可是读者有没有发现一个问题假如是我们的service没有直接在Action中通过start启动,难道服务就不启动了吗。其实不然,通常这种服务会配置一个class的option,如下所示:

service ueventd /sbin/ueventd
    class core
    critical
    seclabel u:r:ueventd:s0

service healthd /sbin/healthd
    class core
    critical
    seclabel u:r:healthd:s0
    group root system wakelock

service console /system/bin/sh
    class core
    console
    disabled
    user root
    group shell log readproc
    seclabel u:r:shell:s0

然后会通过class_start来触发启动相关配置了class main/core的service,如下所示。这里的class_start就会触发启动相关配置的service。
在这里插入图片描述

5. 小结

这一阶段Init进程做了许多重要的事情,比如解析.rc文件,这里配置了所有需要执行的action和需要启动的service,Init进程根据语法一步步去解析.rc,将这些配置转换成一个个数组、队列,然后去处理这些数组、队列中的command和service,并且通过epoll监听子进程结束和属性设置.

至此,我已经将Init进程的三个阶段讲解完了,下一篇我将讲解.rc中配置的一个重要的service–zygote,它是我们app程序的鼻祖.

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-10 12:30:55  更:2021-11-10 12:31:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:31:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码