11.2.2. init.rc
11.2.2.1. init.rc语法
init.rc是一个配置文件,由Android初始化语言(Android Init Language)编写,编写的脚本init.rc是在init进程启动后执行的启动脚本. init.rc主要包含5种类型语句.
Action
Command
Service
Option
Import
所有这些都是以行为单位,各种记号由空格来隔开.
C语言风格的反斜杠可用于记号间插入空格.
双引号也可用于防止字符串被空格分割成多个记号
行末的反斜杠用于折行,注释行以井号开头(允许以空格开头)
注解
这指示一个语法文件,就像一个xml文件一样,没有执行顺序的,解析器通过读这个文件获取想要的数据,包括service, action. Action和Service声明一个新的分组Section.所有的命令或选项都属于最近声明的分组.位于第一个分组之前的命令或选项将会被 忽略.Action和Service都有唯一的名字.如有重名的情况,第二个申明的将会被作为错误忽略
init.rc
文件大致分为两大部分
一部分是以
on
关键字开头的”动作列表”(action list)
Action类型语句格式
on <trigger> [&& <trigger>] ##设置触发器
<command>
<command> ##动作触发之后执行的命令
...
注解
trigger触发器本质上是一个字符串,能够匹配某种包含该字符串的事件.trigger又被细分为事件触发器(event trigger)和属性触发器(property trigger). 一个Action可以有多个属性触发器,但是最多有一个事件触发器
几种常见的事件触发器
类型 |
说明 |
boot |
init.rc被装载后触发 |
device-added-<path> |
指定设备被添加时触发 |
device-removed-<path> |
指定设备被移除时触发 |
service-exited-<name> |
在特定服务(service)退出时触发 |
early-init |
初始化之前触发 |
late-init |
初始化之后触发 |
init |
初始化时触发(在/init.conf启动配置文件被装载之后) |
另一部分是以”Service”关键字开头的”服务列表”(service list)
Service类型语句格式
Service <name> <pathname> [ <argument> ] ##<Service的名字><执行程序路径><传递参数>
<option> ##option是service的修饰词,影响什么时候,如何启动service
<option>
...
注解
option是service的可选项,与service配合使用
oneshot: service退出后不再重启
onrestart: 当服务重启时,执行相应的命令
11.2.2.2. init.rc注释
# Copyright (C) 2012 The Android Open Source Project
#
# IMPORTANT: Do not create world writable files or directories.
# This is a common source of Android security bugs.
#
## import <filename>, 插入一个init配置文件,扩展当前配置
import /init.environ.rc
import /system/etc/init/hw/init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /system/etc/init/hw/init.usb.configfs.rc
import /system/etc/init/hw/init.${ro.zygote}.rc
# 触发条件early-init,在early-init阶段调用以下命令
# Cgroups are mounted right before early-init using list from /etc/cgroups.json
on early-init
# 打开文件/proc/sys/kernel/sysrq,并写入0
# Disable sysrq from keyboard
write /proc/sys/kernel/sysrq 0
# Android doesn't need kernel module autoloading, and it causes SELinux
# denials. So disable it by setting modprobe to the empty string. Note: to
# explicitly set a sysctl to an empty string, a trailing newline is needed.
write /proc/sys/kernel/modprobe \n
# Set the security context of /adb_keys if present.
# 恢复指定文件到file_contexts配置中指定的安全上下文环境
restorecon /adb_keys
# Set the security context of /postinstall if present.
restorecon /postinstall
# 创建目录
mkdir /acct/uid
# memory.pressure_level used by lmkd
chown root system /dev/memcg/memory.pressure_level
chmod 0040 /dev/memcg/memory.pressure_level
# app mem cgroups, used by activity manager, lmkd and zygote
mkdir /dev/memcg/apps/ 0755 system system
# cgroup for system_server and surfaceflinger
# mkdir <path> [mode] [owner] [group],如果没有指定,默认权限为755,属于root用户和root组
mkdir /dev/memcg/system 0550 system system
# symlink the Android specific /dev/tun to Linux expected /dev/net/tun
mkdir /dev/net 0755 root root
# 创建软连接
symlink ../tun /dev/net/tun
# set RLIMIT_NICE to allow priorities from 19 to -20
# 执行start ueventd.ueventd是一个service,后面有定义
start ueventd
... ...
# Healthd can trigger a full boot from charger mode by signaling this
# property when the power button is held.
# 属性触发器,在sys.boot_from_charger_mode=1时执行下述命令
on property:sys.boot_from_charger_mode=1
# 停止指定类别服务类下的所有已运行的服务
class_stop charger
# 触发一个事件,将该action排在某个action之后(用于action排队)
trigger late-init
... ...
# Mount filesystems and start core system services.
# 上面触发的late-init事件
on late-init
trigger early-fs
# Mount fstab in init.{$device}.rc by mount_all command. Optional parameter
# '--early' can be specified to skip entries with 'latemount'.
# /system and /vendor must be mounted by the end of the fs stage,
# while /data is optional.
trigger fs
trigger post-fs
... ...
on boot
# basic network init
# 初始化网络
ifup lo
hostname localhost
domainname localdomain
... ...
# 控制台进程
service console /system/bin/sh
# 为当前service设定一个类别,相同类别的服务将会同时启动或者停止,默认类名是default
class core
# 服务需要一个控制台
console
# 服务不会自动启动,必须通过服务名显示启动
disabled
# 在执行此服务之前切换用户名,当前默认的是root
user shell
group shell log readproc
seclabel u:r:shell:s0
setenv HOSTNAME console
11.2.2.3. init.rc代码解析
LoadBootScripts
// system/core/init/init.cpp
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", "");
//如果bootscript不存在则解析init.rc文件
if (bootscript.empty()) {
//实际的解析过程
parser.ParseConfig("/system/etc/init/hw/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
// late_import is available only in Q and earlier release. As we don't
// have system_ext in those versions, skip late_import for system_ext.
parser.ParseConfig("/system_ext/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);
}
}
CreateParser
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
//添加一个ServiceParser, 解析service
parser.AddSectionParser("service", std::make_unique<ServiceParser>(
&service_list, GetSubcontext(), std::nullopt));
//添加一个ActionParser, 解析On
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
//添加一个ImportParser, 解析import
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
return parser;
}
parser.ParseConfig
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) { //判断传入参数是否为目录地址
return ParseConfigDir(path); //递归目录,最终仍是调用ParseConfigFile来解析实际的文件
}
return ParseConfigFile(path); //传入参数为文件地址,则直接解析
}
ParseConfigDir
bool Parser::ParseConfigDir(const std::string& path) {
LOG(INFO) << "Parsing directory " << path << "...";
std::unique_ptr<DIR, decltype(&closedir)> config_dir(opendir(path.c_str()), closedir);
if (!config_dir) {
PLOG(INFO) << "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)) {
LOG(ERROR) << "could not import file '" << file << "'";
}
}
return true;
}
ParseConfigFile
bool Parser::ParseConfigFile(const std::string& path) {
LOG(INFO) << "Parsing file " << path << "...";
android::base::Timer t;
auto config_contents = ReadFile(path); //读取指定文件的内容,保存为字符串形式
if (!config_contents.ok()) {
LOG(INFO) << "Unable to read config file '" << path << "': " << config_contents.error();
return false;
}
ParseData(path, &config_contents.value()); //解析获取的字符串
LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";
return true;
}
ParseData
void Parser::ParseData(const std::string& filename, std::string* data) {
data->push_back('\n'); // TODO: fix tokenizer
data->push_back('\0');
//解析用的结构体
parse_state state;
state.line = 0;
state.ptr = data->data();
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
int section_start_line = -1;
std::vector<std::string> args;
// If we encounter a bad section start, there is no valid parser object to parse the subsequent
// sections, so we must suppress errors until the next valid section is found.
bool bad_section_found = false;
auto end_section = [&] {
bad_section_found = false;
if (section_parser == nullptr) return;
if (auto result = section_parser->EndSection(); !result.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << section_start_line << ": " << result.error();
}
section_parser = nullptr;
section_start_line = -1;
};
for (;;) {
//next_token以行为单位分割参数传递过来的字符串
switch (next_token(&state)) {
case T_EOF:
end_section(); //解析结束
for (const auto& [section_name, section_parser] : section_parsers_) {
section_parser->EndFile();
}
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.
auto line_callback = std::find_if(
line_callbacks_.begin(), line_callbacks_.end(),
[&args](const auto& c) { return android::base::StartsWith(args[0], c.first); });
if (line_callback != line_callbacks_.end()) {
end_section();
if (auto result = line_callback->second(std::move(args)); !result.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
}
} else if (section_parsers_.count(args[0])) {
end_section(); //结束上一个parser的工作
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.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
section_parser = nullptr;
bad_section_found = true;
}
} else if (section_parser) {
if (auto result = section_parser->ParseLineSection(std::move(args), state.line);
!result.ok()) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line << ": " << result.error();
}
} else if (!bad_section_found) {
parse_error_count_++;
LOG(ERROR) << filename << ": " << state.line
<< ": Invalid section keyword found";
}
args.clear();
break;
case T_TEXT:
args.emplace_back(state.text);
break;
}
}
}