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

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


Android 9 (P)系统启动及进程创建源码分析目录:

Android 9 (P)之init进程启动源码分析指南之一
Android 9 (P)之init进程启动源码分析指南之二
Android 9 (P)之init进程启动源码分析指南之三
Android 9 (P)核心服务和关键进程启动
Android 9 (P)Zygote进程启动源码分析指南一
Android 9 (P)Zygote进程启动源码分析指南二
Android 9 (P)系统启动之SystemServer大揭秘上
Android 9 (P)系统启动之SystemServer大揭秘下
Android 9 (P)应用进程创建流程大揭秘



引言

  在前面的篇章Android 9 (P)之init进程启动源码分析指南之一Android 9 (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.cpp
system/core/init/parser.cpp
system/core/init/parser.h
system/core/init/action.h
system/core/init/action.cpp
system/core/init/action_parser.h
system/core/init/action_parser.cpp
system/core/init/service.h
system/core/init/service.cpp
system/core/init/import_parser.h
system/core/init/import_parser.cpp
system/core/init/tokenizer.h
system/core/init/tokenizer.cpp
system/core/init/keyword_map.h
system/core/init/builtins.cpp
system/core/init/util.cpp
system/core/init/action_manager.h
system/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 by
whitespace.  The c-style backslash escapes may be used to insert
whitespace into a token.  Double quotes may also be used to prevent
whitespace 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 is
required, such as `import /init.recovery.${ro.hardware}.rc`.

Actions and Services implicitly declare a new section.  All commands
or options belong to the section most recently declared.  Commands
or options before the first section are ignored.

Services have unique names.  If a second Service is defined
with the same name as an existing one, it is ignored and an error
message 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-init

on 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> ]* #<service的名字><执行程序路径><传递参数>
       <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.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

  /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.
android.hidl.allocator@1.0-service.rc mediaextractor.rc
atrace.rc                             mediametrics.rc
audioserver.rc                        mediaserver.rc
bootanim.rc                           mtpd.rc
bootstat.rc                           netbox.rc
bugreport.rc                          netd.rc
cameraserver.rc                       performancemanager.rc
cmd_services.rc                       phasecheckserver.rc
dataLogDaemon.rc                      racoon.rc
data_rps.rc                           servicemanager.rc
drmserver.rc                          storaged.rc
dumpstate.rc                          surfaceflinger.rc
engpc.rc                              thermalservice.rc
gatekeeperd.rc                        tiny_firewall.rc
hwservicemanager.rc                   tombstoned.rc
ims_bridged.rc                        uncrypt.rc
installd.rc                           vdc.rc
keystore.rc                           vold.rc
lmkd.rc                               webview_zygote32.rc
log_service.rc                        wifi-events.rc
logd.rc                               wificond.rc
mdnsd.rc                              ylog.rc
mediadrmserver.rc
  • /vendor/etc/init/ 用于SoC(系统级核心厂商,如高通),为他们提供一些核心功能和服务
android.hardware.audio@2.0-service.rc
android.hardware.bluetooth@1.0-service.rc
android.hardware.camera.provider@2.4-service.rc
android.hardware.cas@1.0-service.rc
android.hardware.configstore@1.0-service.rc
android.hardware.drm@1.0-service.rc
android.hardware.dumpstate@1.0-service.rc
android.hardware.gatekeeper@1.0-service.rc
android.hardware.graphics.allocator@2.0-service.rc
android.hardware.graphics.composer@2.1-service.rc
android.hardware.health@1.0-service.rc
android.hardware.keymaster@3.0-service.rc
android.hardware.light@2.0-service.rc
android.hardware.media.omx@1.0-service.rc
android.hardware.memtrack@1.0-service.rc
android.hardware.sensors@1.0-service.rc
android.hardware.usb@1.1-service.rc
android.hardware.vibrator@1.0-service.rc
android.hardware.wifi@1.0-service.rc
  • /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 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对象
    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 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}}},
        {"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.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_存起来
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, "<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_列表
}

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_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 源码编译选择了非加密模式)

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。


2.11 疑问点

  通过前面的分析完了监听各种触发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。

XXX@Ubuntu16-YFErbu-01:~/ssd/qcom_64/msm8953-9/system/core/rootdir$ grep -nr "class_start core"
init.rc:691:    class_start core
XXX@Ubuntu16-YFErbu-01:~/ssd/qcom_64/msm8953-9/system/core/rootdir$ grep -nr "class_start main"
init.rc:694:    class_start main
init.rc:724:    class_start main
init.rc:731:    class_start main




总结

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




写在最后

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

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Retry机制可以应用于整个Step的重试,也可以应用于特定任务(例如,读取、处理或写入)的重试。以下是两种应用Spring Retry机制的方法: 1. 应用于整个Step的重试: 在Step中使用faultTolerant()方法启用重试机制,并设置RetryTemplate和最大重试次数。例如: ``` @Bean public Step step1() { return stepBuilderFactory.get("step1") .<Person, Person>chunk(10) .reader(itemReader()) .processor(itemProcessor()) .writer(itemWriter()) .faultTolerant() .retryTemplate(retryTemplate()) .retryLimit(3) .build(); } ``` 在上面的代码中,我们通过调用faultTolerant()方法启用重试机制,并设置了RetryTemplate和最大重试次数。当任务执行失败时,Spring Batch会自动使用RetryTemplate进行重试,直到达到最大重试次数或任务成功执行为止。 2. 应用于特定任务的重试: 在Tasklet或ItemReader/ItemWriter/ItemProcessor中使用RetryTemplate对象执行任务,并设置RetryPolicy。例如: ``` @Component public class MyTasklet implements Tasklet { @Autowired private RetryTemplate retryTemplate; @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { return retryTemplate.execute(context -> { // 执行任务 // 如果任务失败,会根据RetryPolicy进行重试 return RepeatStatus.FINISHED; }); } } ``` 在上面的代码中,我们在MyTasklet中使用RetryTemplate对象执行任务,并设置了RetryPolicy。当任务执行失败时,Spring Batch会自动使用RetryTemplate进行重试,直到达到最大重试次数或任务成功执行为止。 无论是应用于整个Step的重试,还是应用于特定任务的重试,都需要在RetryPolicy中指定重试的异常类型。例如,在RetryPolicy中指定SQLException.class表示只有在任务执行时抛出SQLException异常时才进行重试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值