分享免费的编程资源和教程

网站首页 > 技术教程 正文

Android 开机启动流程分析(06)init.rc解析流程

goqiw 2025-07-02 20:41:02 技术教程 4 ℃ 0 评论

本章关键点总结 & 说明:

说明:思维导图是基于之前文章不断迭代的,本章内容我们关注"解析init.rc" 部分即可

1 init.rc语法知识
1.1 AIL{Android Init Language}语言的严格规则说明:

C++
/**
1 这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。
2 可以使用反斜杠转义符在 Token中插入空格。
3 双引号可以将多个由空格分隔的Tokens合成一个Tokens。
4 如果一行写不下,可以在行尾加上反斜杠,来连接下一行。即可以用反斜杠将多行代码连接成一行代码。
5 AIL的注释与很多Shell脚本一行,以#开头。
6 AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。即每一个 Actions或 Services确定一个Section。
7 所有的Commands和Options只能属于最近定义的Section。如果Commands和Options在第一个Section之前被定义,它们将被忽略。
8 Actions和Services的名称必须唯一。如果Action或Service拥有同样的名称,那么init在执行它们时将抛出错误log,并忽略这些Action和Service。
*/

init.rc是由AIL脚本写成的文件,接下来以Section方式对Actions、Services、Commands、Options部分进行分析

1.2 Section的详细说明{init.rc的解析是以Section为核心进行解析}

section分为三种类型,分别由三个关键字(on、service、import)来区分

1.2.1 import类型的section表示引入另外一个.rc文件,如下所示:

Bash
import init.test.rc
#包含其他section, 在解析完init.rc文件后继续会调用init_parse_config_file来解析引入的.rc文件。

1.2.2 on类型的section表示一系列命令的组合, 例如:

Bash
on init
export PATH /sbin:/system/sbin:/system/bin
export ANDROID_ROOT /system
export ANDROID_DATA /data
#说明:Actions的语法格式如下{AIL语言}:
on <trigger>
<command1>
<command2>
<command3>

这样一个section包含了三个export命令,命令的执行是以section为单位的,所以这3个命令是一起执行的,不会单独执行。

那什么时候执行呢?

这是由init.c的main()所决定的,main()里在某个时间会调用action_for_each_trigger("init", action_add_queue_tail);这样就把 on init 开始的这样一个section里的所有命令加入到一个执行队列,在未来的某个时候会顺序执行队列里的命令,所以调用action_for_each_trigger()的先后决定了命令执行的先后。command是trigger的实体,常见的commands如下所示:

1.3 service类型的section表示一个可执行程序,例如:

Bash
service surfaceflinger /system/bin/surfaceflinger
class main
user system
group graphics drmrpc
onrestart restart zygote
#surfaceflinger作为一个名字标识了这个service,/system/bin/surfaceflinger表示可执行文件的位置
#class、user、group、onrestart这些关键字所对应的行都被称为options,
#options是用来描述的service一些特点,不同的service有着不同的options。
#说明:service的语法格式如下{AIL语言}:
service <name> <pathname> [ <argument> ]* 
<option1> 
<option2>
<option3>

Option是service的修饰词,主要包括以下几类:

关键问题:service类型的section标识了一个service(或者说可执行程序), 那这个service什么时候被执行?
是在class_start 这个命令被执行的时候,这个命令行总是存在于某个on类型的section中,“class_start core”这样一条命令被执行,就会启动类型为core的所有service。如:

Bash
on boot
class_start core
class_start main

属性的设置方式如下:

通过在配置文件init.rc中通过命令setprop来设置

通过程序来用property_set来设置

可以看出android的启动过程主要就是on类型的section被执行的过程。


1.4 关于property的说明

属性的判断on property:<name>=<value>,例如:

Bash
on property:vold.decrypt=trigger_encryption #表示当属性vold.decrypt的值为trigger_encryption时执行下面的命令
start surfaceflinger #开始执行service surfaceflinger
start encrypt

属性的设置方式如下:

Bash
通过在配置文件init.rc中通过命令setprop来设置
通过程序来用property_set来设置

2 AIL相关的结构体说明

2.1 action相关结构体说明{定义在init.h中}

C++
#define COMMAND_RETRY_TIMEOUT 5
struct command//在action中处理command时会用到,核心是该结构体
{
struct listnode clist; //存放command链表的节点
int (func)(int nargs, char args); //处理command需要的回调函数
int line;
const char filename;
int nargs; //command参数个数
char args[1]; //具体的command参数
};
struct action {//解析action这个SECTION时,实际上就是解析init.rc,填充该结构体
struct listnode alist; //alist用于存储所有的action
struct listnode qlist; //qlist用于链接那些等待执行的action
struct listnode tlist; //tlist用于链接那些待某些条件满足后需要执行的action
unsigned hash;
const char name; //action的name
struct listnode commands;//存储的是commands链表的节点{通过next或者prev可以获得command}
struct command *current;//当前的command{一般是当前要执行的command}
};

2.2 service相关结构体说明{定义在init.h中}

C++
struct socketinfo { //service下的socket信息
struct socketinfo next; //节点信息
const char name; //socket名字
const char type; //socket类型
uid_t uid; //创建socket的进程uid
gid_t gid; //创建socket的进程gid
int perm; //socket权限信息{类似0666这种}
const char socketcon; //socket上下文{socket context}
};
struct svcenvinfo {
struct svcenvinfo next;//service的环境变量信息
const char name; //环境变量名字init.svc.{ServiceName}
const char value; //当前环境变量的值{"running","restarting"等等}
};
#define SVC_DISABLED 0x01 /不随class自动启动 /
#define SVC_ONESHOT 0x02 /退出后不需要重启,即只启动一次 /
#define SVC_RUNNING 0x04 /正在运行/
#define SVC_RESTARTING 0x08 /等待重启/
#define SVC_CONSOLE 0x10 /该service需要使用控制台 /
#define SVC_CRITICAL 0x20 / 如果在规定时间内连续重启,则系统会进入到recovery模式/
#define SVC_RESET 0x40 //Use when stopping a process, but not disabling,so it can be restarted with its class 
#define SVC_RC_DISABLED 0x80 // Remember if the disabled flag was set in the rc script
#define SVC_RESTART 0x100 // Use to safely restart (stop, wait, start) a service
#define SVC_DISABLED_START 0x200 //a start was requested but it was disabled at the time
#define NR_SVC_SUPP_GIDS 12 //一个service最多支持的用户组数量,目前是12
struct service {
struct listnode slist; //全局 service_list用来保存解析配置文件后得到的service
const char name; //service的名字
const char classname; //service所属的class名字,默认是default
unsigned flags; //service的属性标志位
pid_t pid; //进程号
time_t time_started; //上一次启动时间
time_t time_crashed; //上一次crash的时间
int nr_crashed; //死亡次数

uid_t uid; //用户ID
gid_t gid; //组ID
gid_t supp_gids[NR_SVC_SUPP_GIDS]; //存放支持用户组的数组
size_t nr_supp_gids; //当前用户组的个数,最大为12
char seclabel; //SELinux相关信息
struct socketinfo sockets; //有些service使用了socket,下面这个socketinfo用来描述socket的相关信息

struct svcenvinfo envvars; //service一般运行于单独的进程中,该变量描述创建进程时所需要的环境信息
struct action onrestart; //执行onrestart的action?

//keycodes相关内容{组合按键}
int keycodes; //组合按键具体的几个按键
int nkeycodes; //组合按键个数
int keychord_id; //组合按键在驱动中注册的id,驱动最后通过该id可以找到service
//IO优先级设置相关
int ioprio_class;
int ioprio_pri;
//参数相关
int nargs; //参数个数
char args[1]; //实际参数,动态数组用法
/* 'args' MUST be at the end of this struct! */
};

2.3 import相关结构体{定义在Init_parse.h中}

C++
struct import {//名为import的SECTION
struct listnode list; //import的链表节点
const char *filename; //import的文件名称
};

2.4 核心链表的定义{定义在Init_parse.h中}

C++
static list_declare(service_list); //存放service的链表
static list_declare(action_list); //存放所有action的链表
static list_declare(action_queue); //存放待执行action的链表

3 keyword相关使用说明

Keywords.h中定义了init中使用的关键字,分析如下:

C++
#ifndef KEYWORD
int do_chroot(int nargs, char args);
int do_chdir(int nargs, char args);
int do_class_start(int nargs, char args);
int do_class_stop(int nargs, char args);
...
#define __MAKE_KEYWORD_ENUM__
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,//第1次进来宏会被替换成关键字,而第2次执行时执行不到这里
enum {
K_UNKNOWN,
#endif //从这里往下,第1次执行时只是K_##symbol而已,第2次执行时才具有本身的含义
KEYWORD(chdir, COMMAND, 1, do_chdir)//即第1次等价于K_chdir,下面以此类推
KEYWORD(chroot, COMMAND, 1, do_chroot)
KEYWORD(class_start, COMMAND, 1, do_class_start)
KEYWORD(class_stop, COMMAND, 1, do_class_stop)
...
#ifdef __MAKE_KEYWORD_ENUM__//第2次执行不会到这里来
KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif

第1次包含时定义了各种回调函数和枚举标签,第2次包含时通过枚举标签被定义成keyword_info数组中的一部分
在Init_parse.c中被包含,使用方式如下所示:

C++
#define SECTION 0x01
#define COMMAND 0x02
#define OPTION 0x04
#include "keywords.h" //第1次包含,会得到一个枚举定义
//第2次,定义宏KEYWORD,这次四个参数全用上了
#define KEYWORD(symbol, flags, nargs, func) [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
struct {
const char name; //关键字的名称
int (func)(int nargs, char **args); //对应的处理函数
unsigned char nargs; //参数个数
unsigned char flags; //flag标识关键字的类型,包括COMMAND、OPTION、SECTION
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
#include "keywords.h"
};
#undef KEYWORD
#define kw_is(kw, type) (keyword_info[kw].flags & (type))
#define kw_name(kw) (keyword_info[kw].name)
#define kw_func(kw) (keyword_info[kw].func)
#define kw_nargs(kw) (keyword_info[kw].nargs)

keyword_info结构体说明:

存放的是keyword_info结构体类型的数据,每一项对应一个关键字

根据每一项的flags就可以判断出关键字的类型,

如新的一行是SECTION,就调用parse_new_section()来解析这一行

如新的一行不是一个SECTION的第一行,那么调用state.parseline()来解

说明:state.parseline所对应的函数会根据section类型的不同而不同,会在parse_new_section()中进行动态设置

4 配置文件解析流程
4.1 配置文件的解析从这里开始

C++
//init_parse_config_file->parse_config
static void parse_config(const char fn, char s)
{
...//变量初始化等操作
state.parse_line = parse_line_no_op;//空函数
/**
for循环中调用next_token不断从init.rc文件中获取token,这里的token是该编程语言的最小单位,不可再分。
例如,对于传统的编程语言的if、then等关键字、变量名等标识符都属于一个token。
而对于init.rc文件来说,import、on以及触发器的参数值都是属于一个token。
一个解析器要进行语法和词法的分析,词法分析就是在文件中找出一个个的token,
即词法分析器的返回值是token,而语法分析器的输入就是词法分析器的输出。
即语法分析器就需要分析一个个的token,而不是一个个的字符。
词法分析器就是next_token,而语法分析器就是T_NEWLINE分支中的代码。
*/
for (;;) {
switch (next_token(&state)) { //next_token函数相当于词法分析器
case T_EOF://xxx.rc文件分析完毕
state.parse_line(&state, 0, 0);
goto parser_done;
case T_NEWLINE://一行分析完毕
state.line++;
if (nargs) {//有效token个数不为0
int kw = lookup_keyword(args[0]);//获取第一个token,是关键词返回K_XXX,不是则返回K_UNKNOWN
if (kw_is(kw, SECTION)) {//判断该关键词是否是SECTION
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
} else {
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break;
case T_TEXT:
/*
注意:普通token,逐一读取init.rc文件的字符,并将由空格、/t分隔的字符串挑出来,
将有效字符串放入args[nargs]中
*/
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
}
parser_done:
list_for_each(node, &import_list) {?/如果有其他import的rc文件
struct import import = node_to_item(node, struct import, list);
int ret;
ret = init_parse_config_file(import->filename);//这里会递归调用解析其他的rc文件
...//容错处理
}
}

这里继续分析关键方法parse_new_section,实现如下:

C++
static void parse_new_section(struct parse_state state, int kw,int nargs, char *args)
{
switch(kw) {
case K_service://如果该SECTION是service
state->context = parse_service(state, nargs, args);
if (state->context) {//返回的service结构体有效
state->parse_line = parse_line_service;//重置state->parse_line
return;
}
break;
case K_on://如果该SECTION是action
state->context = parse_action(state, nargs, args);
if (state->context) {//返回的action结构体有效
state->parse_line = parse_line_action;//重置state->parse_line
return;
}
break;
case K_import://如果该SECTION是import
parse_import(state, nargs, args);//由于import section中只有一行所以没有对应的state.parseline。
break;
}
state->parse_line = parse_line_no_op;
}

从这里开始,主要从几个方面继续分析解析流程:

针对service关键点的解析:方法parse_service和parse_line_service

针对action 关键点的解析:方法parse_action 和parse_line_action

针对import 关键点的解析:方法parse_import

4.2 解析service到service_list的流程
4.2.1 parse_service的分析{初始化service,将其添加到init核心service_list中}

C++
static void parse_service(struct parse_state state, int nargs, char *args)
{
struct service svc;
...
svc = service_find_by_name(args[1]);//确保无同名service
if (svc) {//如果有则直接返回,不处理
parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
return 0;
}
//申请空间,初始化service结构体
nargs -= 2;
svc = calloc(1, sizeof(svc) + sizeof(char) * nargs);
svc->name = args[1];
svc->classname = "default";
memcpy(svc->args, args + 2, sizeof(char) nargs);
svc->args[nargs] = 0;
svc->nargs = nargs;
svc->onrestart.name = "onrestart";

list_init(&svc->onrestart.commands);//初始化onrestart.commands链表节点
list_add_tail(&service_list, &svc->slist);//将svc->slist节点添加到init的核心service_list中
return svc;//这里返回service
}

parse_line_service的分析{针对service下option的处理,option主要是对svc结构体成员变量的重新赋值/初始化}

C++
//说明:该部分代码做全角分析,剔除所有容错处理流程,仅分析核心逻辑
//对于遇到option解析的问题,该部分代码很具有参考价值
static void parse_line_service(struct parse_state state, int nargs, char args)
{
struct service svc = state->context;
struct command cmd;
int i, kw, kw_nargs;
svc->ioprio_class = IoSchedClass_NONE;
kw = lookup_keyword(args[0]);
switch (kw) {
case K_capability:
break;
case K_class:
svc->classname = args[1];
break;
case K_console:
svc->flags |= SVC_CONSOLE;
break;
case K_disabled:
svc->flags |= SVC_DISABLED;
svc->flags |= SVC_RC_DISABLED;
break;
case K_ioprio:
svc->ioprio_pri = strtoul(args[2], 0, 8);
if (!strcmp(args[1], "rt")) {
svc->ioprio_class = IoSchedClass_RT;
} else if (!strcmp(args[1], "be")) {
svc->ioprio_class = IoSchedClass_BE;
} else if (!strcmp(args[1], "idle")) {
svc->ioprio_class = IoSchedClass_IDLE;
}
break;
case K_group:
int n;
svc->gid = decode_uid(args[1]);
for (n = 2; n < nargs; n++) {
svc->supp_gids[n-2] = decode_uid(args[n]);
}
svc->nr_supp_gids = n - 2;
break;
case K_keycodes:
svc->keycodes = malloc((nargs - 1) sizeof(svc->keycodes[0]));
svc->nkeycodes = nargs - 1;
for (i = 1; i < nargs; i++) {
svc->keycodes[i - 1] = atoi(args[i]);
}
break;
case K_oneshot:
svc->flags |= SVC_ONESHOT;
break;
case K_onrestart://如果该服务重启,则会执行onrestart的commands链表中的command
nargs--;
args++;
kw = lookup_keyword(args[0]);
kw_is(kw, COMMAND)
kw_nargs = kw_nargs(kw);
cmd = malloc(sizeof(cmd) + sizeof(char) * nargs);
cmd->func = kw_func(kw);
cmd->nargs = nargs;
memcpy(cmd->args, args, sizeof(char) nargs);
list_add_tail(&svc->onrestart.commands, &cmd->clist);//这里的onrestart是一个action
break;
case K_critical:
svc->flags |= SVC_CRITICAL;
break;
case K_setenv: { //设置环境变量
struct svcenvinfo ei;
ei = calloc(1, sizeof(ei));
ei->name = args[1];
ei->value = args[2];
ei->next = svc->envvars;
svc->envvars = ei;
break;
}
case K_socket: {/* name type perm [ uid gid context ] */
struct socketinfo si;
si = calloc(1, sizeof(*si));
si->name = args[1];
si->type = args[2];
si->perm = strtoul(args[3], 0, 8);
if (nargs > 4)
si->uid = decode_uid(args[4]);
if (nargs > 5)
si->gid = decode_uid(args[5]);
if (nargs > 6)
si->socketcon = args[6];
si->next = svc->sockets;
svc->sockets = si;
break;
}
case K_user:
svc->uid = decode_uid(args[1]);
break;
case K_seclabel:
svc->seclabel = args[1];
break;
default:
}
}

4.3 解析action到action_list的流程
parse_action的分析{初始化action,将其添加到init核心action_list中}

C++
static void parse_action(struct parse_state state, int nargs, char *args)
{
struct action act;
...
act = calloc(1, sizeof(act));//申请空间,初始化action结构体
act->name = args[1];
list_init(&act->commands); //初始化act->commands链表节点
list_init(&act->qlist); //初始化act->qlist链表节点
list_add_tail(&action_list, &act->alist);//将act->alist节点添加到init的核心action_list中
/ XXX add to hash */
return act;//这里返回action
}

parse_line_action的分析{初始化command,并将command添加到当前解析action的commands链表中}

C++
static void parse_line_action(struct parse_state* state, int nargs, char args)
{
struct command cmd;
struct action act = state->context;//获取当前的action
int (func)(int nargs, char args);
int kw, n;
...
kw = lookup_keyword(args[0]);
if (!kw_is(kw, COMMAND)) {
parse_error(state, "invalid command '%s'\n", args[0]);
return;
}

n = kw_nargs(kw);
...
//command申请空间并初始化
cmd = malloc(sizeof(cmd) + sizeof(char) nargs);
cmd->func = kw_func(kw);
cmd->line = state->line;
cmd->filename = state->filename;
cmd->nargs = nargs;
memcpy(cmd->args, args, sizeof(char) nargs);
list_add_tail(&act->commands, &cmd->clist);//将cmd->clist节点添加到对应action的commands链表中
}

4.4 解析import关键字流程

C++
static void parse_import(struct parse_state state, int nargs, char *args)
{
...//初始化操作
import = calloc(1, sizeof(struct import));
import->filename = strdup(conf_file);//初始化import节点
list_add_tail(import_list, &import->list);//将import->list节点添加到import_list链表中
}

至此,整个解析的流程分析完毕,从init.rc的语法结构设计到keywords的代码架构,再到最后 解析文件的整个流程分析,把整个解析流程完整的走了一遍;为后面action队列初始化做了完善的准备。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表