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.youkuaiyun.com/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的详细流程,敬请期待!