该系列文章总纲链接:专题分纲目录 android 开机启动流程分析
说明:思维导图是基于之前文章不断迭代的,本章内容我们关注➕"解析init.rc" 部分即可
1 init.rc语法知识 1.1 AIL{Android Init Language}语言的严格规则说明:
/** 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文件,如下所示:
import init.test.rc #包含其他section, 在解析完init.rc文件后继续会调用init_parse_config_file来解析引入的.rc文件。1.2.2 on类型的section表示一系列命令的组合, 例如:
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如下所示:
commands command && command 含义command && command 含义exec <path> [ <argument> ]* 执行一个<path>指定的程序 exec <path> [ <argument> ]* 执行一个<path>指定的程序setprop <name> <value> 设置系统属性sysclktz <mins_west_of_gmt> 设置系统时钟export <name> <value> 设置一个全局变量chown <owner> <group> <path> 设置用户和组 setrlimit <resource> <cur> <max> 设置资源访问权限chmod <octal-mode> <path> 设置访问权限hostname <name> 设置主机名class_start <serviceclass> 启动类中的serviceifup <interface> 使网络接口<interface>连接 class_stop <serviceclass> 停止类中的service,不可以通过class_start来重新启动该类服务 import <filename> 引入其他的配置文件 domainname <name> 设置域名 symlink <target> <path> 创建一个动态链接 trigger <event>: 触发事件,是对 on <event>这类的事件进行触发 chdir <directory> 切换工作目录 write <path> <string> [ <string> ]* 向<path>路径的文件写入多个<string> chroot <directory> 设置根目录 loglevel <N> 设置level显示等级,0表示不显示 insmod <path> 安装模块mkdir <path> [mode] [owner] [group]
创建一个目录,并可以指定权限,用户和组
start <service> 开启服务 stop <service> 停止服务class_reset <serviceclass> 停止类中的service,并可以通过class_start来重新启动该类服务mount <type> <device> <dir> [ <mountoption> ]* 加载指定设备到目录下,其中<mountoption> 包括"ro", "rw", "remount", "noatime"1.3 service类型的section表示一个可执行程序,例如:
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的修饰词,主要包括以下几类:
OPTION关键词 对应含义critical: 表示如果服务在4分钟内存在多于4次,则系统重启到recovery modedisabled: 表示服务不会自动启动,需要手动调用名字启动setEnv <name> <value> 设置启动环境变量socket <name> <type> <permission> [<user> [<group>]]
开启一个unix域的socket,名字为/dev/socket/<name> ,<type>只能是dgram或者stream, <user>和<group>默认为0
user <username> 表示将用户切换为<username>,用户名已经定义好了,只能是system/rootgroup <groupname> 表示将组切换为<groupname> oneshot 表示这个service只启动一次 class <name> 指定一个要启动的类,这个类中如果有多个service,将会被同时启动。默认的class将会是“default”onrestart 在重启时执行一条命令 seclabel:SElinux 设置相关,设置安全等级关键问题:service类型的section标识了一个service(或者说可执行程序), 那这个service什么时候被执行? 是在class_start 这个命令被执行的时候,这个命令行总是存在于某个on类型的section中,“class_start core”这样一条命令被执行,就会启动类型为core的所有service。如:
on boot class_start core class_start main属性的设置方式如下:
通过在配置文件init.rc中通过命令setprop来设置通过程序来用property_set来设置可以看出android的启动过程主要就是on类型的section被执行的过程。1.4 关于property的说明 属性的判断on property:<name>=<value>,例如:
on property:vold.decrypt=trigger_encryption #表示当属性vold.decrypt的值为trigger_encryption时执行下面的命令 start surfaceflinger #开始执行service surfaceflinger start encrypt属性的设置方式如下:
通过在配置文件init.rc中通过命令setprop来设置 通过程序来用property_set来设置2 AIL相关的结构体说明
2.1 action相关结构体说明{定义在init.h中}
#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中}
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中}
struct import {//名为import的SECTION struct listnode list; //import的链表节点 const char *filename; //import的文件名称 };2.4 核心链表的定义{定义在Init_parse.h中}
static list_declare(service_list); //存放service的链表 static list_declare(action_list); //存放所有action的链表 static list_declare(action_queue); //存放待执行action的链表3 keyword相关使用说明
Keywords.h中定义了init中使用的关键字,分析如下:
#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中被包含,使用方式如下所示:
#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 配置文件的解析从这里开始
//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,实现如下:
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_import4.2 解析service到service_list的流程 4.2.1 parse_service的分析{初始化service,将其添加到init核心service_list中}
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结构体成员变量的重新赋值/初始化}
//说明:该部分代码做全角分析,剔除所有容错处理流程,仅分析核心逻辑 //对于遇到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中}
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链表中}
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关键字流程
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队列初始化做了完善的准备。