Android 9之init进程启动源码分析指南之三



前言

  在前面的篇章Android P之init进程启动源码分析指南之一和Android P之init进程启动源码分析指南之二讲解了init进程经过前面两个阶段以后,已经建立了相关的文件系统,属性系统,SELinux安全策略系统。但是我们知道init进程做的远远不止这些,还要启动一些Android的native service系统服务及其其他相关的操作,但是如果都是像属性系统和SELinux系统那样一行行代码去做,显得有点杂乱繁琐,而且不容易扩展,所以Android系统引入了init.rc。这个就是我们本篇要讲解的重点,init进程解析init.rc相关文件。

本篇章主要讲解的内容大概如下:

  • Android Init Language语法介绍
  • 解析相关init.rc文件
  • 继续解析init进程启动其它相关的逻辑,主要是一些Action事件的加入和触发,以及一些其它的事件触发的监听

注意:本文演示的代码是Android P高通msm8953平台源码。涉及的源码如下:

system/core/init/init.cppsystem/core/init/parser.cppsystem/core/init/parser.hsystem/core/init/action.hsystem/core/init/action.cppsystem/core/init/action_parser.hsystem/core/init/action_parser.cppsystem/core/init/service.hsystem/core/init/service.cppsystem/core/init/import_parser.hsystem/core/init/import_parser.cppsystem/core/init/tokenizer.hsystem/core/init/tokenizer.cppsystem/core/init/keyword_map.hsystem/core/init/builtins.cppsystem/core/init/util.cppsystem/core/init/action_manager.hsystem/core/init/action_manager.cpp


一. init.rc配置文件语法

  init.rc是一个可配置的初始文件,是由Android初始化语言编写(Android Init Language)编写的脚本,这里顺便扩展一些Android里面还有那些类似的Android独有的语言呢(譬如AIDL, HIDL等)?
init.rc文件主要包含如下五类声明:

  • Action
  • Command
  • Service
  • Options
  • Import

  init.rc的配置代码在system/core/rootdir/init.rc中,如果你够仔细的话,会发现在统计目录下还有许多的init.xxx.rc类似文件,这个后续会讲解为什么会存在这么多的init.xxx.rc文件。如下,

  init.rc文件是在init进程启动后执行的启动脚本,文件中记录着init进程需执行的操作,关于init.rc的相关介绍Android提供了一个官方的参考文档,在Android源码中的路径如下所示system/core/init/README.md中有详细介绍,不过是英文。

Android Init Language---------------------The Android Init Language consists of five broad classes of statements:Actions, Commands, Services, Options, and Imports.All of these are line-oriented, consisting of tokens separated bywhitespace.  The c-style backslash escapes may be used to insertwhitespace into a token.  Double quotes may also be used to preventwhitespace from breaking text into multiple tokens.  The backslash,when it is the last character on a line, may be used for line-folding.Lines which start with a `#` (leading whitespace allowed) are comments.System properties can be expanded using the syntax`${property.name}`. This also works in contexts where concatenation isrequired, such as `import /init.recovery.${ro.hardware}.rc`.Actions and Services implicitly declare a new section.  All commandsor options belong to the section most recently declared.  Commandsor options before the first section are ignored.Services have unique names.  If a second Service is definedwith the same name as an existing one, it is ignored and an errormessage is logged.

  下面让我们对rc文件中涉及到的五种类型的声明,一一讲解,但不包会。


1.1 Action

  Action是以Section的形式出现的,每个Action Section可以含有若干的Command。Section只有起始标记,却没有明确的结束标记,也就是说,是用“后一个Section”的起始来结束"前一个Section",这里需要注意的一点是Action可以重复,但是最后会合并到一起。
  口干舌燥的说了这么大一堆,是不是还是没有搞明白,还是上个实例来说明一下,其实rc里面的Action就是以"on"关键词开头的动作列表(action list):

on early-init             #Action类型语句    # Set init and its forked children's oom_adj.    write /proc/1/oom_score_adj -1000    #Command语句    # Disable sysrq from keyboard    write /proc/sys/kernel/sysrq 0    # Set the security context of /adb_keys if present.    restorecon /adb_keys

  Action需要一个触发器(trigger)来触发它,这个会在解析init进程的代码里面看到,一旦满足了触发条件,这个Action就会被加到执行队列的末尾。Action类型的语句格式是:

    on <trigger> [&& <trigger>]*  #设置触发器       <command>                  #动作触发之后要执行的命令       <command>       <command>

  trigger触发的条件可以分为如下几种情况:

  • 这里的trigger可以是字符串,如
on early-init  #表示当trigger early-init或QueueEventTrigger("early-init")调用时触发
  • 这里的trigger也可以是属性,如
on property:sys.boot_from_charger_mode=1 #表示当sys.boot_from_charger_mode的值通过property_set设置为1时触发    class_stop charger    trigger late-initon property:sys.init_log_level=*  # *表示任意值触发    loglevel ${sys.init_log_level}
  • 这里的trigger,条件可以是多个,用&&连接,如
on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file  #表示三个条件都满足的时候才触发    # A/B update verifier that marks a successful boot.    exec_start update_verifier_nonencrypted    start netd    start zygote    start zygote_secondary

1.2 Service

  Service也是以Section的形式出现的,其中每个Service Section可以包含有若干的Option。Section 只有起始标记,却没有明确的结束标记,也就是说,是用“后一个 Section”的起始来结束“前一个 Section”。这里有一点需要需要注意地是Service 不能出现重名。
  口干舌燥的说了这么大一堆,是不是还是没有搞明白,还是上个实例来说明一下,其实rc里面的Service就是以“service”关键字开头的 服务列表(service list):

## Daemon processes to be run by init.##service ueventd /sbin/ueventd   #Service类型语句    class core    critical    seclabel u:r:ueventd:s0    shutdown critical

  Service表示一个服务程序,会通过 start command 执行。并根据 option 参数判断服务在退出时是否需要自动重启。Service的语句结构如下:

    service <name> <pathname> [ <argument> ]* #<执行程序路径><传递参数>       <option>  #option是service的修饰此,影响什么时候,如何启动service       <option>       ... 

  从上面对Action和Service的解读中我们可以看出,这两个声明主要借助于系统环境变量或者Linux命令来在Android启动的不同阶段做一些工作,主要如下:

  • 动作列表用于创建所需目录,以及为某些特定文件指定权限;
  • 服务列表用来记录init进程需要启动的一些子进程,如上面代码所示,service关键字后的第一个字符串表示服务(子进程)的名称,第二个字符串表示服务的执行路径。

1.3 Options

  Options是Services的参数配置,他们将影响Service如何运行以及运行时机。比如Android大名鼎鼎的zygote进程的Options配置如下:

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server    class main    priority -20     user root    group root readproc reserved_disk    socket zygote stream 660 root system    onrestart write /sys/android_power/request_state wake    onrestart write /sys/power/state on    onrestart restart audioserver    onrestart restart cameraserver    onrestart restart media    onrestart restart netd    onrestart restart wificond    onrestart restart vendor.servicetracker-1-0    writepid /dev/cpuset/foreground/tasks

  Options类型比较多,这里就不一一介绍了,感兴趣的可以阅读system/core/init/README.md,里面有非常详细的介绍。


1.4 Command

  Command通常和Action关联在一起,一般表示一些具体的操作,通常是借助Linux命令完成相关的操作,譬如:

mkdir /dev/fscklogs 0770 root system //新建目录class_stop charger //终止服务trigger late-init  //触发late-init

  上面的Command只是我随意列举的,还有很多,这里就不一一介绍了,感兴趣的可以阅读system/core/init/README.md,里面有非常详细的介绍。


1.5 Import

  在前面我们讲过在system/core/rootdir文件目录下面还有其它类型的init.xxx.rc,那么这些rc文件是怎么加载的呢,这里就是import的功劳了。 import 则是导入其它 init..rc 用的,如 import /init.${ro.hardware}.rc。其它的 init..rc 就是通过 import 导入进来的。

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

  /init.rc 是最主要的一个.rc文件,它由init进程在初始化时加载,主要负责系统初始化,它会导入 /init.${ro.hardware}.rc ,这个是系统级核心厂商提供的主要.rc文件

  当执行 DoFirstStageMount语句时,init进程将加载所有在 /{system,vendor,odm}目录下的文件当然也包括对应目录下的rc文件,挂载好文件系统后,这些目录将会为Actions和Services服务。这个也是谷歌为了划分层次,针对不同的开发者不同开发阶段而做的优化。

  上述三个目录用于扩展的init.rc功能分别如下:

  • /system/etc/init/ 用于系统本身,比如SurfaceFlinger, MediaService, and logcatd.
[email protected] mediaextractor.rcatrace.rc                             mediametrics.rcaudioserver.rc                        mediaserver.rcbootanim.rc                           mtpd.rcbootstat.rc                           netbox.rcbugreport.rc                          netd.rccameraserver.rc                       performancemanager.rccmd_services.rc                       phasecheckserver.rcdataLogDaemon.rc                      racoon.rcdata_rps.rc                           servicemanager.rcdrmserver.rc                          storaged.rcdumpstate.rc                          surfaceflinger.rcengpc.rc                              thermalservice.rcgatekeeperd.rc                        tiny_firewall.rchwservicemanager.rc                   tombstoned.rcims_bridged.rc                        uncrypt.rcinstalld.rc                           vdc.rckeystore.rc                           vold.rclmkd.rc                               webview_zygote32.rclog_service.rc                        wifi-events.rclogd.rc                               wificond.rcmdnsd.rc                              ylog.rcmediadrmserver.rc
  • /vendor/etc/init/ 用于SoC(系统级核心厂商,如高通),为他们提供一些核心功能和服务
[email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected][email protected]rvice.rc[email protected][email protected][email protected]
  • /odm/etc/init/ 用于设备制造商(odm定制厂商,如华为、小米),为他们的传感器或外围设备提供一些核心功能和服务


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

  前面的篇章我们恶补了相关的rc语法知识,前面这些都是为了init进程解析init.rc铺垫的,任何事情不都有个前戏不是。好吗废话不多说,直接开撸代码。

//代码定义在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);......}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;}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()) {        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 {        parser.ParseConfig(bootscript);    }}

2.1 Parser

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

Parser parser = CreateParser(action_manager, service_list);

这段代码很好理解,初始化ServiceParser用来解析"service"块,初始化ActionParser用来解析"on"块,初始化ImportParser用来解析“import”块。

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));//增加ServiceParser为一个section,对应的name为service    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));//增加ActionParser为一个section,对应的name为on    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));//增加ImportParser为一个section,对应的name为import

2.2 ParseConfig

  下面就要开始分析解析过程了,ParseConfig的代码定义在system/core/init/parser.cpp中。

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);//传入参数为文件地址}

让我们先从大到小的顺序先来看看ParseConfigDir函数的内容:

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;}

可以看出这里的重点是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);                        !result) {                        (*parse_errors)++;                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();                    }                }                args.clear();//清空本次解析的数据                break;            case T_TEXT:                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 2struct 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.hclass 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对象    service_ = std::make_unique<Service>(name, restart_action_subcontext, str_args);    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();}Result<Success> Service::ParseLine(const std::vector<std::string>& args) {    static const OptionParserMap parser_map;    auto parser = parser_map.FindFunction(args);//查找命令对应的执行函数    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();}    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;    }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.3 ServiceParser::EndFile

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


2.5 ActionParser

  在 2.3章节中,我们完整介绍了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";    }......    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 startconst 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}}},        {"umount",                  {1,     1,    {false,  do_umount}}},        {"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;}

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.rcimport /init.usb.rcimport /init.${ro.hardware}.rcimport /vendor/etc/init/hw/init.${ro.hardware}.rcimport /init.usb.configfs.rcimport /init.${ro.zygote}.rcimport /init.xxxdroid.common.rc

2.6.1 ImportParser::ParseSection

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

  • 首先检查单词只能是两个,因为只能是import xxx 这种语法
  • 然后调用expand_props处理第二个参数,最后将结果放入数组imports_存起来
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文件还要搞这么复杂呢。我才谷歌这么设计是为了兼容不同硬件配置和不同cpu位数的产品而为之。通过简单分析代码不难看出其作用就是找到 x . y 或 {x.y}或 x.yx.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进程中是对这个五类声明关联起来的吗。


2.8 加入其它Action和Action触发条件

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

    // 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("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");

2.8.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);}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_;......};

2.8.2 QueueBuiltinAction

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

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

2.9 Trigger触发顺序以及内容

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

2.9.1 Trigger触发顺序

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

2.9.2 Trigger触发内容

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

Trigger启动阶段 触发内容
early-init 初始化第一阶段,设置 init 进程 score adj 值,重置安全上下文,启动 uevent 服务
wait_for_coldboot_done wait uevent_main – device_init 完成 coldboot_done 目录创建,Timeout 1s
mix_hwrng_into_linux_rng 读取 512 bytes 硬件随机数,写入 linux RNG,不支持HWrandom 直接返回,不影响 init 启动
keychord_init keychord 是组合按键,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 源码编译选择了非加密模式)

2.10 监听各种触发

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

    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                }            }            // 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)();        }    }

2.10.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。

2.10.2 监听property变化触发

  通过前面我们知道Trigger触发有两种情况,第一种是直接QueueEventTrigger然后另外一种就是满足property,我们通过前面篇章Android P之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) 函数

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。



总结

  随着Android版本越高,init的工作量也是越来越大了,分析起来不得不使出吃奶的力气了,在init进程的最后阶段主要工作是主要工作是讲解init.rc的基本语法,然后解析.rc文件,然后继续解析init进程启动其它相关的逻辑,主要是一些Action事件的加入和触发,以及一些其它的事件触发的监听。



写在最后

  Android P之init进程启动源码分析指南之三的告一段落了,不容易啊分析起来,在接下来的篇章我们将继续讲解Android P启动中非常重要的一个进程zygote启动流程。如果对给位有帮助欢迎点赞一个,如果写得有问题也欢迎多多指正。未完待续,关于zygote启动篇章详见Android P Zygote进程启动源码分析指南一。

更多相关文章

  1. C语言函数的递归(上)
  2. Android系统分析之Activity的启动流程
  3. 【Android(安卓)学习记录】Fragment注入漏洞
  4. Kotlin~基本语法
  5. Android(安卓)JSBridge的原理与实现
  6. android之事件分发机制
  7. Android(安卓)操作系统的内存回收机制
  8. Retrofit2.0+网络框架HTTP实战
  9. Android应用程序键盘(Keyboard)消息处理机制分析(二)

随机推荐

  1. Android使用第三方或者自制字体库(Typesp
  2. Android掌上背包游(1)
  3. OpenCV Android解决相机旋转90度及全屏问
  4. Android技术之ListView分割线显示和隐藏
  5. android 禁止Viewpager左右滑动功能
  6. Android开发工具更新ADT23,AS0.8.13下载地
  7. Android USB gadget
  8. android 升级ADT到22后,出现bug: Could not
  9. Android内部存储和外部存储的获取方法
  10. Android(安卓)Studio使用技巧-看这一篇就