在前面的篇章Android P之init进程启动源码分析指南之一和Android P之init进程启动源码分析指南之二讲解了init进程经过前面两个阶段以后,已经建立了相关的文件系统,属性系统,SELinux安全策略系统。但是我们知道init进程做的远远不止这些,还要启动一些Android的native service系统服务及其其他相关的操作,但是如果都是像属性系统和SELinux系统那样一行行代码去做,显得有点杂乱繁琐,而且不容易扩展,所以Android系统引入了init.rc。这个就是我们本篇要讲解的重点,init进程解析init.rc相关文件。
本篇章主要讲解的内容大概如下:
注意:本文演示的代码是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是一个可配置的初始文件,是由Android初始化语言编写(Android Init Language)编写的脚本,这里顺便扩展一些Android里面还有那些类似的Android独有的语言呢(譬如AIDL, HIDL等)?
init.rc文件主要包含如下五类声明:
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文件中涉及到的五种类型的声明,一一讲解,但不包会。
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触发的条件可以分为如下几种情况:
on early-init #表示当trigger early-init或QueueEventTrigger("early-init")调用时触发
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}
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
Service也是以Section的形式出现的,其中每个Service Section可以包含有若干的Option。Section 只有起始标记,却没有明确的结束标记,也就是说,是用“后一个 Section”的起始来结束“前一个 Section”。这里有一点需要需要注意地是Service 不能出现重名。
口干舌燥的说了这么大一堆,是不是还是没有搞明白,还是上个实例来说明一下,其实rc里面的Service就是以“service”关键字开头的 服务列表(service list):
## Daemon processes to be run by init.
##
service ueventd /sbin/ueventd #Service类型语句
class core
critical
seclabel u:r:ueventd:s0
shutdown critical
Service表示一个服务程序,会通过 start command 执行。并根据 option 参数判断服务在退出时是否需要自动重启。Service的语句结构如下:
service <name> <pathname> [ <argument> ]* #<执行程序路径><传递参数>
<option> #option是service的修饰此,影响什么时候,如何启动service
<option>
...
从上面对Action和Service的解读中我们可以看出,这两个声明主要借助于系统环境变量或者Linux命令来在Android启动的不同阶段做一些工作,主要如下:
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,里面有非常详细的介绍。
Command通常和Action关联在一起,一般表示一些具体的操作,通常是借助Linux命令完成相关的操作,譬如:
mkdir /dev/fscklogs 0770 root system //新建目录
class_stop charger //终止服务
trigger late-init //触发late-init
上面的Command只是我随意列举的,还有很多,这里就不一一介绍了,感兴趣的可以阅读system/core/init/README.md,里面有非常详细的介绍。
在前面我们讲过在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功能分别如下:
[email protected] 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
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
前面的篇章我们恶补了相关的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);
}
}
可以看出在正式解析前,创建了一个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
下面就要开始分析解析过程了,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进行解析。
在正式开始分析代码前,先来一个流程图,防止大伙在代码分析中迷失了自己,可以回过头找到自己在哪里。
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函数遍历每一个字符,然后对不同的字符进行判断,采取不同的规则进行处理,其主要流程如下:
在上述流程中牵涉到一个非常重要的结构体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 循环中分别进行相应的配置操作。
该函数定义在system/core/init/service.h实现在system/core/init/service.cpp中,ServiceParser实现了对Service section的解析,下面让我们来分析其几个主要函数ParseSection,ParseLineSection和EndSection的实现。
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的函数处理逻辑如下:
前面通过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去执行那个函数。它的具体执行步骤如下:
这里解析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对象中。
通过前面的操作我们经过一系列猛虎般的操作,已经解析完了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的处理逻辑简单,主要做了如下几个操作:
ServiceList& sm = ServiceList::GetInstance(); //这个是一个单例模式,这个非常重要
LoadBootScripts(am, sm);
如果说前面的函数轰轰烈烈,那么EndFile就平淡无奇了,EndFile是一个空函数没有做任何事情。就不多说它了。
在 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
在正式开始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 的解析过程实际上就是解析填充这些信息。
让我们按照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处理的逻辑比较简单,主要干了如下几件事情:
我们接着继续分析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的处理逻辑也不是很复杂,主要遵循如下几个处理逻辑:
革命尚未成功,让我们再接再厉继续分析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就分析完了,下面还是对其流程归纳总结一下:
前面章节通过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;
}
经过万水千山,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中三个重要的方法都做了那些工作,这个也是对我们阶段性成果的鼓励不是:
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
ParseSection函数处理逻辑比较简单,其主要流程如下:
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.y或x.y这种语法,将x.y取出来作为name,去属性系统中找对应的value,然后替换。
bool expand_props(const std::string& src, std::string* dst) {
const char* src_ptr = src.c_str();
if (!dst) {
return false;
}
/* - variables can either be $x.y or ${x.y}, in case they are only part
* of the string.
* - will accept $$ as a literal $.
* - no nested property expansion, i.e. ${foo.${bar}} is not supported,
* bad things will happen
* - ${x.y:-default} will return default value if property empty.
*/
/*
*先生们,女士们下面让我用我蹩脚的中文,不蹩脚的英文来翻译一下这段英文,翻译如下:
*合法的参数要么是$x.y或者${x.y}
*如果参数是$$那么会解析成$
*参数不支持${foo.${bar}}的形式,否则会发生很坏的事情,至于坏事情是什么我也不知道
*${x.y:-default}的解析规则是将default作为默认值返回,前提是找不到对应的属性值的话
*/
while (*src_ptr) {
const char* c;
c = strchr(src_ptr, '$');
if (!c) {//找不到$符号,即是直接引用rc文件,直接讲str_ptr返回,譬如如下improtimport /init.environ.rc
dst->append(src_ptr);
return true;
}
dst->append(src_ptr, c);
c++;
if (*c == '$') {//跳过$
dst->push_back(*(c++));
src_ptr = c;
continue;
} else if (*c == '\0') {
return true;
}
std::string prop_name;
std::string def_val;
if (*c == '{') {//找到 { 就准备找 }的下标,然后截取它们之间的字符串,对应${x.y}的情况,譬如import /init.${ro.zygote}.rc
c++;
const char* end = strchr(c, '}');
if (!end) {
// failed to find closing brace, abort.
LOG(ERROR) << "unexpected end of string in '" << src << "', looking for }";
return false;
}
prop_name = std::string(c, end);//截取{}之间的字符串作为name,即ro.zygote
c = end + 1;
size_t def = prop_name.find(":-");//如果发现有 ":-" ,就将后面的值作为默认值先存起来
if (def < prop_name.size()) {
def_val = prop_name.substr(def + 2);
prop_name = prop_name.substr(0, def);
}
} else {//对应$x.y的情况
prop_name = c;
LOG(ERROR) << "using deprecated syntax for specifying property '" << c << "', use ${name} instead";
c += prop_name.size();
}
if (prop_name.empty()) {
LOG(ERROR) << "invalid zero-length property name in '" << src << "'";
return false;
}
std::string prop_val = android::base::GetProperty(prop_name, "");//通过name在属性系统中找对应的value,内部调用的是之前属性系统的__system_property_find函数,这里ro.zygote的取值是zygote64_32
//没有找到值,如果有默认值就返回默认值
if (prop_val.empty()) {
if (def_val.empty()) {
LOG(ERROR) << "property '" << prop_name << "' doesn't exist while expanding '" << src << "'";
return false;
}
prop_val = def_val;
}
dst->append(prop_val);
src_ptr = c;
}
return true;
}
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.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 #仅此一行,童叟无欺
#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
虽然从代码层方面对init.rc的五类声明已经解析完成了,但是总感觉少了点什么,还是上一个代码类图来总结一下init进程中是对这个五类声明关联起来的吗。
在前面的篇章中我们已经解析完了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");
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_;
......
};
QueueBuiltinAction的代码定义在system/core/init/action_manager.cpp中,这个函数有两个参数,第一个函数是一个函数指针,第二个参数是字符串。该函数的处理逻辑如下:
void ActionManager::QueueBuiltinAction(BuiltinFunction func, const std::string& name) {
//构建Action
auto action = std::make_unique<Action>(true, nullptr, "" , 0, name,
std::map<std::string, std::string>{});
std::vector<std::string> name_vector{name};
action->AddCommand(func, name_vector, 0);//往Action中添加Command
event_queue_.emplace(action.get());//触发队列中加入触发条件
actions_.emplace_back(std::move(action));//将Action加入到actions_列表
}
通过QueueBuiltinAction 和QueueEventTrigger 上述两步,将需要处理的 trigger 添加到 trigger_queue_中,而 trigger_queue_本身就是一个队列,所以先加进去的,先执行,后加入的,后执行。也就是通过这种方式,init.rc 中所列 action的执行顺序得到的确认。
我们知道trigger_queue_的触发顺序是以队列形式进行的,那么其具体流程是什么呢,这里给出一个流程图以供大家参考(这里的前提是Android源码选择了非加密模式)。
前面我们知道了Trigger的触发顺序,现在让我们看看每个Trigger主要做了那些事情。
Trigger启动阶段 | 触发内容 |
---|---|
early-init | 初始化第一阶段,设置 init 进程 score adj 值,重置安全上下文,启动 uevent 服务 |
wait_for_coldboot_done | wait uevent_main – device_init 完成 coldboot_done 目录创建,Timeout 1s |
mix_hwrng_into_linux_rng | 读取 512 bytes 硬件随机数,写入 linux RNG,不支持HWrandom 直接返回,不影响 init 启动 |
keychord_init | keychord 是组合按键,keychord 为每个服务配置组合键,在服务解析时为指定服务设置相应的键码值 |
console_init | 如果ro.boot.console 指定了控制台终端,那么优先使用这个控制台,如果没有指定,那么将使用默认控制台终端/dev/console |
init | 创建文件系统, mount节点以及写内核变量 |
late-init | 触发各种trigger,trigger early-fs fs post-fs load_system_props_action post-fs load_persist_props_action firmware_mounts_complete early-boot boot |
early-fs | 设置外部存储环境变量 |
fs | 专门用于加载各个分区,如mtd分区,创建adb设备目录,修改 adf 设备文件权限 |
post-fs | 修改productinfo用户群组,改变系统目录访问权限(kmsg、vmallcoinfo、cache等) |
load_persist_props_action | 加载property 文件如 “/system/build.prop”"/vendor/build.prop" “/factory/factory.prop” |
post-fs-data | 创建、改变/data 目录以及它的子目录的访问权限,启动 vold、debuggerd 服务,bootchart_init |
load_persisit_props_action | 启动logd服务,load property file /data/property,"/data/local.prop" |
firmware_mounts_complete | :删除 dev/.booting 目录 |
early-boot | 修改 proc、sys/class 子目录访问权限 |
boot | 正常的启动命令,设置 usb 厂商参数、CPU 参数,修改 sensorhub、 bluetooth、gnss、thermal 目录访问权限,网络参数设置。 启动 Core class servic |
charge | 当手机处于充电模式时(关机情况下充电), 需要执行的命令 |
nonencrypted | 启动 main、late_start class service(这里的前提条件是Android 源码编译选择了非加密模式) |
如果说之前的所有工作都是往各种链表、队列里面存入信息,并没有真正去触发,而是做了重复的准备工作,前戏已经够了,那么接下来的工作就是真正去触发这些事件,以及用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)();
}
}
该代码定义在system/core/init/action_manager.cpp中,从函数名字可以看出它是执行一个Command,该函数的处理逻辑如下:
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)。其执行循序遵从如下逻辑:
通过前面我们知道Trigger触发有两种情况,第一种是直接QueueEventTrigger然后另外一种就是满足property,我们通过前面篇章Android P之init进程启动源码分析指南之二知道但是属性系统的写操作只能在 init 进程中进行,其它进程进行属性的写操作也需要通过 init 进程。最终会调用到HandlePropertySet进行处理。这个函数处理分两种情况:
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);
}
void property_changed(const std::string& name, const std::string& value) {
// If the property is sys.powerctl, we bypass the event queue and immediately handle it.
// This is to ensure that init will always and immediately shutdown/reboot, regardless of
// if there are other pending events to process or if init is waiting on an exec service or
// waiting on a property.
// In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
// commands to be executed.
if (name == "sys.powerctl") {//处理sys.powerctl命令
// Despite the above comment, we can't call HandlePowerctlMessage() in this function,
// because it modifies the contents of the action queue, which can cause the action queue
// to get into a bad state if this function is called from a command being executed by the
// action queue. Instead we set this flag and ensure that shutdown happens before the next
// command is run in the main init loop.
// TODO: once property service is removed from init, this will never happen from a builtin,
// but rather from a callback from the property service socket, in which case this hack can
// go away.
shutdown_command = value;
do_shutdown = true;
}
//通知ActionManager属性变化
if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
// 当前正在设置属性,进行一些同步化的操作
if (waiting_for_prop) {
if (wait_prop_name == name && wait_prop_value == value) {
LOG(INFO) << "Wait for property took " << *waiting_for_prop;
ResetWaitForProp();
}
}
}
从代码中可以看到,property_triggers_enabled 是 <属性变化触发 action> 的使能点,开启之后每次属性发生变化都会调用 ActionManager.QueuePropertyChange(name, value) 函数
void ActionManager::QueuePropertyChange(const std::string& name, const std::string& value) {
event_queue_.emplace(std::make_pair(name, value));//添加触发条件
}
这个函数比较简单就是event_queue_列表添加触发条件,将已改变属性的键值对作为参数。运行队列中已经添加了该属性的变化触发条件,同样通过 am.ExecuteOneCommand() 函数遍历所有的 _actions 链表,执行相应的 commands。
随着Android版本越高,init的工作量也是越来越大了,分析起来不得不使出吃奶的力气了,在init进程的最后阶段主要工作是主要工作是讲解init.rc的基本语法,然后解析.rc文件,然后继续解析init进程启动其它相关的逻辑,主要是一些Action事件的加入和触发,以及一些其它的事件触发的监听。
Android P之init进程启动源码分析指南之三的告一段落了,不容易啊分析起来,在接下来的篇章我们将继续讲解Android P启动中非常重要的一个进程zygote启动流程。如果对给位有帮助欢迎点赞一个,如果写得有问题也欢迎多多指正。未完待续,关于zygote启动篇章详见Android P Zygote进程启动源码分析指南一。