Android Q Init进程解析 rc文件的流程分析

 

init进程是Android系统在内核启动完毕之后,启动的第一个进程。这个进程会创建运行Android上层所需要的各种运行环境。

这篇博文主要分析 init进程具体是如何解析 init.rc 以及其他的rc文件的。

一,所涉及到的资源文件有:

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

二,首先介绍下rc文件的基本构成。

下面这段代码是init.rc 的部分代码。

代码中import 语句表示引入新的 rc文件。这里需要注意的是部分rc文件的路径是以系统属性的形式体现的。后续解析 import关键字的时候,是需要将这些系统属性名称替换成具体的属性值。

代码中的 on 表示事件,它的事件名称是 early-init,表示系统在执行 early-init 这个事件的时候触发,触发之后,会执行下面的所有操作,直到遇见下一个事件。

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

on early-init
    # 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

on 的格式如下:

on后面跟着一个触发器,当trigger被触发时,command1,command2,command3,会依次执行,直到下一个Action或下一个Service。

on <trgger> [&& <trigger>]*
   <command1>
   <command2>
   <command3>
   ...

trigger即我们上面所说的触发器,本质上是一个字符串,能够匹配某种包含该字符串的事件.
trigger又被细分为事件触发器(event trigger)和属性触发器(property trigger).
Triggers(触发器)是一个用于匹配特定事件类型的字符串,用于使Actions发生。

其中部分on事件不止一个条件,可能会有多个条件,通常是 事件名称+系统属性满足的方式触发执行。

比如:启动zygote的事件就是满足三个必要的条件的情况下启动的。

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

(在zygote-start事件的情况下,后面两个系统属性必须满足才可以执行后面的操作)

另外rc文件中还有一个service开头的标志,表示当前是一个服务。

Services(服务)是一个程序,以 service开头,由init进程启动,一般运行于另外一个init的子进程,所以启动service前需要判断对应的可执行文件是否存在。init生成的子进程,定义在rc文件,其中每一个service,在启动时会通过fork方式生成子进程。Services(服务)的形式如下:

service <name> <pathname> [ <argument> ]*
    <option>
    <option>
    ...

其中:

  • name:服务名
  • pathname:当前服务对应的程序位置
  • option:当前服务设置的选项
  • argument 可选参数
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
    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
    writepid /dev/cpuset/foreground/tasks

service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote_secondary stream 660 root system
    onrestart restart zygote
    writepid /dev/cpuset/foreground/tasks

上面这个文件是系统中init.zygote64_32.rc的文件内容,定义的就是系统zygote服务的相关内容。

三,init进程解析rc文件的基本架构

从前面的简要分析我们可以知道,rc文件中的关键字有三个,分别是 on ,inport ,以及service。

因此init进程解析rc文件的时候,也是从这三个关键字入手,用三个不同的工具类来分别解析三个不同的关键字所对应的内容,并将解析结果保存。

具体解析的方法如下:

首先定义了一个SectionParser类,类中提供相应的接口函数,然后分别定义 ImportParser (解析import关键字)ActionParser(解析on关键字),ServiceParser(解析service关键字)

其中各个类的函数列表如下:

class SectionParser {
  public:
    virtual ~SectionParser() {}
	// 当解析到一个新的关键字的时候,调用此方法来解析
    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(); };
	// 当前rc文件解析完毕的时候调用
    virtual void EndFile(){};
};
// 解析inport关键字
class ImportParser : public SectionParser {
  public:
    ImportParser(Parser* parser) : parser_(parser) {}
    Result<Success> ParseSection(std::vector<std::string>&& args, const std::string& filename,
                                 int line) override;
    void EndFile() override;

  private:
    Parser* parser_;
    // Store filename for later error reporting.
    std::string filename_;
    // Vector of imports and their line numbers for later error reporting.
    std::vector<std::pair<std::string, int>> imports_;
};
解析on关键字
class ActionParser : public SectionParser {
  public:
    ActionParser(ActionManager* action_manager, std::vector<Subcontext>* subcontexts)
        : action_manager_(action_manager), subcontexts_(subcontexts), action_(nullptr) {}
    Result<Success> ParseSection(std::vector<std::string>&& args, const std::string& filename,
                                 int line) override;
    Result<Success> ParseLineSection(std::vector<std::string>&& args, int line) override;
    Result<Success> EndSection() override;

  private:
    ActionManager* action_manager_;
    std::vector<Subcontext>* subcontexts_;
	// 指向当前正在解析的action对象
    std::unique_ptr<Action> action_;
};
解析service关键字
class ServiceParser : public SectionParser {
  public:
    ServiceParser(ServiceList* service_list, std::vector<Subcontext>* subcontexts)
        : service_list_(service_list), subcontexts_(subcontexts), service_(nullptr) {}
    Result<Success> ParseSection(std::vector<std::string>&& args, const std::string& filename,
                                 int line) override;
    Result<Success> ParseLineSection(std::vector<std::string>&& args, int line) override;
    Result<Success> EndSection() override;

  private:
    bool IsValidName(const std::string& name) const;
    ServiceList* service_list_;
	// 与厂商定制有关
    std::vector<Subcontext>* subcontexts_;
	// 当前正在解析的服务
    std::unique_ptr<Service> service_;
};

看到这三个类的定义我们基本上就清楚了init解析rc文件的基本结构流程了。它就是在解析过程中,根据不同的关键字调用不同的类来进行解析。

四,init进程解析rc文件之前的准备工作。


    // 这里定义一个BuiltinFunctionMap 这个里面有个非常重要的map对象,
    // 用来保存不同的操作所对应的具体函数列表
    const BuiltinFunctionMap function_map;
	// 将前面的function_map添加到Action中,供后续解析rc文件的时候使用
    Action::set_function_map(&function_map);

    // 与定制厂商有关 可以访问下面两个链接了解详细情况
    // https://blog.csdn.net/vicluo/article/details/103186032
    // https://source.android.google.cn/security/selinux/vendor-init
    subcontexts = InitializeSubcontexts();

    //用来保存以 on 开头的节点信息
    ActionManager& am = ActionManager::GetInstance();
    //用来保存以 service 开头的节点信息
    ServiceList& sm = ServiceList::GetInstance();

 在开始解析rc文件之前,init做了一个简单的准备工作。这里主要介绍下定义BuiltinFunctionMap变量并设置到Action类中的准备。因为on关键字对应的操作各种各样,因此就需要根据不同的命令来调用不同的函数进行处理。这个类就是通过map对象来映射不同命令所需要调用的具体函数;其内容如下:

// 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;
}
// Builtin-function-map end

 init除了设置这个map对象之外还初始化了 subcontexts 变量,这个主要使用odm厂商的定制有关,可以看注解部分的链接详细了解。然后init定义了两个变量am,sm,分别用来保存解析到的action 以及service

五,详细的解析流程分析。

  具体的解析流程是从 LoadBootScripts 函数开始的。

Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser;
    // 如果解析到以 service 开头的节点,就调用 ServiceParser 来处理,将service_list作为参数
    // 传递给ServiceParser类,待ServiceParser解析完毕之后,将解析结果保存到 ServiceList 类中
    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, subcontexts));
	// 如果解析到以 on 开头的节点,就调用 ActionParser 来处理,将action_manager作为参数传递给
	// ActionParser,待ActionParser解析完毕一个事件之后,将解析结果保存到ActionManager
    parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontexts));
	// 如果解析到以 import 开头的节点,就调用 ImportParser 来处理,将parser作为参数传递
	// 用来在某个rc文件解析完毕之后继续解析其引入的其他rc文件
    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));

    return parser;
}

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    // 初始化解析 如此文件所需要的 Parser 
    Parser parser = CreateParser(action_manager, service_list);

    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
		//如果没有强制解析指定的如此文件,就会走到这里 正常情况下都是会走到这里的
        parser.ParseConfig("/init.rc");

	    // 解析完 /init.rc 之后,还会依次循环解析下面这几个目录下面的 rc文件
	    // 如果某个目录或者文件不存在,或者解析失败,会添加到 late_import_paths 集合,待后解析完毕之后在解析一次
        if (!parser.ParseConfig("/system/etc/init")) {
			LOG(INFO) << "parser.ParseConfig : /system/etc/init failed";
            late_import_paths.emplace_back("/system/etc/init");
        }
        if (!parser.ParseConfig("/product/etc/init")) {
			LOG(INFO) << "parser.ParseConfig : /product/etc/init failed";
            late_import_paths.emplace_back("/product/etc/init");
        }
        if (!parser.ParseConfig("/odm/etc/init")) {
			LOG(INFO) << "parser.ParseConfig : /odm/etc/init failed";
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if (!parser.ParseConfig("/vendor/etc/init")) {
			LOG(INFO) << "parser.ParseConfig : /vendor/etc/init failed";
            late_import_paths.emplace_back("/vendor/etc/init");
        }
    } else {
        parser.ParseConfig(bootscript);
    }
}

上面的代码中LoadBootScripts首先调用CreateParser函数创建解析rc文件所需要的Parser对象。具体创建方法有详细的注解,这里就不多说了、然后在LoadBootScripts有获取ro.boot.init_rc属性,反正这个属性在我们平台是空,因此我们直奔重点。parser.ParseConfig("/init.rc");

bool Parser::ParseConfigFile(const std::string& path, size_t* parse_errors) {
    LOG(INFO) << "Parsing file ttid :"<<gettid()<<" file_path " << path;
    android::base::Timer t;
	// 检查 并读取文件的内容
    auto config_contents = ReadFile(path);
    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_) {
        // 在当前文件解析结束的时候,依次调用解析类的EndFile函数
        // 主要是如果刚刚解析结束的文件中,如果有import引入了新的文件,那么这里会开始解析引入的新文件
        section_parser->EndFile();
    }

    LOG(INFO) << "(Parsing file ttid :"<<gettid()<<" file_path " << path << " took " << t << ".)";
    return true;
}

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) {
        if (!ParseConfigFile(file, parse_errors)) {
            LOG(ERROR) << "could not import file '" << file << "'";
        }
    }
    return true;
}

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);
    }
    return ParseConfigFile(path, parse_errors);
}

上面的代码中我们可以看到,这里首先判断是否是目录,如果是目录就扫描目录下面的文件逐个解析,如果是文件就直接开始解析。最后都是调用的ParseConfigFile函数处理的。ParseConfigFile函数首先调用ReadFile将文件的内容读取到config_contents中并在变量结尾添加 \n 标志。然后调用ParseData解析文件的具体内容,下面我们就看下ParseData的具体实现。

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_copy 并将data的内容copy到新的容器,同时在容器最后添加 \0 标志,表示字符串结束
    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;  // next_token 函数返回的当前解析到的节点类型

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

    // 当解析到一个新的section 或者文件结束的时候调用
    // 如果section_parser不为空,就调用 section_parser的EndSection函数,说明当前的section解析完毕,可以提交保存数据了
    auto end_section = [&] {
        if (section_parser == nullptr) return;
		LOG(INFO) << "end_section section_parser != null";
        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 (;;) {
        switch (next_token(&state)) {
            case T_EOF:
				LOG(INFO) << "start call end_section";
                end_section();
			    LOG(INFO) << "end call end_section";
                return;
            case T_NEWLINE: // 每次解析到新的一行,就会走到这里
                state.line++; // 当前正在解析的行数 +1
                if (args.empty()) break; // 如果解析到的字符串容器为空,直接返回(rc文件第一行就是空着的就会出现这种情况)
                // 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.
                // line_callbacks_ 这个变量是在当前运行的是 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;
                    }
                }
                /*
                 如果当前解析到的是一个section的开始,那么第一个判断就会进去,
                 如果不是,那么如果 section_parser 不为空,就说明当前这一行是前面这个section_parser中的一行

				*/
                if (section_parsers_.count(args[0])) {  // 如果当前行是以 on ,service,import 那么这里会返回1
                    end_section(); // 如果是一个新的节点开始,这里通知上个解析的节点,表示上一个节点已经解析完毕
                    section_parser = section_parsers_[args[0]].get(); // 获取解析当前关键字所对应的解析工具类
                    section_start_line = state.line;
                    if (auto result = section_parser->ParseSection(std::move(args), filename, state.line);
                        !result) {
                        (*parse_errors)++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        section_parser = nullptr;
                    }
                } else if (section_parser) {
                    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 中
                args.emplace_back(state.text);
                break;
        }
    }
}

代码中都有比较详细的注解,其首先创建parse_state变量,然后就开始执行循环解析了,循环解析的步骤还是比较清晰的,

循环里面的一个关键的函数就是next_token函数。下面我们看下next_token函数的具体实现;

int next_token(struct parse_state *state)
{
    char *x = state->ptr;
    char *s;

    if (state->nexttoken) {
        int t = state->nexttoken;
        state->nexttoken = 0;
        return t;
    }
    /*
      前面的 “char *x = state->ptr;” 使得*x 指向了待解析的字符串的第一个字符的位置。
      这里通过for循环进行解析,直到 *x 指向有效字符串的第一个字符的位置结束,这个时候会跳到 text 的地图执行

    */
    for (;;) {
        switch (*x) {
        case 0:    // 如果到了文件结尾,直接返回文件结束的标志
            state->ptr = x;
            return T_EOF;
        case '\n': // 如果预见换行符,这个时候 x++ 指向下一行的第一个字符,并将x赋值给全局变量 state->ptr,然后返回下一行
            x++;
            state->ptr = x;
            return T_NEWLINE;
        case ' ':
        case '\t':
        case '\r':  // 遇见这几个字符就直接跳过,继续循环解析
            x++;
            continue;
        case '#':  // # 表示注释
            while (*x && (*x != '\n')) x++; // 直接使用while循环,找到行尾
            if (*x == '\n') {               // 如果最后一个是换行符,就说明还有下一行,返回T_NEWLINE,开始解析下一行
                state->ptr = x+1;
                return T_NEWLINE;
            } else {            // 如果行尾直接是空,就说明到了文件末尾了,这个时候返回文件结束的标志
                state->ptr = x;
                return T_EOF;
            }
        default:   // 如果当前x指向的字符是一个有效字符,这是时候,跳转到 text 继续执行
            goto text;
        }
    }

textdone: 
    // 解析到字符串的结尾的时候会走到这里,这个时候 s指向的是字符串最后一个字符的下一个字符,x是s指向的字符的下一个字符
    // 比如 当前解析的字符串是 123456 789 那么s指向的就是字符串中间的空格,x指向的是字符串7的位置
    state->ptr = x;
    *s = 0; // 将s指向的字符设置为0,表示字符串结束,这个时候从 state->text 读取字符串的时候,就可以刚好读出当前解析到的字符串了
    return T_TEXT;
text: // 遇到字符串的开始会走到这里
	// state->text 和 s 已经 x 都指向有效字符串的第一个字符位置
	// state->text 保存的是当前字符串起始位置的字符的位置,待当前字符串解析完毕之后,作为当前字符串返回
	// s 和 x 用来在下面的循环中查找当前字符串的末尾位置
    state->text = s = x;
textresume:
    for (;;) {
        switch (*x) {
        case 0:  // 如果预见\0 表示文件结束,因此字符串也就结束了
            goto textdone;
        case ' ':
        case '\t':
        case '\r':
            x++; // x++;会使得 x指向的位置是s指向的位置的下一个,
            goto textdone; // 如果出现前3个字符,都表示当前字符串结束了,这个时候 x执行当前字符串最后一个字符的后面2个字符的位置
        case '\n': 
			// 表示预见了下一行。这个时候需要同时处理两个状态,1:当前字符串结束,2:新的一行,
            state->nexttoken = T_NEWLINE;
            x++; // x++; 使得x指向下一行的第一个字符的位置
            goto textdone;
        case '"': // 如果字符中有" 就需要删掉 比如 123"456"789 解析的结果就是 123456789
            x++;
            for (;;) {
                switch (*x) {
                case 0:
                        /* unterminated quoted thing */
                    state->ptr = x;
                    return T_EOF;
                case '"':
                    x++;
                    goto textresume;
                default:
                    *s++ = *x++;
                }
            }
            break;
        case '\\':
            x++;
            switch (*x) {
            case 0:
                goto textdone;
            case 'n':
                *s++ = '\n';
                break;
            case 'r':
                *s++ = '\r';
                break;
            case 't':
                *s++ = '\t';
                break;
            case '\\':
                *s++ = '\\';
                break;
            case '\r':
                    /* \ <cr> <lf> -> line continuation */
                if (x[1] != '\n') {
                    x++;
                    continue;
                }
            case '\n':
                    /* \ <lf> -> line continuation */
                state->line++;
                x++;
                    /* eat any extra whitespace */
                while((*x == ' ') || (*x == '\t')) x++;
                continue;
            default:
                    /* unknown escape -- just copy */
                *s++ = *x++;
            }
            continue;
        default:
            *s++ = *x++;
        }
    }
    return T_EOF;
}

}  // namespace init
}  // namespace android

这个函数的每一步代码中都有详细的注解。其中next_token这个函数的唯一功能就是:从参数state->ptr所指向的字符串处开心循环查找,如果发现查找到了字符串结尾,就返回T_EOF,并将state->ptr指向字符串的最后一个字符的位置,如果发现新的一行就返回T_NEWLINE,并将state->ptr指向新的一行的第一个字符的位置,如果查找的是一个字符串,那么会跳过字符串前面的无效字符将state->text指向有效字符的起始位置的第一个字符。然后循环查找直到字符串结束,在textdone:的时候将有效字符结尾处的字符的后一个字符置为 \0 并将state->ptr指向重置字符的下一个字符。然后返回T_TEXT。

六,解析流程举例说明

    下面通过三个具体的例子来详细说明具体的解析过程。

     举例1: import 关键字的解析流程

     假设我们要解析下面这段内容的rc文件。

# Copyright Android Open Source

import /init.environ.rc
import /init.${ro.hardware}.rc

 那么在ParseData函数中state变量的ptr锁指向的字符串的具体内容如下:其中最后面的 \n\0是代码中添加上去的

# Copyright Android Open Source\n\nimport /init.environ.rc\nimport /init.${ro.hardware}.rc\n\0

  那么具体解析流程是这样的,首先在next_token的第一个for循环中,因为第一个字符是#号,因此会case '#': 这个地方进入while循环,直到判断if (*x == '\n')进入,这个时候state->ptr = x+1使得state->ptr指向了第二行的第一个字符(就是前面字符串中的第二个\n的位置)。然后返回了T_NEWLINE,告诉ParseData发现了新的一行,然而在ParseData函数的case T_NEWLINE: 中因为

if (args.empty()) break;而结束本次循环,进入下一次循环,继续调用next_token函数查找,然后next_token因为state->ptr指向了第二行的第一个字符就是第二个\n,因此在第一轮for循环中就会返回一个T_NEWLINE消息,并且将state->ptr指向了第三行的第一个字符(就是import /init.environ.rc的i的位置),但是在ParseData中依然因为args是空的而进入下一轮循环。

在下一轮循环中因为state->ptr指向的是有效字符i的位置,因此next_token的第一个for循环,会走到default分支,并通过goto语句走到text:位置。在这里会执行state->text = s = x;将state->text指向import /init.environ.rc的i的位置,同时进入第二个for循环,直到找到import字符串之后的第一个空格,这个是x++会使得x指向下一个字符(就是import /init.environ.rc的/的位置),然后goto到textdone: 这个时候,因为变量s没有++,因此变量s依然指向的是import /init.environ.rc的空格的位置,然后在这里会将这个空格替换成\0。然后返回T_TEXT。在ParseData函数中执行args.emplace_back(state.text);将本次循环解析到的import关键字保存到args中。然后开始下一轮循环。下一轮的循环会一直走到import /init.environ.rc结尾的\n,这个时候会执行 state->nexttoken = T_NEWLINE;,并将x++使得x指向import /init.${ro.hardware}.rc语句的i的位置,同时goto到textdone: 在这里会将state->ptr指向x所指向的位置,同时将s所指向的位置置为\0(就是刚刚的\n的位置)。然后会返回T_TEXT。在ParseData函数的case T_TEXT:中会将刚刚截取到的字符串“/init.environ.rc”保存到args中,然后开始下一轮循环。

在下一轮的循环中由于state->nexttoken不为空,因此会直接返回state->nexttoken的值,并将state->nexttoken置为空。由于前面知道state->nexttoken的值是T_NEWLINE因此会继续执行ParseData的case T_NEWLINE:分支。

在这个分支下,因为section_parsers_.count(args[0])的结果是1,因此判断会进入,然后会执行section_parser = section_parsers_[args[0]].get(),因为args[0]保存的是 import,因此这里获取到的section_parser就是ImportParser类的对象。

然后就会调用这个类的ParseSection函数进行具体的解析。

下面我们来看看ImportParser类的ParseSection函数的具体实现:

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

    std::string conf_file;
	// 检查要加入的文件路径是否合法,如果文件路径中包含(/init.${ro.hardware}.rc)的时候,会将属性获取并拼接,来返回完整的地址
    bool ret = expand_props(args[1], &conf_file);
    if (!ret) {
        return Error() << "error while expanding import";
    }

    LOG(INFO) << "ImportParser Added '" << conf_file << "' to import list";
	// 记录当前正在解析的rc文件的名称
    if (filename_.empty()) filename_ = filename;
	// 将通过import引入的待解析的rc文件添加到imports_中
    imports_.emplace_back(std::move(conf_file), line);
    return Success();
}

这个函数的内容比较清晰,注解里面都有,这里主要分析下expand_props这个函数的实现,这个函数会对待引入的rc文件中的路径进行检查,如果引入的路径中包含属性,这个函数会将属性替换成真正的属性值并返回替换之后的rc文件的路径。这个函数的具体实现在后续会继续分析。

在ParseSection函数返回之后,ParseData会继续解析,待解析到最后一个\n的时候,会返回再次返回T_NEWLINE,函数流程会走到ParseData函数的case T_NEWLINE:中,然后继续调用ImportParser类的ParseSection函数。这里需要注意的是,这一次返回回来的rc文件的路径是包含系统属性的,因此在ImportParser类的ParseSection函数中,需要调用expand_props函数,将系统属性替换成系统属性的值。这里我们传入的文件路径字符串为:  /init.${ro.hardware}.rc

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

    if (!dst) {
        return false;
    }
    LOG(INFO) << "expand_props src_ptr = "<< src_ptr;
	// /init.${ro.hardware}.rc
    while (*src_ptr) {
        const char* c;
        // 查找src_ptr中是否包含$符,如果没有,就说明路径中没有系统属性,无需转换,直接返回
        // 如果有,这个时候,c指向$的位置。
        c = strchr(src_ptr, '$');
        if (!c) {
            dst->append(src_ptr);
            return true;
        }
		LOG(INFO) << "expand_props c = "<<c;
		// expand_props c = ${ro.hardware}.rc

		// 将src_ptr字符串中c前面的部分拼接到dst中
        dst->append(src_ptr, c);
        c++; // 这里给c进行++ 使得c指向下一个位置,正常情况下这里就指向了{的位置

		LOG(INFO) << "expand_props dst = "<<*dst;
        //  expand_props dst = /init.

        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 == '{') {
            // 查找}的位置
			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;
            }
			// 获取字符串C到end的部分,就刚好是属性的名称了
            prop_name = std::string(c, end);
            c = end + 1; // C 指向}的后面一个字符
            // 这里会查询有没有默认的系统属性.通过find函数查找,如果查找不到,这里会返回2的64次方减1的这个数字
            size_t def = prop_name.find(":-");
			LOG(INFO) << "expand_props def = "<< def;
			//  expand_props def = 18446744073709551615
            if (def < prop_name.size()) {
                def_val = prop_name.substr(def + 2);
                prop_name = prop_name.substr(0, def);
            }
			LOG(INFO) << "expand_props prop_name = "<< prop_name<< " def_val = " <<def_val ;
			//  expand_props prop_name = ro.hardware def_val = 
        } else {
            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, "");
		LOG(INFO) << "expand_props prop_val = "<< prop_val;
		//  expand_props prop_val = mt6739
        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;
}

这个函数比较简单,唯一有点难度的就是这个while的循环,比如转换/init.${ro.hardware}.rc会进行两次循环,

第一次循环的结束之后dst的内容为 /init.mt6739  这个时候c指向的是.rc位置的.

然后在下次循环的时候,会在c = strchr(src_ptr, '$');的时候,因为c为空而退出循环,将替换之后的完整路径/init.mt6739.rc保存到dst中,函数返回。

至此两个import语句的解析就完成了,函数执行流程会自动返回ParseData函数执行,由于这个时候待解析的字符串到达末尾了,因此在next_token函数中的第一个for循环的时候,就会返回T_EOF,然后在ParseData函数中会执行end_section,清空部分数据,并调用当前解析类的EndSection函数,由于ImportParser类的这个函数没有实现,因此这里没有任何作用,至此当前文件解析完毕,ParseData函数退出,会退到ParseConfigFile函数中,然后ParseConfigFile函数会依次遍历调用每个解析类的EndFile函数,说明当前文件解析完毕。这个时候就会调用到ImportParser类的EndFile函数,这里我们看下这个类的EndFile函数的具体实现:

// 当前文件解析完毕的时候,遍历当前文件中解析出来的需要 import的rc文件,并依次调用 parser_->ParseConfig 继续解析
void ImportParser::EndFile() {
    LOG(INFO) << "ImportParser EndFile '" << filename_;
	// 如果A文件中 import 了B,然后B文件中import了C,
	// 然后在A文件解析完毕的时候,这里会开始解析文件B,当解析到文件B的import C 语句的时候,就会调用
	// 上面的 ImportParser::ParseSection 导致 imports_ 变量被重置。因此这里需要对imports_进行拷贝保存,并清空原变量
    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
                        << "'";
        }
    }
}

这个函数最终会调用parser_->ParseConfig函数进行刚刚的引入的两个函数的解析操作。其中parser_参数就是在init.cpp中初始化的时候,传递进去的。

至此import的解析流程就梳理完了。

举例2: on关键字的解析流程

假设我们要解析下面这段内容的rc文件。

on early-init
   write /proc/1/oom_score_adj -1000
   chown system system /dev/stune/rt/tasks
   chmod 0664 /dev/stune/tasks
on init
   mkdir /dev/stune/top-app
   mkdir /dev/stune/rt

根据前面的解析流程,这里我们知道,当ParseData函数while循环的case T_NEWLINE:第一次执行的时候,args[0] = on

args[1] = early-init;

这个时候因为section_parsers_.count(args[0])不为空,因此if判断会进去,然后执行section_parser = section_parsers_[args[0]].get();,这里获取到的section_parser指向的就是ActionParser的实例对象。然后会调用到ActionParser的ParseSection函数进行解析。

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());
    if (triggers.size() < 1) { // 如果参数数量不合适,直接返回错误
        return Error() << "Actions must have a trigger";
    }

    Subcontext* action_subcontext = nullptr;
    if (subcontexts_) { // 还是与 厂商定制有关
        for (auto& subcontext : *subcontexts_) {
            if (StartsWith(filename, subcontext.path_prefix())) {
                action_subcontext = &subcontext;
                break;
            }
        }
    }

    std::string event_trigger;
    std::map<std::string, std::string> property_triggers;
    // 查看action 有没有&&的系统属性相关的参数,如果有就需要将其解析并保存到property_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_指向新构造的Action类
    action_ = std::move(action);
    return Success();
}

这个函数流程比较清晰,主要就是根据传递的参数构造Action类,并将action_指向构造的Action类。

当下一个循环解析完write /proc/1/oom_score_adj -1000的是,会继续执行到ParseData函数while循环的case T_NEWLINE:中,这一次,因为section_parsers_.count(args[0])的结果为false,但是因为上一次进入的时候section_parser已经不为空了,因此这里会进入到if(section_parser)中。

if (section_parsers_.count(args[0])) {  // 如果当前行是以 on ,service,import 那么这里会返回1
                    end_section(); // 如果是一个新的节点开始,这里通知上个解析的节点,表示上一个节点已经解析完毕
                    section_parser = section_parsers_[args[0]].get(); // 获取解析当前关键字所对应的解析工具类
                    section_start_line = state.line;
                    if (auto result = section_parser->ParseSection(std::move(args), filename, state.line);
                        !result) {
                        (*parse_errors)++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                        section_parser = nullptr;
                    }
                } else if (section_parser) {
                    if (auto result = section_parser->ParseLineSection(std::move(args), state.line);
                        !result) {
                        (*parse_errors)++;
                        LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
                    }
                }
                args.clear();

在} else if (section_parser) { 中会掉用到ActionParser类的ParseLineSection函数进行处理,下面我们就看下这个函数的具体实现

// 将当前action解析到的后续 命令添加到他的命令列表中
Result<Success> ActionParser::ParseLineSection(std::vector<std::string>&& args, int line) {
    return action_ ? action_->AddCommand(std::move(args), line) : Success();
}
// 首先根据解析到的参数,查询此参数操作所需要的具体函数,然后构造执行此条指令所需要的
// command 对象,并添加到 commands_ 容器中
Result<Success> Action::AddCommand(const std::vector<std::string>& args, int line) {
    if (!function_map_) { // 这个function_map_变量是init.cpp在开始准备阶段就设置的变量
        return Error() << "no function map available";
    }

    // 查询当前指令所对应的执行函数是否满足要求
    auto function = function_map_->FindFunction(args);
    if (!function) return Error() << function.error();

    // emplace_back 会自动调用command的构造函数,构建command对象并添加到commands_
    commands_.emplace_back(function->second, function->first, args, line);
    return Success();
}
  // 这个函数非常重要
    // 这个函数会根据解析到的参数的具体内容,从Map对象中查询其对应的处理函数,
    // 并且对解析到的参数个数进行匹配,检测参数个数是否满足要求
    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);
        if (function_info_it == map().end()) {
            return Error() << StringPrintf("Invalid keyword '%s'", keyword.c_str());
        }

        auto function_info = function_info_it->second;

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

这个函数的流程非常清晰,就是根据当前解析出来的具体参数内容,从最开始的map中查询其对应的处理函数,然后执行commands_.emplace_back 函数根据执行本条明细所需要的具体函数,以及参数构建Command结构并将其添加到当前action的commands_列表中。

后续的两行    chown system system /dev/stune/rt/tasks 跟  chmod 0664 /dev/stune/tasks的执行跟write /proc/1/oom_score_adj -1000的流程一致,这里就不做介绍了。

当解析流程解析到on init这一行的时候,会继续进入到ParseData函数while循环的case T_NEWLINE:中,这个时候,因为section_parsers_.count(args[0]为1因此判断会进去,这个时候会执行end_section();表示新的节点开始,这里我们看下end_section();的具体内容:

    // 当解析到一个新的section 或者文件结束的时候调用
    // 如果section_parser不为空,就调用 section_parser的EndSection函数,说明当前的section解析完毕,可以提交保存数据了
    auto end_section = [&] {
        if (section_parser == nullptr) return;
		LOG(INFO) << "end_section section_parser != null";
        if (auto result = section_parser->EndSection(); !result) {
            (*parse_errors)++;
            LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
        }

        section_parser = nullptr;
        section_start_line = -1;
    };

这里会调用到ActionParser类的EndSection函数,然后EndSection函数会将当前正在解析的action添加到action_manager_中。

// 在当前 action解析完成之后,判断当前 action是否符合要求,如果符合要求,
// 将当前解析到的 actiong 添加到 action_manager_ 的action 容器中
Result<Success> ActionParser::EndSection() {
    if (action_ && action_->NumCommands() > 0) {
        action_manager_->AddAction(std::move(action_));
    }

    return Success();
}

这个action_manager_是在init.cpp中创建的ActionParser类的时候传递进来的用来保存解析到的action列表的。

至此on关键字的解析流程就梳理完成了。

举例3: service关键字的解析流程

service的解析流程跟on的解析流程基本上是一样的,因此这里就不做介绍了!

后续我会继续梳理init启动zygote的详细流程,敬请期待!

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值