Android启动流程之二 init.rc解析
一、init.rc
Android中利用rc文件配置一些需要提前执行的操作,在系统启动的时候解析并执行,为启动Android系统核心服务提供保障。可参考:http://androidxref.com/9.0.0_r3/xref/system/core/init/README.md
rc文件以行为单位,一行定义一个语句,使用#作为注释。rc语法核心包括
- Action
- Service
- Command
- Options
其中需要注意的是Action和Service的名称是唯一的。
rc文件是可以通过import语句来导入其他rc文件的,例如/init.rc就引入包含了其他文件
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
1.1、 Action
以on开头,通过触发器trigger来决定对应的service什么时候执行,执行的时机有
- on early-init -> 初始化的早期触发
- on init -> 初始化阶段触发
- on late-init -> 初始化后期触发
- on boot/charger -> 系统启动或者充电时触发
- on property:
= -> 满足条件时触发
定义语法
on #触发条件
#触发命令
#第二个触发命令,可以执行多个命令
1.2、Service
Service,顾名思义就是需要启动的服务,以service开头,init启动后会由init进程启动它作为子进程。
定义:
service []* #可以有多个参数
例子
service servicemanager /system/bin/servicemanager
定义的就是名称为servicemanager的服务,对应的执行文件为system/bin/servicemanager,因此在启动服务前需要判断服务对应的文件是否存在
1.3、Command
Command主要是一些常用的操作命令,例如:
- class_start
-> 启动属于同一个class的所有服务 - start
->启动指定的服务 - stop
->停止正在运行的服务 - setprop
-> 设置系统属性值 - symlink
->创建连接到target的sym_link符号连接 - write
-> 向path路径下写入字符串 - exec -> fork 并执行,init进程会被阻塞,知道执行完毕
- export -> 设置环境变量
1.4、Options
Option是Service配合使用的可选操作项
- disable -> 不跟随class自动启动,只有跟随service name才启动
- oneshot -> service执行完毕退出后不会再重启
- user/group -> 设置service的用户和群组,默认是root
- class -> 设置所属的类名,service绑定的class启动或者退出时,service也会启动或者退出,默认是default
- onrestart ->服务重启时执行
- socket -> 创建名为/dev/socket/name的socket
- critical -> 在规定的时间内service如果不断重启,系统会重启进入recovery模式
1.5 demo
#初始化早期执行
on early-init
# Set init and its forked children's oom_adj.
# 往/proc/1/oom_score_adj中写入 -1000
write /proc/1/oom_score_adj -1000
#启动ueventd服务
start ueventd
#初始化时执行
on init
mkdir /dev/stune
write /proc/sys/kernel/sched_tunable_scaling 0
#当sys.boot_from_charger_mode为1时执行
on property:sys.boot_from_charger_mode=1
class_stop charger
trigger late-init
#初始化后期执行
on late-init
trigger early-fs
# 定义一个名为flash_recovery,执行文件在/system/bin/install-recovery.sh的服务
# 该服务是oneshot的,执行完成后不再重启,且启动或者退出动作和main服务一起
service flash_recovery /system/bin/install-recovery.sh
class main
oneshot
二、init.rc解析
在《Android启动流程之一 init进程启动》中有提到,init进程的main函数中有加载init.rc文件,对应的执行函数为:
2.1、LoadBootScripts
先判断是否自定义了rc文件,如果没有的话就读取默认的/init.rc
system/core/init/init.cpp
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
//创建解析器
Parser parser = CreateParser(action_manager, service_list);
//先判断是否自定义了rc文件,如果没有的话就读取默认的/init.rc
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
//解析/init.rc
parser.ParseConfig("/init.rc");
//以下这些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 {
//解析自定义的rc文件
parser.ParseConfig(bootscript);
}
}
这个过程中最主要的一个操作是,调用CreateParser方法创建了rc文件的解析器,有三种解析器,分别是:
- service解析器,用于解析service开头的rc语句,对应的类是service.cpp
- on 解析器,用于解析on 开头的语句.对应的类是action_parser.cpp
- import解析器,用于解析import开头的语句,该语句定义了让rc文件之间可以互相包含进来.对应的类是import_parser.cpp
类图为:
Parser中主要有四个关键方法:
- ParseSection 该方法主要解析主要的指令,例如service,on,import等
- ParseLineSection 该方法主要是解析指令service,on后面跟随的Command和options
- EndSection 解析每个命令配置完成后调用,这时解析完成的数据需要在这里进行保存
- EndFile 解析完成一个文件时调用
2.2、ParseConfig & ParseData方法
ParseConfig会调用两个参数的ParseConfig,这里会判断传入的path是文件还是目录,如果是文件的话调用ParseConfigFile解析,如果是目录的话调用ParseConfigDir遍历目录下的rc文件全部解析,这个步骤可以忽略不用太关注,最终都会调用到ParseData方法真正解析文件,整个解析过程会扫描配置文件,解析指令存放到链表中,在init进程中执行。
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
//开始之前为了安全考虑,先复制一份文件内容进行解析
std::vector data_copy(data.begin(), data.end());
//在读取到的数据最后添加一个‘\0’
data_copy.push_back('\0');
parse_state state;
//初始化从第0行
state.line = 0;
//开始指针指向0
state.ptr = &data_copy[0];
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
int section_start_line = -1;
//存放读取到的配置文本
std::vector args;
auto end_section = [&] {
if (section_parser == nullptr) return;
//调用EndSection方法保存读取到的配置信息
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: //字符串解析结束
end_section();
return;
case T_NEWLINE://解析完一行,新开始一行的解析
//开始读取新的一行,line加1
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.
//这里主要是针对ueventd.rc文件的处理,识别 /sys/ /dev/开头的配置
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;
}
}
if (section_parsers_.count(args[0])) {
//切换parser之前先确保上一次的解析完成
end_section();
//args[0]表示是section的开头文本,也就是service,on,import三者之一
//依据这个来匹配到对应的Parser
section_parser = section_parsers_[args[0]].get();
section_start_line = state.line;
//解析Action关键字行
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) {
//解析Action对应的命令行
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中,一行保存一次
//state是一个结构体
//struct parse_state
//{
// char *ptr; // 要解析的字符串
// char *text; // 解析到的字符串,可以理解为返回一行的数据
// int line; // 解析到第行数
// int nexttoken; // 解析状态,有 T_EOF、T_NEWLINE、T_TEXT 三种
//};
//
//
args.emplace_back(state.text);
break;
}
}
}
ParseData函数中我们需要注意以下几点
- arg[0] 是每一行读取到的第一个字符串,解析过程中会依据这个字符串来动态切换解析器,字符串和解析器的对应关系为:service -> ServiceParser, on -> ActionParser,import -> ImportParser
- parse_state 是一个结构体
struct parse_state{
char *ptr; //要解析的字符串
char *text;//解析到的字符串,一般为一行字符串
int line; //解析到第几行
int nexttoken; //解析状态,有 T_EOF、T_NEWLINE、T_TEXT 三种
}
- nexttoken,解析状态,T_EOF 表示字符串解析结束,T_NEWLINE 表示解析完一行的数据,T_TEXT 表示解析到一个单词
2.3、ActionParser
上面有提供Parser的类图大致结构,在解析过程中实际的解析会在对应的Parser中进行。ActionParser主要是解析以 on开头的一系列命令,Action决定对应的动作什么时候会执行,解析完成后Action会被添加到存放Action的队列中。Action的每个命令是按顺序排队执行,每个命令依次进入执行状态。
ActionParser实际上就是把配置解析为Action结构,对应的Action类定义在system/core/init/action.h中
......
std::map property_triggers_;
std::string event_trigger_;
std::vector commands_;
......
Commands存放Action中定义的一系列Command操作。
2.3.1 ParseSection
Result ActionParser::ParseSection(std::vector&& args,
const std::string& filename, int line) {
//这里判断Action是否有定义对应的trigger触发器,如果没有的话就返回失败
std::vector triggers(args.begin() + 1, args.end());
if (triggers.size() < 1) {
return Error() << "Actions must have a trigger";
}
......
std::string event_trigger;
std::map property_triggers;
//解析Action的trigger触发器,提取event_trigger和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(false, action_subcontext, filename, line, event_trigger,
property_triggers);
action_ = std::move(action);
return Success();
}
这里传递给ParseTriggers方法的triggers是去除了命令关键字on之外的触发器信息,ParseTriggers会判断是否是属性变化触发的触发器,如果是属性变化的触发器的话填充到property_triggers,否则填充到event_trigger。
Result ParseTriggers(const std::vector& args, Subcontext* subcontext,
std::string* event_trigger,
std::map* 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";
}
//trigger触发器需要通过 && 连接
//例如:on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
if (i % 2) {
if (args[i] != "&&") {
return Error() << "&& is the only symbol allowed to concatenate actions";
} else {
continue;
}
}
//判断是否是property:开头的配置条件,Action中满足条件时会被执行,这里主要是属性变化触发器的解析
//这里会把属性变化的触发条件填充到property_triggers中
if (!args[i].compare(0, prop_str.length(), prop_str)) {
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做了两件事情
- 触发器语法判断 && 连接符
- 触发器依据是否是属性变化区分不同触发器提取到不同的数据结构里面
2.3.2、ParseLineSection
解析完成命令关键字部分后就会解析对应的Command子模块,ParseLineSection直接调用了Action的AddCommand函数
Result Action::AddCommand(const std::vector& args, int line) {
if (!function_map_) {
return Error() << "no function map available";
}
//通过args调用FindFunction查找对应的处理命令,类似解析命令关键字行的做法
//利用args[0]进行匹配,function_map在init进程启动的时候已经初始化了。
auto function = function_map_->FindFunction(args);
if (!function) return Error() << function.error();
commands_.emplace_back(function->second, function->first, args, line);
return Success();
}
FindFunction函数在keyword_map.h中会通过args[0]以及具体的字符内容匹配到最终的命令参数,最后填充到commands_中
function_map_的内容如下(system/core/init/builtins.cpp)
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}}},
#ifdef VENDOR_EDIT
{"reload_policy", {0, 0, {true, do_reload_policy}}},
{"md", {1, 1, {true, do_md}}},
{"copyall", {2, 2, {true, do_copyall}}},
#if OP_FEATURE_UPDATE_RESERVE == 1
{"mount_reserve", {3, kMax, {false, do_mount_reserve}}},
#endif
#endif
};
map对应的各字段,从左到右
- 函数名
- 最小参数个数
- 最大参数个数
- 处理的函数地址
2.3.3、EndSection
这里主要是将最终解析出来的Action保存到ActionManager的actions_中
Result ActionParser::EndSection() {
if (action_ && action_->NumCommands() > 0) {
action_manager_->AddAction(std::move(action_));
}
return Success();
}
void ActionManager::AddAction(std::unique_ptr action) {
actions_.emplace_back(std::move(action));
}
2.4、ServiceParser
Service结构比较复杂,感兴趣的话可以阅读system/core/init/service.h文件。但是主要还是定义了Service的一些操作函数,rc文件中定义的一些命令和Options
2.4.1、ParseSection
Result ServiceParser::ParseSection(std::vector&& args,
const std::string& filename, int line) {
//首先需要验证service配置是否正确,service配置必须包含
//服务名和启动参数
if (args.size() < 3) {
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 << "'";
}
......
//服务启动参数
std::vector str_args(args.begin() + 2, args.end());
service_ = std::make_unique(name, restart_action_subcontext, str_args);
return Success();
}
2.4.2、ParseLineSection
ParseLineSection主要是解析service命令快接下来Options的指令块。
Result ServiceParser::ParseLineSection(std::vector&& args, int line) {
return service_ ? service_->ParseLine(std::move(args)) : Success();
}
Result Service::ParseLine(const std::vector& args) {
static const OptionParserMap parser_map;
auto parser = parser_map.FindFunction(args);
if (!parser) return parser.error();
return std::invoke(*parser, this, args);
}
parser_map是固定的,内容如下,参数分别代表:处理关键字、最小参数个数、最大参数个数、处理函数地址。:
static const Map option_parsers = {
{"capabilities",
{1, kMax, &Service::ParseCapabilities}},
{"class", {1, kMax, &Service::ParseClass}},
{"console", {0, 1, &Service::ParseConsole}},
{"critical", {0, 0, &Service::ParseCritical}},
{"disabled", {0, 0, &Service::ParseDisabled}},
{"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}},
{"priority", {1, 1, &Service::ParsePriority}},
{"keycodes", {1, kMax, &Service::ParseKeycodes}},
{"oneshot", {0, 0, &Service::ParseOneshot}},
{"onrestart", {1, kMax, &Service::ParseOnrestart}},
{"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}},
{"shutdown", {1, 1, &Service::ParseShutdown}},
{"socket", {3, 6, &Service::ParseSocket}},
{"file", {2, 2, &Service::ParseFile}},
{"user", {1, 1, &Service::ParseUser}},
{"writepid", {1, kMax, &Service::ParseWritepid}},
};
3.4.3 EndSection
EndSection 将创建并填充完成的 Sevice 对象加入到 services_ 链表中,首先会先依据service name在链表中查找是否已经有了,如果有的话需要先移除,再重新创建新的服务保存到链表中
Result ServiceParser::EndSection() {
if (service_) {
//先找到旧的service并移除
Service* old_service = service_list_->FindService(service_->name());
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到链表中
service_list_->AddService(std::move(service_));
}
return Success();
}
三、总结
总结来看,init.rc文件会依据不同的命令切换不同的Parser进行解析,解析过程中主要是执行ParseSection,ParseLineSection和EndSection来进行解析。此过程中会包含语法的检查,数据储存等。大致的过程如下: