init.rc 语法与解析

版权说明:本文为 开开向前冲 原创文章,转载请注明出处;
注:限于作者水平有限,文中有不对的地方还请指教。

本文基于Android 6.0,涉及源码如下:

/system/core/rootdir/init.rc
/system/core/init/init_parser.cpp

一:init.rc 语法

一个完整的init.rc 脚本由4中类型声明组成。即:
Action(动作)
Commands(命令)
Services(服务)
Options(选项)

Action 和Services 表明一个新语句的开始,这两个关键字后面跟着的Commands 或者 Options 都是属于这个语句;
Action 和Services 都有唯一的名字,如果出现动作或者服务重名,则会被当做错误处理。

1:Action(动作)

语法格式

on <trigger>   ##触发条件
     <command1>  ##执行命令
     <command2>  ##可以同时执行多个命令
     <command3>

其中trigger 是触发条件,即当触发条件满足时将 依次 执行commands

举例说明:下面这句话的意思是当属性值满足persist.service.adb.enable=1时,启动adbd 进程;
on property:persist.service.adb.enable=1
   start adbd

具体实现:当系统变化的时候,系统会对init.rc 中的每个<trigger>进行匹配,只要发现符合条件的Action,就会把它加入 “命令执行队列”的尾部(除非这个Action 在队列中已经存在), 然后系统在对这些Commands按顺序执行;

2:Commands(命令)

命令将在触发条件发生时被一个个执行。

init.rc中常见的trigger如下:

trigger Description
boot init程序启动后触发的第一个事件
<name>=<value> 当属性<name> 满足<value>时触发
device-added/removed-<patch> 当设备节点添加/删除时触发此事件
sevice-exited-<name> 当指定服务<name> 存在时触发

init.rc中常见的Commands如下:

Command Description
exec <path> [<argument>]* Fork 并执行一个程序,其路径为<path>。该命令将阻塞 直到该程序启动完成
export <name> <value> 设置某个环境变量<name> 的值为<value>。对全局有效,之后的进程都将继承这个变量
ifup <interface> 使网络接口<interface> 成功连接
import <filename> 解析另一个配置文件<filename>,以扩展当前配置
chdir <directory> 更换工作目录为<directory>
chmod <octal-mode> <path> 更改文件访问权限
chown <owner> <group> <path> 更改文件所有者和群组
mount <type> <device> <dir> [<mountoption>]* 尝试在指定路径上挂载一个设备
start <service> 启动一个服务,如果它没有处于运行状态的话
stop <service> 停止一个服务,如果它当前处于运行状态的话
setprop <name> <value> 设置系统属性<name> 的值为 <value>
trigger <event> 触发一个事件

很多没有列举完成,但是很多Commands 可以根据名字理解意思,如下举几个例子,例子都是init.rc中的内容;

on init
      symlink /sys/kernel/debug /d
      mount cgroup none /acct cpuacct
      mkdir /acct/uid
      chown root system /sys/fs/cgroup/memory/sw/tasks
      chmod 0660 /sys/fs/cgroup/memory/sw/tasks

on property:sys.boot_from_charger_mode=1
    class_stop charger
    trigger late-init
3:Services(服务)

3.1:命令格式:

service <name> <pathname> [ <argument> ]*
        <option>
        <option>
        ...
  • <name>——表示service 的名字;
  • <pathname>——表示service所在路径,此处的service是可执行文件,所以一定有存储路径;
  • <argument>——启动service所带的参数;
  • <option>——对此service的约束选项,后面将详细讲解;

Service 样例如下:

service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm
4:Options(选项)

init.rc 中可用的选项如下:

Option Description
critical 表明这是对设备至关重要的服务;如果它在四分钟内退出超过四次,则设备将进入Recovery 模式
disabled 表示此服务是不会自动启动,而是需要通过显示调用服务名来启动
setenv <name> <value> 设置环境变量<name> 为值<value>
socket <name> <type> <perm> [<user> [<group>] ] 创建一个名为dev/socket/<name>的 socket,然后将它的fd值传给启动它的进,有效的<type>值包括dgram,stream 和seqpacket。ueser 和group 的默认值为0。
user <username> 在启动服务前将用户切换至<username>,默认情况下用户都是root。
group <groupname> [<groupname>]* 在启动服务前将用户组切换至<groupname>
oneshot 当次服务退出时,不要主动去重启它
class <name> 为该服务指定一个class 名。同一个class 的所有服务必须同时启动或者停止。默认情况下服务的class名是“default”
onrestart 当次服务重启时,执行某些命令

二:init.rc解析

/system/core/init/init.cpp
/system/core/init/init_parser.cpp

1:init.rc脚本基本结构

init.rc 脚本的基本单位为section,即片段,section 分为三类,分别由三个关键字(所谓关键字即每一行的第一列)来区分,这三个关键字是on、service、import。

  • on ——即前文的action 的起始关键字;
    on 类型的section 表示一系列命令的组合;
 on init
    loglevel 3
    symlink /system/etc /etc
    symlink /sys/kernel/debug /d
    symlink /system/vendor /vendor
    mount cgroup none /acct cpuacct
    mkdir /acct/uid

这条命令包含上述的命令,命令的执行事以section 为单位执行的,所以这几条命令是一起执行的,而不会单独执行,那它们是在那里执行呢?——这是由init.cpp中的main()方法决定的,init.cpp在某个阶段会执行

action_for_each_trigger("init", action_add_queue_tail);

该方法就是将 on init 开始的section 里面的所有命令加入到一个执行队列,在未来的某个时间会顺序的执行,所以调用action_for_each_trigger的先后决定了命令执行的先后顺序;

  • service ——即前文所说的服务起始关键字;
    service类型的section代表了一个可执行程序;
service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm

服务的内容之前已经介绍过,可以看看前文,那service 类型的section标识的服务在什么时候调用呢?—— 是在class_start命令被执行的时候,class_start命令总是存在于on 类型的section中;

class_start core #这样一条命令代表将启动所有类型为core的service
所以可以看出android的启动过程主要就是on类型的section被执行的过程。

  • import —— import类型的section代表将引入另外一个.rc文件;

import /init.environ.rc
import /init.usb.rc

相当包含另外一些section, 在解析完init.rc文件后继续会调用init_parse_config_file来解析引入的.rc文件

2:解析过程

init.rc是一个纯文本文件,我们需要程序去解析init.rc,然后交给程序运行,解析init.rc的过程就是识别一个个section的过程,将各个section的信息保存下来,然后在init.c的main()中去执行一个个命令。

android采用双向链表来存储section的信息,解析完成之后,会得到三个双向链表action_list、service_list、import_list来分别存储三种section的信息上。

/system/core/init/init_parser.cpp
static list_declare(service_list);
static list_declare(action_list);
static list_declare(action_queue);

1:init.c中调用init_parse_config_file("/init.rc")

int init_parse_config_file(const char *fn)
{
    char *data;
    data = read_file(fn, 0);//读文件数据
    if (!data) return -1;

    parse_config(fn, data);//解析数据
    DUMP();
    return 0;
}

2:parse_config

static void parse_config(const char *fn, const std::string& data)
{
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];

    int nargs = 0;

    parse_state state;
    state.filename = fn;
    state.line = 0;
    state.ptr = strdup(data.c_str());  // TODO: fix this code!
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;

    list_init(&import_list);
    state.priv = &import_list;

    for (;;) {
        switch (next_token(&state)) { //next_token()根据从state.ptr开始遍历
        case T_EOF: //遍历到文件结尾,然后goto解析import的.rc文件
            state.parse_line(&state, 0, 0);
            goto parser_done;
        case T_NEWLINE: //到了一行结束
            state.line++;
            if (nargs) {
                int kw = lookup_keyword(args[0]); // 找到这一行的关键字
                if (kw_is(kw, SECTION)) { // 如果这是一个section的第一行
                    state.parse_line(&state, 0, 0);
                    parse_new_section(&state, kw, nargs, args);
                } else { //如果这不是一个section的第一行
                    state.parse_line(&state, nargs, args);
                }
                nargs = 0;
            }
            break;
        case T_TEXT: // 遇到普通字符
            if (nargs < INIT_PARSER_MAXARGS) {
                args[nargs++] = state.text;
            }
            break;
        }
    }
parser_done:
    list_for_each(node, &import_list) {
         struct import *import = node_to_item(node, struct import, list);
         int ret;

         ret = init_parse_config_file(import->filename);
         if (ret)
             ERROR("could not import file '%s' from '%s'\n",
                   import->filename, fn);
    }
}

next_token() 解析完init.rc中一行之后,会返回T_NEWLINE,这时调用lookup_keyword函数来找出这一行的关键字, lookup_keyword返回的是一个整型值,对应keyword_info[]数组的下标,keyword_info[]存放的是keyword_info结构体类型的数据,

struct {
   const char *name;                                    //关键字的名称
   int (*func)(int nargs, char **args);            //对应的处理函数
   unsigned char nargs;                                //参数个数
   unsigned char flags;                                 //flag标识关键字的类型,包括COMMAND、OPTION、SECTION
} keyword_info

keyword_info[]中存放的是所有关键字信息,每一项对应一个关键字;
根据每一项的flags就可以判断出关键字的类型,如新的一行是SECTION,就调用parse_new_section()来解析这一行, 如新的一行不是一个SECTION的第一行,那么调用state.parseline()来解析(state.parseline所对应的函数会根据section类型的不同而不同),在parse_new_section()中进行动态设置。

static void parse_new_section(struct parse_state *state, int kw,
                       int nargs, char **args)
{
    printf("[ %s %s ]\n", args[0],
           nargs > 1 ? args[1] : "");
    switch(kw) {
    case K_service: //解析service
        state->context = parse_service(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_service; //Service 对应的parse_line
            return;
        }
        break;
    case K_on://解析 on 开头的action
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_action; //action 对应的parse_line
            return;
        }
        break;
    case K_import: //解析import
        parse_import(state, nargs, args);
        break;
    }
    state->parse_line = parse_line_no_op;
}

三种类型的section: service、on、import, service对应的state.parse_line为parse_line_service,
on对应的state.parse_line为parse_line_action, import section中只有一行所以没有对应的state.parseline。

鉴于篇幅原因,使用文字描述parse_service过程,parse_action,parse_import解析也是类似的:

parse_service会去service_list链表查找有没有该命名的service(svc = service_find_by_name(args[1]);),如果没有则会构造service 对象并添加到service_list链表中,返回该service 对象;但是这个service 没有Options,然后通过parse_line_service 解析Options选项填充service 对象;

3:相关结构体

一个on类型的section对应一个action, action类型定义如下:

struct action {
        /* node in list of all actions */
    struct listnode alist;
        /* node in the queue of pending actions */
    struct listnode qlist;
        /* node in list of actions for a trigger */
    struct listnode tlist;

    unsigned hash;

        /* list of actions which triggers the commands*/
    struct listnode triggers;
    struct listnode commands; //command的双向链表
    struct command *current;
};

每个on类型section的第二行开始每一行都解析了一个command, 所有command组成一个双向链表指向该action的commands字段中。

command结构体定义如下:

struct command
{
        /* list of commands in an action */
    struct listnode clist;

    int (*func)(int nargs, char **args);

    int line;
    const char *filename;

    int nargs;
    char *args[1];
};

command结构体比较简单, 用于标识一个命令,包含双向链表指针、对应的执行函数、参数个数以及命令关键字。

service结构体定义如下:

struct service {
    void NotifyStateChange(const char* new_state);

        /* list of all services */
    struct listnode slist;

    char *name;
    const char *classname;

    unsigned flags;
    pid_t pid;
    time_t time_started;    /* time of last start */
    time_t time_crashed;    /* first crash within inspection window */
    int nr_crashed;         /* number of times crashed within window */

    uid_t uid;
    gid_t gid;
    gid_t supp_gids[NR_SVC_SUPP_GIDS];
    size_t nr_supp_gids;

    const char* seclabel;

    struct socketinfo *sockets;
    struct svcenvinfo *envvars;

    struct action onrestart;  /* Actions to execute on restart. */

    std::vector<std::string>* writepid_files_;

    /* keycodes for triggering this service via /dev/keychord */
    int *keycodes;
    int nkeycodes;
    int keychord_id;

    IoSchedClass ioprio_class;
    int ioprio_pri;

    int nargs;
    /* "MUST BE AT THE END OF THE STRUCT" */
    char *args[1];
}; 

service结构体存储了service的相关信息, 包括进程号、启动时间、名字等, onrestart这个option后面通常跟着一个命令,所以也用action结构体来表示。
例如:

service servicemanager /system/bin/servicemanager
    class core
    user system
    group system
    critical
    onrestart restart healthd
    onrestart restart zygote
    onrestart restart media
    onrestart restart surfaceflinger
    onrestart restart drm

init.rc中配置的service 启动流程:

/system/core/rootdir/init.rc
      class_start core
  on nonencrypted
      class_start main
      class_start late_start

/system/core/init/keywords.h
      KEYWORD(class_start, COMMAND, 1, do_class_start)

/system/core/init/builtins.c
  int do_class_start(int nargs, char **args)
  {
        /* Starting a class does not start services
         * which are explicitly disabled.  They must
         * be started individually.
         */
    service_for_each_class(args[1], service_start_if_not_disabled);
    return 0;
  }

/system/core/init/init_parser.cpp
  void service_for_each_class(const char *classname,
                            void (*func)(struct service *svc))
  {
      struct listnode *node;
      struct service *svc;
      list_for_each(node, &service_list) {
          svc = node_to_item(node, struct service, slist);
          if (!strcmp(svc->classname, classname)) {
              func(svc);
          }
      }
  }

/system/core/init/builtins.cpp
  static void service_start_if_not_disabled(struct service *svc)
  {
      if (!(svc->flags & SVC_DISABLED)) {
          service_start(svc, NULL);
      } else {
          svc->flags |= SVC_DISABLED_START;
      }
  }

/system/core/init/init.c
  void service_start(struct service *svc, const char *dynamic_args)
  {
      struct stat s;
      pid_t pid;
      int needs_console;
      int n;
      char *scon = NULL;
      int rc;

        /* starting a service removes it from the disabled or reset
         * state and immediately takes it out of the restarting
         * state if it was in there
         */
      ......
      pid = fork();//fork 子进程
      ......
      for (si = svc->sockets; si; si = si->next) {
            int socket_type = (
                    !strcmp(si->type, "stream") ? SOCK_STREAM :
                        (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));//判断socket 类型
            int s = create_socket(si->name, socket_type,
                                  si->perm, si->uid, si->gid, si->socketcon ?: scon);//创建socket对应的fd
            if (s >= 0) {
                publish_socket(si->name, s);//发布socket的fd,即将create_socket返回的fd添加到
                                            //以"ANDROID_SOCKET_"为前缀的环境变量;
                                            //Zygote的socket的key 为ANDROID_SOCKET_zygote,也是在这创建的;
            }
       }
      ......

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容