Android系统启动2————Android初始化语言概述和解析
一.概述
在Android初始化语言包含了4种类型的声明,Actions(行动),Commands(命令)、Services(服务)和Options(选项)。
所以这些都是以行为单位,各种记号由空格隔开。反斜杠可用于在记号间插入空格,双引号也可以用于防止字符串被空格分隔成多个记号。行末的反斜杠用于折行。
注释行以井号(#)开头(允许以空格开头)。
Actions和Services声明一个新的分组。所有的命令或选项都属于最近申明的分组。位于第一个分组之前的命令或选项将会被忽略。
Actions和Services有唯一的名字。如果有重名的情况,第二个申明的将会被作为错误忽略。
二.Actions
Actions其实就是一序列的Commands(命令)。Actions都有一个trigger(触发器),它被用于决定action的执行时间。当一个符合action触发条件的事件发生时,action会被加入到执行队列的末尾,除非它已经在队列里了。
队列中的每一个action都被依次提取出,而这个action中的每个command(命令)都将被依次执行。Init在这些命令的执行期间还控制着其他的活动(设备节点的创建和注销、属性的设置、进程的重启)。
1.Actions的形式
on <trigger>
<command>
<command>
<command>
示例
on boot
ifup lo
hostname localhost
domainname localdomain
上面的示例中,boot是触发器,下面的三行是command
2.触发器
触发器 | 含义 |
---|---|
boot | 在init执行后出发,也就是在init.rc被装载后执行该trigger |
<name>=<value> | 当属性被设置成时被触发 |
device-added-<path> | 当设备节点被添加时触发 |
device-removed-<path> | 当设备节点被移除时添加 |
service-exited-<name> | 会在一个特定的服务退出时触发 |
3.命令
命令 | 含义 |
---|---|
exec <path> [<argument> ]* | 创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。 尽量避免使用 ,它可能会引起init执行超时 |
export <name> <value> | 在全局环境中将 <name>变量的值设为<value> |
ifup <interface> | 启动网络接口 |
import <filename> | 指定要解析的其他配置文件 |
hostname <name> | 设置主机名 |
chdir <directory> | 改变工作目录 |
chmod <octal-mode><path> | 改变文件的访问权限 |
chown <owner><group> <path> | 更改文件的所有者和组 |
chroot <directory> | 改变处理根目录 |
class_start<serviceclass> | 启动所有指定服务类下的未运行服务 |
class_stop<serviceclass> | 停止指定服务类下的所有已运行的服务。 |
domainname <name> | 设置域名 |
insmod <path> | 加载<path>指定的驱动模块 |
mkdir <path> [mode][owner] [group] | 创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。 |
mount <type> <device> <dir> [<mountoption> ]* | 试图在目录<dir>挂载指定的设备 |
setkey | 保留,暂时未用 |
setprop <name><value> | 将系统属性<name>的值设为<value> |
setrlimit <resource> <cur> <max> | 设置<resource>的rlimit (资源限制) |
start <service> | 启动指定服务(如果此服务还未运行) |
stop<service> | 停止指定服务(如果此服务在运行中) |
symlink <target> <path> | 创建一个指向<path>的软连接<target> |
sysclktz <mins_west_of_gmt> | 设置系统时钟基准 |
trigger <event> | 触发一个事件。用于Action排队 |
wait <path> [<timeout> ] | 等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定<timeout>,默认超时时间是5秒。 |
write <path> <string> [ <string> ]* | 向<path>指定的文件写入一个或多个字符串 |
三.Services
Services(服务)是一个程序,他在初始化时启动,并在退出时重启(可选)
1.Services 形式
service <name> <pathname> [ <argument> ]* //<service的名字><执行程序路径><传递参数>
<option>
<option>
示例
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
Services的选项是服务的修饰符,可以影响服务如何以及怎样运行
2.选项
- critical,明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。
- disabled,表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。
- setenv <name><value>,在进程启动时将环境变量<name>设置为<value>。
- socket <name><type> <perm> [ <user> [ <group> ] ], 创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。<type> 必须是 “dgram”,“stream” 或"seqpacket"。用户和组默认是0。
- user <username>,在启动这个服务前改变该服务的用户名。
- group <groupname> [<groupname> ]*,在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。
- oneshot, 服务退出时不重启。
- class <name>,指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。
- onrestart,当服务重启,执行一个命令
四.分析rc文件
在Android启动过程中,通过下面代码来分析rc文件.
源码版本:Android 8.0
parser.ParseConfig("/init.rc");
1.Parser类
Parser类的定义在parser.h中
目录:system/core/init/init_parser.h
class SectionParser {
public:
virtual ~SectionParser() {
}
virtual bool ParseSection(const std::vector<std::string>& args,
std::string* err) = 0;
virtual bool ParseLineSection(const std::vector<std::string>& args,
const std::string& filename, int line,
std::string* err) const = 0;
virtual void EndSection() = 0;
virtual void EndFile(const std::string& filename) = 0;
};
class Parser {
public:
static Parser& GetInstance();
void DumpState() const;
bool ParseConfig(const std::string& path);
void AddSectionParser(const std::string& name,
std::unique_ptr<SectionParser> parser);
void set_is_system_etc_init_loaded(bool loaded) {
is_system_etc_init_loaded_ = loaded;
}
void set_is_vendor_etc_init_loaded(bool loaded) {
is_vendor_etc_init_loaded_ = loaded;
}
void set_is_odm_etc_init_loaded(bool loaded) {
is_odm_etc_init_loaded_ = loaded;
}
bool is_system_etc_init_loaded() { return is_system_etc_init_loaded_; }
bool is_vendor_etc_init_loaded() { return is_vendor_etc_init_loaded_; }
bool is_odm_etc_init_loaded() { return is_odm_etc_init_loaded_; }
private:
Parser();
void ParseData(const std::string& filename, const std::string& data);
bool ParseConfigFile(const std::string& path);
bool ParseConfigDir(const std::string& path);
std::map<std::string, std::unique_ptr<SectionParser>> section_parsers_;
bool is_system_etc_init_loaded_ = false;
bool is_vendor_etc_init_loaded_ = false;
bool is_odm_etc_init_loaded_ = false;
};
parser包含一个比较重要的成员变量
- section_parsers_,是负责解析语法的,在初始化的时候添加了三个解析类ServiceParser、ActionParser、ImportParser,通过AddSectionParser函数添加
目录;system/core/init/init_parser.cpp
void Parser::AddSectionParser(const std::string& name,
std::unique_ptr<SectionParser> parser) {
section_parsers_[name] = std::move(parser);
}
2.parser.ParseConfig
分析完Parser类,我们继续来看看ParseConfig,即Parser对外提供的解析.rc方法
目录:system/core/init/init_parser.cpp
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) {
return ParseConfigDir(path);
}
return ParseConfigFile(path);//继续追踪
}
bool Parser::ParseConfigFile(const std::string& path) {
LOG(INFO) << "Parsing file " << path << "...";
Timer t;
std::string data;
if (!read_file(path, &data)) { //打开.rc文件,并返回内容
return false;
}
data.push_back('\n'); // TODO: fix parse_config.
ParseData(path, data); //核心函数,主要用来分析.rc文件内容
for (const auto& sp : section_parsers_) {
sp.second->EndFile(path);
}
LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)";
return true;
}
下面分析重点内容ParseData
void Parser::ParseData(const std::string& filename, const std::string& data) {
//TODO: Use a parser with const input and remove this copy
std::vector<char> data_copy(data.begin(), data.end());
data_copy.push_back('\0');
parse_state state;
state.filename = filename.c_str();
state.line = 0;
state.ptr = &data_copy[0];
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
std::vector<std::string> args;//存放token的容器
for (;;) {
//相当于词法分析器,重点代码①
switch (next_token(&state)) {
case T_EOF://.rc文件分析完毕
if (section_parser) {
section_parser->EndSection();
}
return;
case T_NEWLINE://分析每一行的代码
state.line++;
if (args.empty()) { //为空
break;
}
//section_parsers_ 匹配规则
if (section_parsers_.count(args[0])) {
if (section_parser) {
section_parser->EndSection();
}
// 根据参数获取解析器②
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
// 解析内容,重点代码③
if (!section_parser->ParseSection(args, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
section_parser = nullptr;
}
} else if (section_parser) {
std::string ret_err;
if (!section_parser->ParseLineSection(args, state.filename,
state.line, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
}
}
args.clear();
break;
case T_TEXT://处理每一个token
args.emplace_back(state.text);
break;
}
}
}
分析一下parse_config方法的原理。在for循环中next_token方法不断从init.rc文件中获取token。
token,就是一种编程语言的最小 单元,也就是不可再分。例如,对于传统的编程语言,if、then等关键字、变量名等标识符都属于一个token。而对于init.rc文件来 说,import、on、以及触发器的参数值,都属于一个token。
一个完整的编译器最开始需要进行词法和语法分析,词法分析就是从源代码中挑出一个个token,也就是说,词法分析器的返回值是 Token,而语法分析器的输入就是词法分析器的输出。语法分析器需要分析一个个的token,而不是一个个的字符。由于init解析语言很简 单,所以就将词法和语法分析器放到了一起。词法分析器就是next_token函数,而语法分析器就是T_NEWLINE分支中的代码,更详细的说是section_parser->ParseLineSection。
3.next_token
下面是next_token()的调用
next_token(&state)
先分析传入的参数,类型是parse_state的结构体,定义在parser.h
目录:system/core/init/parser.h
struct parse_state
{
char *ptr;
char *text;
int line;
int nexttoken;
void *context;
void (*parse_line)(struct parse_state *state, int nargs, char **args);
const char *filename;
void *priv;
};
然后继续看看next_token的实现,他的实现在parser.cpp
目录:system/core/init/parser.cpp
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;
}
//过滤掉结束符 \r \t # 符号
for (;;) {
switch (*x) {
case 0:
state->ptr = x;
return T_EOF;
case '\n':
x++;
state->ptr = x;
return T_NEWLINE;
case ' ':
case '\t':
case '\r':
x++;
continue;
case '#':
while (*x && (*x != '\n')) x++;
if (*x == '\n') {
state->ptr = x+1;
return T_NEWLINE;
} else {
state->ptr = x;
return T_EOF;
}
default:
goto text; //开始解析token
}
}
textdone: //解析完成,返回给ParseConfig
state->ptr = x; //更新下次需要解析的字符串位置
*s = 0;
return T_TEXT;
text:
state->text = s = x; //将解析出来的token存放在text中
textresume:
for (;;) {
switch (*x) {
case 0:
goto textdone; //完成解析
case ' ':
case '\t':
case '\r':
x++;
goto textdone;
case '\n':
state->nexttoken = T_NEWLINE;
x++;
goto textdone;
case '"':
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;
}
通过上面的next_token解析出来一个个tokent,接下来来就是分析token
4.section_parsers_ 获取解析器
先回顾一下ParseData的内容,现在就可解析代码含义了
目录:system/core/init/init_parser.cpp ParseData函数
case T_NEWLINE: //解析出来
state.line++;
if (args.empty()) {
break;
}
//使用count,返回的是被查找元素的个数。如果有,返回1;否则,返回0。
//即查找该Actions/Services是否有对应的加载器
if (section_parsers_.count(args[0])) {
if (section_parser) {
section_parser->EndSection();
}
//获取section_parser
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
//调用section_parser->ParseSection进行初始化 重点代码下面分析
if (!section_parser->ParseSection(args, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
section_parser = nullptr;
}
} else if (section_parser) {
std::string ret_err;
//进行解析 重点代码下面分析
if (!section_parser->ParseLineSection(args, state.filename,
state.line, &ret_err)) {
parse_error(&state, "%s\n", ret_err.c_str());
}
}
args.clear();
break;
case T_TEXT:
args.emplace_back(state.text);//加入到args容器的尾部
break;
获取section_parser语法分析器
ection_parser = section_parsers_[args[0]].get();获取到成员分析器
我们前面在分析Parser类说过,他有一个section_parsers_成员变量,在init.cpp通过AddSectionParser来添加对应的语法解析器。
调用
目录;system/core/init/init.cpp
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm)); //service解析器
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am)); //Actions解析器
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
实现
目录:system/core/init/init_parser.cpp
void Parser::AddSectionParser(const std::string& name,
std::unique_ptr<SectionParser> parser) {
section_parsers_[name] = std::move(parser);
}
我们现在主要来看看service解析器的实现
5.ServiceParser
ServiceParser的定义,在 service.h
目录: system/core/init/service.h
class ServiceParser : public SectionParser {
public:
ServiceParser() : service_(nullptr) {
}
bool ParseSection(const std::vector<std::string>& args,
std::string* err) override;
bool ParseLineSection(const std::vector<std::string>& args,
const std::string& filename, int line,
std::string* err) const override;
void EndSection() override;
void EndFile(const std::string&) override {
}
private:
bool IsValidName(const std::string& name) const;
std::unique_ptr<Service> service_;
};
重点来看ParseSection和ParseLineSection两个方法
首先看看ParseSection方法
目录:system/core/init/service.c
bool ServiceParser::ParseSection(const std::vector<std::string>& args,
std::string* err) {
if (args.size() < 3) {
*err = "services must have a name and a program";
return false;
}
const std::string& name = args[1];
if (!IsValidName(name)) {
*err = StringPrintf("invalid service name '%s'", name.c_str());
return false;
}
std::vector<std::string> str_args(args.begin() + 2, args.end());//获取Service的参数
service_ = std::make_unique<Service>(name, str_args);//创建对应的Service类
return true;
}
//Service的构造方法
Service::Service(const std::string& name, const std::vector<std::string>& args)
: name_(name), //确定service的名字
classnames_({"default"}),
flags_(0),
pid_(0),
crash_count_(0),
uid_(0),
gid_(0),
namespace_flags_(0),
seclabel_(""),
keychord_id_(0),
ioprio_class_(IoSchedClass_NONE),
ioprio_pri_(0),
priority_(0),
oom_score_adjust_(-1000),
args_(args) {
onrestart_.InitSingleTrigger("onrestart"); //设置重启
}
再来分析ParseLineSection
目录:system/core/init/service.c
bool ServiceParser::ParseLineSection(const std::vector<std::string>& args,
const std::string& filename, int line,
std::string* err) const {
return service_ ? service_->ParseLine(args, err) : false; //
}
bool Service::ParseLine(const std::vector<std::string>& args, std::string* err) {
if (args.empty()) {
*err = "option needed, but not provided";
return false;
}
//查看OptionParserMap的构造函数
static const OptionParserMap parser_map;
//找出该function对应的方法
auto parser = parser_map.FindFunction(args[0], args.size() - 1, err);
if (!parser) {
return false;
}
//执行该方法
return (this->*parser)(args, err);
}
//OptionParserMap的构造函数
class Service::OptionParserMap : public KeywordMap<OptionParser> {
public:
OptionParserMap() {
}
private:
Map& map() const override;
};
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}},
{"console", {0, 1, &Service::ParseConsole}},
{"critical", {0, 0, &Service::ParseCritical}},
{"disabled", {0, 0, &Service::ParseDisabled}},
{"group", {1, NR_SVC_SUPP_GIDS + 1, &Service::ParseGroup}},
{"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}},
{"oom_score_adjust",
{1, 1, &Service::ParseOomScoreAdjust}},
{"namespace", {1, 2, &Service::ParseNamespace}},
{"seclabel", {1, 1, &Service::ParseSeclabel}},
{"setenv", {2, 2, &Service::ParseSetenv}},
{"socket", {3, 6, &Service::ParseSocket}},
{"file", {2, 2, &Service::ParseFile}},
{"user", {1, 1, &Service::ParseUser}},
{"writepid", {1, kMax, &Service::ParseWritepid}},
};
// clang-format on
return option_parsers;
}
在解析完所有Service后,会调用EndSection方法
void ServiceParser::EndSection() {
if (service_) {
ServiceManager::GetInstance().AddService(std::move(service_));
}
}
void ServiceManager::AddService(std::unique_ptr<Service> service) {
Service* old_service = FindServiceByName(service->name());
if (old_service) {
LOG(ERROR) << "ignored duplicate definition of service '" << service->name() << "'";
return;
}
services_.emplace_back(std::move(service)); //将Service加入到Service链表中,并根据之前的即内容进行填充
}
五.小结
- 在init进程中将rc文件传入,并传入对应的解析器
- 在init_parser利用parser对rc文件进行解析
- 解析分为两个步骤:词法分析和语法分析
- 词法分析,将rc文件里的字符分割成一个个对应的token
- 语法分析,将一个个token放容器中,调用对应的解析器创建Service/Actions,然后根据对应的token在map中映射为对于的方法,进行调用。
六.参考资料
Android的init过程(二):初始化语言(init.rc)解析
Android P on property属性无法trigger流程分析