6.android 开机启动流程分析---init.rc解析流程

目录

本章关键点总结 & 说明:

1 init.rc语法知识

1.1 AIL{Android Init Language}语言的严格规则说明:

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

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

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

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

1.4 关于property的说明

2 AIL相关的结构体说明

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

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

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

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

3 keyword相关使用说明

4 配置文件解析流程

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

4.2 解析service到service_list的流程

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

4.3 解析action到action_list的流程

4.4 解析import关键字流程


 

本章关键点总结 & 说明:

说明:思维导图是基于之前文章不断迭代的,本章内容我们关注➕"解析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如下所示:
 

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的修饰词,主要包括以下几类:

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

on boot
    class_start core
    class_start main

属性的设置方式如下:

  1. 通过在配置文件init.rc中通过命令setprop来设置
  2. 通过程序来用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       

属性的设置方式如下:

  1. 通过在配置文件init.rc中通过命令setprop来设置
  2. 通过程序来用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结构体说明:

  1. 存放的是keyword_info结构体类型的数据,每一项对应一个关键字
  2. 根据每一项的flags就可以判断出关键字的类型,
  3. 如新的一行是SECTION,就调用parse_new_section()来解析这一行
  4. 如新的一行不是一个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;
}

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

  1. 针对service关键点的解析:方法parse_service和parse_line_service
  2. 针对action 关键点的解析:方法parse_action 和parse_line_action
  3. 针对import 关键点的解析:方法parse_import

4.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队列初始化做了完善的准备。

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值