Android 4.4 Init进程分析三:init.rc脚本文件的解析

****************************************************************************

Android 4.4 init进程分析文章链接

  Android 4.4 Init进程分析一 :Android init进程概述

  Android 4.4 Init进程分析二 :Android初始化语言

  Android 4.4 Init进程分析三:init.rc脚本文件的解析

  Android 4.4 Init进程分析四 :init.rc脚本文件的执行

  Android 4.4 Init进程分析五 :进程的终止与再启动

  Android 4.4 Init进程分析六 :属性服务

***************************************************************************

 

1 概述

Android启动后,在Init进程中分析init.rc启动脚本文件,并根据相关文件中包含的内容,执行相应的功能。比如设置系统环境变量、记录待执行的进程。

此外,系统中还存在着init.${ro.hardware}.rc等文件,其作用类似于init.rc文件,init进程按照init.rc的处理方式处理init.${ro.hardware}.rc文件。

init.rc文件解析完后会得到action list 和 service list两个链表,记录需要执行的动作或服务。

 

接下来我们就通过源码来看具体的解析过程。

2 init.rc文件的解析过程

国际惯例,我们先看一下源码:

2.1 init main中开始

init是一个可执行档,所以启动时会先从main()函数开始执行,如下代码中开始对.rc文件的解析

http://androidxref.com/4.4_r1/xref/system/core/init/init.c#1039

1 init_parse_config_file("/init.rc");

在init进程的main()函数里,会调用init_parse_config_file()方法解析init.rc脚本,注意这里传递的参数是根目录下的 "/init.rc"文件路径。

 2.2 init_parse_config_file()函数

init_parse_config_file()方法定义如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#404

 1 int init_parse_config_file(const char *fn)
 2 {
 3     char *data;
 4     data = read_file(fn, 0); //读取/init.rc文件内容到内存,并返回起始地址存入data
 5     if (!data) return -1;
 6 
 7     parse_config(fn, data); //解析读入的字符内容
 8     DUMP();
 9     return 0;
10 }

read_file()方法的定义非常简单,作用就是把指定的文件以字符的形式读入到内存中,并返回起始地址及读入的数据大小。

其源码如下:

http://androidxref.com/4.4_r1/xref/system/core/init/util.c#142

 1 /* reads a file, making sure it is terminated with \n \0 */
 2 
 3 //该方法接收两个参数:
 4 // fn: 要读取的文件路径
 5 // _sz:unsigned int类型的指针,用于返回读入的字符个数
 6 // 该方法返回读取的字符内容的起始地址,如果读取失败则返回 0
 7 
 8 void *read_file(const char *fn, unsigned *_sz)
 9 {
10     char *data;
11     int sz;
12     int fd;
13     struct stat sb;
14 
15     data = 0;
16     fd = open(fn, O_RDONLY); //以只读的方式打开文件
17     if(fd < 0) return 0;
18 
19     // for security reasons, disallow world-writable
20     // or group-writable files
21     if (fstat(fd, &sb) < 0) {
22         ERROR("fstat failed for '%s'\n", fn);
23         goto oops;
24     }
25     if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
26         ERROR("skipping insecure file '%s'\n", fn);
27         goto oops;
28     }
29 
30     sz = lseek(fd, 0, SEEK_END); // 获取文件长度,有多少个字节
31     if(sz < 0) goto oops;
32 
33     if(lseek(fd, 0, SEEK_SET) != 0) goto oops; // 定位到文件开头
34 
35     data = (char*) malloc(sz + 2); //分配存储空间
36     if(data == 0) goto oops;
37 
38     if(read(fd, data, sz) != sz) goto oops; // 读取文件内容
39     close(fd); // 关闭文件
40     data[sz] = '\n'; // 设置结尾字符
41     data[sz+1] = 0;
42     if(_sz) *_sz = sz;
43     return data;
44 
45 oops:
46     close(fd);
47     if(data != 0) free(data);
48     return 0;
49 }
View Code

 

将文件的内容读入内存后,接下来就可以进行解析了,调用parse_config()方法来解析init.rc的内容。

2.3 parse_config()函数

parser_config函数接收已经读入的字符串数据开始解析

2.3.1 parse_state 结构体

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#347

在分析parse_config()方法前,我们先看一下parse_state 结构体的定义:

 1 struct parse_state
 2 {
 3     char *ptr; // 待解析的字符数组的地址
 4     char *text;
 5     int line;
 6     int nexttoken; 
 7     void *context;
 8     void (*parse_line)(struct parse_state *state, int nargs, char **args);// 解析后续行的解析函数,parse_line_service() or parse_line_action()
 9     const char *filename; // 待解析的文件名
10     void *priv;
11 };

结构体parse_state用于记录解析状态,包括:

> 待解析字符数组的起始地址:ptr
> 已解析的行数:line
> 待解析的文件的名字:filename
> 解析后续行的解析函数:parse_line
> 下一个标记的类型:nexttoken
> 解析出的string token的起始地址:text

 2.3.2 parse_config()方法

parse_config()方法截取部分关键代码如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#347

 1 static void parse_config(const char *fn, char *s)
 2 {
 3     struct parse_state state;
 4     struct listnode import_list;
 5     struct listnode *node;
 6     char *args[INIT_PARSER_MAXARGS];
 7     int nargs;
 8 
 9     nargs = 0;
10     state.filename = fn;
11     state.line = 0;
12     state.ptr = s;
13     state.nexttoken = 0;
14     state.parse_line = parse_line_no_op;
15 
16     list_init(&import_list);
17     state.priv = &import_list;
18 
19     for (;;) {
20         switch (next_token(&state)) {
21         case T_EOF: // 解析到文件尾,结束
22             state.parse_line(&state, 0, 0);
23             goto parser_done;
24         case T_NEWLINE: // 新的一行
25             state.line++;
26             if (nargs) {
27                 int kw = lookup_keyword(args[0]); // 判断每行首个单词的类型
28                 if (kw_is(kw, SECTION)) { 
29                     state.parse_line(&state, 0, 0);
30                     parse_new_section(&state, kw, nargs, args);
31                 } else {
32                     state.parse_line(&state, nargs, args);
33                 }
34                 nargs = 0;
35             }
36             break;
37         case T_TEXT: // 一行中分割出新的 单词(string)
38             if (nargs < INIT_PARSER_MAXARGS) {
39                 args[nargs++] = state.text;
40             }
41             break;
42         }
43     }
44 
45 parser_done:
46     list_for_each(node, &import_list) {
47          struct import *import = node_to_item(node, struct import, list);
48          int ret;
49 
50          INFO("importing '%s'", import->filename);
51          ret = init_parse_config_file(import->filename); // 继续解析import进来的其他.rc文件
52          if (ret)
53              ERROR("could not import file '%s' from '%s'\n",
54                    import->filename, fn);
55     }
56 }
View Code

 几点说明:

  该函数逐行分析init.rc脚本,判断每一行的第一个参数是什么类型的。
  如果是action或service类型的,就表示要创建一个新的section节点了
  如果是section节点,此时它会设置一下解析后续行的解析函数,也就是给state->parse_line赋值。
  针对service类型,解析后续行的函数是parse_line_service()。
  针对action类型,解析后续行的函数则是parse_line_action()。

 2.3.3 next_token()函数

首先next_token()函数是以行为单位分割参数传递过来的字符串,而后调用lookup_keyword()函数,next_token()代码如下:

http://androidxref.com/4.4_r1/xref/system/core/init/parser.c#68

  1 int next_token(struct parse_state *state)
  2 {
  3     char *x = state->ptr; // 字符数组起始地址
  4     char *s;
  5 
  6     if (state->nexttoken) {
  7         int t = state->nexttoken;
  8         state->nexttoken = 0;
  9         return t;
 10     }
 11 
 12     for (;;) {
 13         switch (*x) {
 14         case 0:    // 0: 表示解析到了字符数组尾(文件尾),返回EOF标记
 15             state->ptr = x;
 16             return T_EOF;
 17         case '\n': // '\n': 表示遇到新的一行
 18             x++;
 19             state->ptr = x;
 20             return T_NEWLINE;
 21         case ' ':
 22         case '\t':
 23         case '\r':
 24             x++; //' '、'\t'、'\r': 表示该行未结束,继续解析下一个字符
 25             continue;
 26         case '#': // '#': 表示遇到注释行,继续查找新的一行
 27             while (*x && (*x != '\n')) x++;
 28             if (*x == '\n') {
 29                 state->ptr = x+1;
 30                 return T_NEWLINE;
 31             } else {
 32                 state->ptr = x;
 33                 return T_EOF;
 34             }
 35         default: // 其他字符: 跳转到text标记,
 36             goto text;
 37         }
 38     }
 39 
 40 textdone:  // 解析到一个完整的string token,可以看多一个单词
 41     state->ptr = x;
 42     *s = 0;
 43     return T_TEXT;
 44 text:
 45     state->text = s = x; // 记录这个text的起始地址
 46 textresume:
 47     for (;;) {
 48         switch (*x) {
 49         case 0:
 50             goto textdone;
 51         case ' ':
 52         case '\t':
 53         case '\r':
 54             x++;
 55             goto textdone;
 56         case '\n':
 57             state->nexttoken = T_NEWLINE;
 58             x++;
 59             goto textdone;
 60         case '"': // 双引号内的字符作为一个string token
 61             x++;
 62             for (;;) {
 63                 switch (*x) {
 64                 case 0:
 65                         /* unterminated quoted thing */
 66                     state->ptr = x;
 67                     return T_EOF;
 68                 case '"':
 69                     x++;
 70                     goto textresume;
 71                 default:
 72                     *s++ = *x++;
 73                 }
 74             }
 75             break;
 76         case '\\':
 77             x++;
 78             switch (*x) {
 79             case 0:
 80                 goto textdone;
 81             case 'n':
 82                 *s++ = '\n';
 83                 break;
 84             case 'r':
 85                 *s++ = '\r';
 86                 break;
 87             case 't':
 88                 *s++ = '\t';
 89                 break;
 90             case '\\':
 91                 *s++ = '\\';
 92                 break;
 93             case '\r':
 94                     /* \ <cr> <lf> -> line continuation */
 95                 if (x[1] != '\n') {
 96                     x++;
 97                     continue;
 98                 }
 99             case '\n':
100                     /* \ <lf> -> line continuation */
101                 state->line++;
102                 x++;
103                     /* eat any extra whitespace */
104                 while((*x == ' ') || (*x == '\t')) x++;
105                 continue;
106             default:
107                     /* unknown escape -- just copy */
108                 *s++ = *x++;
109             }
110             continue;
111         default:
112             *s++ = *x++;
113         }
114     }
115     return T_EOF;
116 }
View Code

分析:

// 第一个for循环的作用主要是查找新的一行或到达文件尾或一个单词的起始

// 第二个for循环主要是对一行中的字符串以空格、'\r'、'\t'进行分割

例如:init.rc文件中 “service console /system/bin/sh” 这样一行字符串就会被分割为三个部分:

第一部分:“service”

第二部分:“console”

第三部分:“/system/bin/sh”

这三部分被存储在 parse_config()方法中定义的 args 字符数组中,同时nargs=3,即该行解析出3个单词:

1 args[0] = "service"
2 args[1] = "console"
3 args[2] = "/system/bin/sh"
4 nargs = 3

 

经过上面next_token()的解析,我们已经找到了一行并且分割出了若干个单词,存到了args中,接下下就是调用lookup_keyword(args[0])来判断每行首个单词的类型了。

2.3.4 lookup_keyword()函数

先看lookup_keyword()函数的源码:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#79

 1 int lookup_keyword(const char *s)
 2 {
 3     switch (*s++) {
 4     case 'c':
 5     if (!strcmp(s, "opy")) return K_copy;
 6         if (!strcmp(s, "apability")) return K_capability;
 7         if (!strcmp(s, "hdir")) return K_chdir;
 8         if (!strcmp(s, "hroot")) return K_chroot;
 9         if (!strcmp(s, "lass")) return K_class;
10         if (!strcmp(s, "lass_start")) return K_class_start;
11         if (!strcmp(s, "lass_stop")) return K_class_stop;
12         if (!strcmp(s, "lass_reset")) return K_class_reset;
13         if (!strcmp(s, "onsole")) return K_console;
14         if (!strcmp(s, "hown")) return K_chown;
15         if (!strcmp(s, "hmod")) return K_chmod;
16         if (!strcmp(s, "ritical")) return K_critical;
17         break;
18     case 'd':
19         if (!strcmp(s, "isabled")) return K_disabled;
20         if (!strcmp(s, "omainname")) return K_domainname;
21         break;
22     case 'e':
23         if (!strcmp(s, "xec")) return K_exec;
24         if (!strcmp(s, "xport")) return K_export;
25         break;
26     case 'g':
27         if (!strcmp(s, "roup")) return K_group;
28         break;
29     case 'h':
30         if (!strcmp(s, "ostname")) return K_hostname;
31         break;
32     case 'i':
33         if (!strcmp(s, "oprio")) return K_ioprio;
34         if (!strcmp(s, "fup")) return K_ifup;
35         if (!strcmp(s, "nsmod")) return K_insmod;
36         if (!strcmp(s, "mport")) return K_import;
37         break;
38     case 'k':
39         if (!strcmp(s, "eycodes")) return K_keycodes;
40         break;
41     case 'l':
42         if (!strcmp(s, "oglevel")) return K_loglevel;
43         if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;
44         break;
45     case 'm':
46         if (!strcmp(s, "kdir")) return K_mkdir;
47         if (!strcmp(s, "ount_all")) return K_mount_all;
48         if (!strcmp(s, "ount")) return K_mount;
49         break;
50     case 'o':
51         if (!strcmp(s, "n")) return K_on;
52         if (!strcmp(s, "neshot")) return K_oneshot;
53         if (!strcmp(s, "nrestart")) return K_onrestart;
54         break;
55     case 'p':
56         if (!strcmp(s, "owerctl")) return K_powerctl;
57     case 'r':
58         if (!strcmp(s, "estart")) return K_restart;
59         if (!strcmp(s, "estorecon")) return K_restorecon;
60         if (!strcmp(s, "mdir")) return K_rmdir;
61         if (!strcmp(s, "m")) return K_rm;
62         break;
63     case 's':
64         if (!strcmp(s, "eclabel")) return K_seclabel;
65         if (!strcmp(s, "ervice")) return K_service;
66         if (!strcmp(s, "etcon")) return K_setcon;
67         if (!strcmp(s, "etenforce")) return K_setenforce;
68         if (!strcmp(s, "etenv")) return K_setenv;
69         if (!strcmp(s, "etkey")) return K_setkey;
70         if (!strcmp(s, "etprop")) return K_setprop;
71         if (!strcmp(s, "etrlimit")) return K_setrlimit;
72         if (!strcmp(s, "etsebool")) return K_setsebool;
73         if (!strcmp(s, "ocket")) return K_socket;
74         if (!strcmp(s, "tart")) return K_start;
75         if (!strcmp(s, "top")) return K_stop;
76         if (!strcmp(s, "wapon_all")) return K_swapon_all;
77         if (!strcmp(s, "ymlink")) return K_symlink;
78         if (!strcmp(s, "ysclktz")) return K_sysclktz;
79         break;
80     case 't':
81         if (!strcmp(s, "rigger")) return K_trigger;
82         break;
83     case 'u':
84         if (!strcmp(s, "ser")) return K_user;
85         break;
86     case 'w':
87         if (!strcmp(s, "rite")) return K_write;
88         if (!strcmp(s, "ait")) return K_wait;
89         break;
90     }
91     return K_UNKNOWN;
92 }
View Code

lookup_keyword()函数代码非常简单:

1. 接收一个字符指针,也就是每行的首个单词的地址;

2. switch/case语句判断类型并返回;

 

lookup_keyword()返回值K_copy、K_capability值,其实就是表项的索引号,本质就是对应到keyword_info结构体数组中的数据编号(index)。

keyword_info结构体数组及K_copy等常量的定义如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#58

 1 #include "keywords.h"
 2 
 3 #define KEYWORD(symbol, flags, nargs, func) \
 4     [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
 5 
 6 struct {
 7     const char *name;
 8     int (*func)(int nargs, char **args);
 9     unsigned char nargs;
10     unsigned char flags;
11 } keyword_info[KEYWORD_COUNT] = {
12     [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
13 #include "keywords.h"
14 };
15 #undef KEYWORD
View Code

这里用到了一点儿宏定义的小技巧,两次include了keywords.h头文件,

第一次#include "keywords.h"

其实keywords.h中会先定义一次KEYWORD宏,其主要目的是为了形成一个顺序排列的enum,而后就#undef KEYWORD了。

展开后代码如下:

  1 int do_chroot(int nargs, char **args);
  2 int do_chdir(int nargs, char **args);
  3 int do_class_start(int nargs, char **args);
  4 int do_class_stop(int nargs, char **args);
  5 int do_class_reset(int nargs, char **args);
  6 int do_domainname(int nargs, char **args);
  7 int do_exec(int nargs, char **args);
  8 int do_export(int nargs, char **args);
  9 int do_hostname(int nargs, char **args);
 10 int do_ifup(int nargs, char **args);
 11 int do_insmod(int nargs, char **args);
 12 int do_mkdir(int nargs, char **args);
 13 int do_mount_all(int nargs, char **args);
 14 int do_mount(int nargs, char **args);
 15 int do_powerctl(int nargs, char **args);
 16 int do_restart(int nargs, char **args);
 17 int do_restorecon(int nargs, char **args);
 18 int do_rm(int nargs, char **args);
 19 int do_rmdir(int nargs, char **args);
 20 int do_setcon(int nargs, char **args);
 21 int do_setenforce(int nargs, char **args);
 22 int do_setkey(int nargs, char **args);
 23 int do_setprop(int nargs, char **args);
 24 int do_setrlimit(int nargs, char **args);
 25 int do_setsebool(int nargs, char **args);
 26 int do_start(int nargs, char **args);
 27 int do_stop(int nargs, char **args);
 28 int do_swapon_all(int nargs, char **args);
 29 int do_trigger(int nargs, char **args);
 30 int do_symlink(int nargs, char **args);
 31 int do_sysclktz(int nargs, char **args);
 32 int do_write(int nargs, char **args);
 33 int do_copy(int nargs, char **args);
 34 int do_chown(int nargs, char **args);
 35 int do_chmod(int nargs, char **args);
 36 int do_loglevel(int nargs, char **args);
 37 int do_load_persist_props(int nargs, char **args);
 38 int do_wait(int nargs, char **args);
 39 #define __MAKE_KEYWORD_ENUM__
 40 #define KEYWORD(symbol, flags, nargs, func) K_##symbol,
 41 enum {
 42     K_UNKNOWN,
 43     K_capability,
 44     K_chdir,
 45     K_chroot,
 46     K_class,
 47     K_class_start,
 48     K_class_stop,
 49     K_class_reset,
 50     K_console,
 51     K_critical,
 52     K_disabled,
 53     K_domainname,
 54     K_exec,
 55     K_export,
 56     K_group,
 57     K_hostname,
 58     K_ifup,
 59     K_insmod,
 60     K_import,
 61     K_keycodes,
 62     K_mkdir,
 63     K_mount_all,
 64     K_mount,
 65     K_on,
 66     K_oneshot,
 67     K_onrestart,
 68     K_powerctl,
 69     K_restart,
 70     K_restorecon,
 71     K_rm,
 72     K_rmdir,
 73     K_seclabel,
 74     K_service,
 75     K_setcon,
 76     K_setenforce,
 77     K_setenv,
 78     K_setkey,
 79     K_setprop,
 80     K_setrlimit,
 81     K_setsebool,
 82     K_socket,
 83     K_start,
 84     K_stop,
 85     K_swapon_all,
 86     K_trigger,
 87     K_symlink,
 88     K_sysclktz,
 89     K_user,
 90     K_wait,
 91     K_write,
 92     K_copy,
 93     K_chown,
 94     K_chmod,
 95     K_loglevel,
 96     K_load_persist_props,
 97     K_ioprio,
 98     KEYWORD_COUNT,
 99 };
100 #undef __MAKE_KEYWORD_ENUM__
101 #undef KEYWORD
View Code

 

第二次#include "keywords.h"

接着上面代码中再次定义了KEYWORD宏,这次的主要目的是为了形成一个struct数组,即keyword_info数组。

展开后代码如下:

 1 #define KEYWORD("symbol, flags, nargs, func) \
 2     [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
 3 
 4 struct {
 5     const char *name;
 6     int (*func)(int nargs, char **args);
 7     unsigned char nargs;
 8     unsigned char flags;
 9 } keyword_info[KEYWORD_COUNT] = {
10     [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
11     [ K_capability ] = { "capability", 0,  0,  OPTION) },
12     [ K_chdir ] = { "chdir", do_chdir, 1,       COMMAND) },
13     [ K_chroot ] = { "chroot", do_chroot, 1,      COMMAND) },
14     [ K_class ] = { "class", 0,  0,       OPTION) },
15     [ K_class_start ] = { "class_start", do_class_start, 1, COMMAND) },
16     [ K_class_stop ] = { "class_stop", do_class_stop, 1,  COMMAND) },
17     [ K_class_reset ] = { "class_reset", do_class_reset, 1, COMMAND) },
18     [ K_console ] = { "console", 0,  0,     OPTION) },
19     [ K_critical ] = { "critical", 0,  0,    OPTION) },
20     [ K_disabled ] = { "disabled", 0,  0,    OPTION) },
21     [ K_domainname ] = { "domainname", do_domainname, 1,  COMMAND) },
22     [ K_exec ] = { "exec", do_exec, 1,        COMMAND) },
23     [ K_export ] = { "export", do_export, 2,      COMMAND) },
24     [ K_group ] = { "group",  0, 0,       OPTION) },
25     [ K_hostname ] = { "hostname", 1, do_hostname,    COMMAND) },
26     [ K_ifup ] = { "ifup", do_ifup, 1,        COMMAND) },
27     [ K_insmod ] = { "insmod", do_insmod, 1,      COMMAND) },
28     [ K_import ] = { "import", 0, 1,      SECTION) },
29     [ K_keycodes ] = { "keycodes",  0, 0,    OPTION) },
30     [ K_mkdir ] = { "mkdir", do_mkdir, 1,       COMMAND) },
31     [ K_mount_all ] = { "mount_all", do_mount_all, 1,   COMMAND) },
32     [ K_mount ] = { "mount", do_mount, 3,       COMMAND) },
33     [ K_on ] = { "on", 0, 0,          SECTION) },
34     [ K_oneshot ] = { "oneshot",  0, 0,     OPTION) },
35     [ K_onrestart ] = { "onrestart",  0, 0,   OPTION) },
36     [ K_powerctl ] = { "powerctl", do_powerctl, 1,    COMMAND) },
37     [ K_restart ] = { "restart", do_restart, 1,     COMMAND) },
38     [ K_restorecon ] = { "restorecon", do_restorecon, 1,  COMMAND) },
39     [ K_rm ] = { "rm", do_rm, 1,          COMMAND) },
40     [ K_rmdir ] = { "rmdir", do_rmdir, 1,       COMMAND) },
41     [ K_seclabel ] = { "seclabel",  0, 0,    OPTION) },
42     [ K_service ] = { "service", 0, 0,     SECTION) },
43     [ K_setcon ] = { "setcon", do_setcon, 1,      COMMAND) },
44     [ K_setenforce ] = { "setenforce", do_setenforce, 1,  COMMAND) },
45     [ K_setenv ] = { "setenv", 0,  2,      OPTION) },
46     [ K_setkey ] = { "setkey", do_setkey, 0,      COMMAND) },
47     [ K_setprop ] = { "setprop", do_setprop, 2,     COMMAND) },
48     [ K_setrlimit ] = { "setrlimit", do_setrlimit, 3,   COMMAND) },
49     [ K_setsebool ] = { "setsebool", do_setsebool, 2,   COMMAND) },
50     [ K_socket ] = { "socket",  0, 0,      OPTION) },
51     [ K_start ] = { "start", do_start, 1,       COMMAND) },
52     [ K_stop ] = { "stop", do_stop, 1,        COMMAND) },
53     [ K_swapon_all ] = { "swapon_all", do_swapon_all, 1,  COMMAND) },
54     [ K_trigger ] = { "trigger", do_trigger, 1,     COMMAND) },
55     [ K_symlink ] = { "symlink", do_symlink, 1,     COMMAND) },
56     [ K_sysclktz ] = { "sysclktz", do_sysclktz, 1,    COMMAND) },
57     [ K_user ] = { "user",  0, 0,        OPTION) },
58     [ K_wait ] = { "wait", do_wait, 1,        COMMAND) },
59     [ K_write ] = { "write", do_write, 2,       COMMAND) },
60     [ K_copy ] = { "copy", do_copy, 2,        COMMAND) },
61     [ K_chown ] = { "chown", do_chown, 2,       COMMAND) },
62     [ K_chmod ] = { "chmod", do_chmod, 2,       COMMAND) },
63     [ K_loglevel ] = { "loglevel", do_loglevel, 1,    COMMAND) },
64     [ K_load_persist_props ] = { "load_persist_props", do_load_persist_props, 0,    COMMAND) },
65     [ K_ioprio ] = { "ioprio",  0, 0,      OPTION) },
66 
67 };
68 #undef KEYWORD
View Code

注意:keyword_info结构体数组中只有3个元素的flags是SECTION :

  [ K_import ] = { "import", 0, 1,      SECTION) },

  [ K_on ] = { "on", 0, 0,          SECTION) },

  [ K_service ] = { "service", 0, 0,     SECTION) },

 

 2.3.5 解析section小节

kw_is(kw, SECTION)判断该行是一个sectio小节时,即分析出某句脚本是以on或者service或者import开始,就说明一个新的小节要开始了。此时,会调用到parse_new_section(),该函数的代码如下:
http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#parse_new_section

 1 void parse_new_section(struct parse_state *state, int kw,
 2                        int nargs, char **args)
 3 {
 4     printf("[ %s %s ]\n", args[0],
 5            nargs > 1 ? args[1] : "");
 6     switch(kw) {
 7     case K_service:
 8         state->context = parse_service(state, nargs, args);
 9         if (state->context) {
10             state->parse_line = parse_line_service;
11             return;
12         }
13         break;
14     case K_on:
15         state->context = parse_action(state, nargs, args);
16         if (state->context) {
17             state->parse_line = parse_line_action;
18             return;
19         }
20         break;
21     case K_import:
22         parse_import(state, nargs, args);
23         break;
24     }
25     state->parse_line = parse_line_no_op;
26 }
View Code

代码很清晰,解析的小节就是那三类:
    action小节(以on开头的)
    service小节(以service)
    import小节(以import开头)

最核心的部分当然是service小节和action小节,具体解析的地方在上面代码中的parse_service()和parse_action()函数里。
至于import小节,parse_import()函数只是把脚本中的所有import语句先汇总成一个链表,记入state结构中,待回到parse_config()后再做处理。

2.3.6 解析service section

parse_service()的代码如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#613

 1 static void *parse_service(struct parse_state *state, int nargs, char **args)
 2 {
 3     struct service *svc;
 4     
 5     ....
 6     
 7     nargs -= 2;
 8     svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs); // 申请一个service节点
 9     if (!svc) {
10         parse_error(state, "out of memory\n");
11         return 0;
12     }
13     svc->name = args[1]; // 填入service name
14     svc->classname = "default"; // 填入service class
15     memcpy(svc->args, args + 2, sizeof(char*) * nargs);
16     svc->args[nargs] = 0;
17     svc->nargs = nargs;
18     svc->onrestart.name = "onrestart";
19     list_init(&svc->onrestart.commands);
20     list_add_tail(&service_list, &svc->slist); //加入service_list
21     
22     return svc;
23 }
View Code

解析service section时,首先会调用calloc()申请一个service节点,然后填入name /  class等信息,并加入到service_list链表。

parse_service()函数的返回值是新建立的service节点的指针,该指针会存在state->context中(即parse_config函数中的struct parse_state state;),再解析这个service section的后续option行时可以把信息存入对应的节点。

注意,此时该service节点的onrestart.commands部分还是个空链表,因为我们还没有分析该service的后续脚本行。

 

 

parse_new_section()中为service明确指定了解析后续行的函数parse_line_service()。

如此,在parse_config()函数的for循环中,再解析新的一行时,就会调用state.parse_line(&state, nargs, args);来解析,
并将解析的信息存入 state->context 指向的section 节点中

parse_config()函数部分代码:

 1 static void parse_config(const char *fn, char *s)
 2 {
 3     for (;;) {
 4         switch (next_token(&state)) {
 5 
 6         case T_NEWLINE:
 7             state.line++;
 8             if (nargs) {
 9                 int kw = lookup_keyword(args[0]);
10                 if (kw_is(kw, SECTION)) {
11                     // 解析到一个section开始
12                     state.parse_line(&state, 0, 0);
13                     parse_new_section(&state, kw, nargs, args);
14                 } else {
15                     // 解析一个service section中的option信息 or action section的command信息
16                     state.parse_line(&state, nargs, args); 
17                 }
18                 nargs = 0;
19             }
20             break;
21         }
22     }
23 }
View Code

 

parse_line_service()函数的截选代码如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#parse_line_service

 1 static void parse_line_service(struct parse_state *state, int nargs, char **args)
 2 {
 3     struct service *svc = state->context; // 指向service节点的指针
 4     struct command *cmd;
 5     . . . . . .
 6     kw = lookup_keyword(args[0]);   // 解析具体的service option也是要查关键字表的
 7     switch (kw) {
 8     case K_capability:
 9         break;
10     case K_class:
11         if (nargs != 2) {
12             parse_error(state, "class option requires a classname\n");
13         } else {
14             svc->classname = args[1]; // 填入信息
15         }
16         break;
17     case K_console:
18         svc->flags |= SVC_CONSOLE; // 填入信息
19         break;
20 case K_disabled:
21 . . . . . .
22 . . . . . .
View Code

 

service的各个option会影响service节点的不同域,比如flags域、classname域、onrestart域等等。

其中onrestart域的解析稍有不同,因为onrestart本身是个action节点,会带有多个command。

service section中常见的options有:
1) K_capability
2) K_class   --  服务所属的类,当一个类启动或退出时,其所包含的所有服务可以一同启动或退出。若为指定该option,服务默认属于“default”类
3) K_console
4) K_disabled  --  init进程启动的所有进程被包含在名称为“类”的运行组中。进程所属的类被启动时,若指定进程为disable,该进程将不会被执行,只有按照名称明确指定后才可以启动。
5) K_ioprio
6) K_group   --  执行服务前改变组,默认组为 root
7) K_user  --  执行服务前改变用户名,若为设定,则默认为root
8) K_keycodes  -- 
9) K_oneshot  -- 服务退出后不再重启
10) K_onrestart   --  当服务重启时执行一个命令
11) K_critical
12) K_setenv
13) K_socket   --  创建socket
14) K_seclabel

在service小节解析完毕后,我们应该能得到类似下图这样的service节点

 

 

 

 

 

2.3.7 解析action section

解析action小节与上述service的解析过程类似,代码也很简单。

解析时会调用parse_action()函数,该函数中会用calloc()申请一个action节点,然后填入action名等信息,再加入action_list总表中。
当然,此时action的commands部分也是空的,等待后续解析
parse_action()函数的代码如下:
http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#821

 1 static void *parse_action(struct parse_state *state, int nargs, char **args)
 2 {
 3     struct action *act;
 4     if (nargs < 2) {
 5         parse_error(state, "actions must have a trigger\n");
 6         return 0;
 7     }
 8     if (nargs > 2) {
 9         parse_error(state, "actions may not have extra parameters\n");
10         return 0;
11     }
12     act = calloc(1, sizeof(*act));
13     act->name = args[1];
14     list_init(&act->commands);
15     list_init(&act->qlist);
16     list_add_tail(&action_list, &act->alist);
17         /* XXX add to hash */
18     return act;
19 }
View Code

对于action小节而言,我们指定了不同的解析后续行的函数,也就是parse_line_action()。该函数的代码截选如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#841

 1 static void parse_line_action(struct parse_state* state, int nargs, char **args)
 2 {
 3     struct command *cmd;
 4     struct action *act = state->context;
 5     . . . . . .
 6     kw = lookup_keyword(args[0]);   // 解析具体的action command也是要查关键字表的
 7     if (!kw_is(kw, COMMAND)) {
 8         parse_error(state, "invalid command '%s'\n", args[0]);
 9         return;
10     }
11  
12     n = kw_nargs(kw);
13     if (nargs < n) {
14         parse_error(state, "%s requires %d %s\n", args[0], n - 1,
15             n > 2 ? "arguments" : "argument");
16         return;
17     }
18     cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
19     cmd->func = kw_func(kw);
20     cmd->nargs = nargs;
21     memcpy(cmd->args, args, sizeof(char*) * nargs);
22     list_add_tail(&act->commands, &cmd->clist);
23 }
View Code

既然action的后续行可以包含多条command,那么parse_line_action()就必须先确定出当前分析的是什么command,这一点和parse_line_service()是一致的,都是通过调用lookup_keyword()来查询关键字的。
另外,command子行的所有参数经过next_token()分割,其实已被记入传进来的args参数,现在这些参数会记入command节点的args域中,而且这个command节点会链入action节点的commands链表尾部。

在action小节解析完毕后,我们应该能得到类似下图这样的action节点

 

 

2.3.8 解析import section

 解析import section的代码非常简单,就是将import进来的.rc文件名加入到import list中,以便后续进行解析。

parse_import()函数的代码如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#295

 1 void parse_import(struct parse_state *state, int nargs, char **args)
 2 {
 3     struct listnode *import_list = state->priv;
 4     struct import *import;
 5     char conf_file[PATH_MAX];
 6     int ret;
 7 
 8     if (nargs != 2) {
 9         ERROR("single argument needed for import\n");
10         return;
11     }
12 
13     ret = expand_props(conf_file, args[1], sizeof(conf_file));
14     if (ret) {
15         ERROR("error while handling import on line '%d' in '%s'\n",
16               state->line, state->filename);
17         return;
18     }
19 
20     import = calloc(1, sizeof(struct import));
21     import->filename = strdup(conf_file);
22     list_add_tail(import_list, &import->list);
23     INFO("found import '%s', adding to import list", import->filename);
24 }
View Code

首先调用expand_props()函数获取import进来的.rc文件的扩展名,也就是去解析这样的语句中的file name: import /init.${ro.hardware}.rc,需要根据property name值来得到实际的文件名

然后调用calloc创建一个import节点,并填入filename信息

最后加入到import_list链表中等待后续使用

在import小节解析完毕后,我们应该能得到类似下图这样的import节点

 

 2.3.9 解析完毕

 至此,当init.rc文件解析完成后,我们得到了三个list链表:

首先要清楚,在/system/core/init/init_parser.c中定义:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#39

1 static list_declare(service_list);
2 static list_declare(action_list);
3 static list_declare(action_queue);
View Code

service_list 及 action_list均是全局static变量,也就是链表的头节点。

另外在parse_config()函数中还定义了局部变量import_list (http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#350),import进来的.rc文件最终也是会被解析并将action and service加入service_list or action_list.

 

1. service服务列表

 

 

2. action动作列表

 

 

3. import 导入列表

 

 

 

还有一点要注意的是 import_list 在parse_config()函数的最后还是会去调用init_parse_config_file(import->filename)函数对所有import进来的.rc文件进行解析。

代码如下:

http://androidxref.com/4.4_r1/xref/system/core/init/init_parser.c#391

 1 static void parse_config(const char *fn, char *s)
 2 {
 3 
 4 .....
 5 
 6 parser_done:
 7     list_for_each(node, &import_list) {
 8          struct import *import = node_to_item(node, struct import, list);
 9          int ret;
10 
11          INFO("importing '%s'", import->filename);
12          ret = init_parse_config_file(import->filename); 
13          if (ret)
14              ERROR("could not import file '%s' from '%s'\n",
15                    import->filename, fn);
16     }
17 }
View Code

 

最终:

把所有的action 都加入到action_list。

把所有的service 都加入到service_list。

 

3 总结:

 

init.rc文件的解析流程大体如下图:

 

Init进程一启动就会读取并解析init.rc脚本文件,把其中的元素整理成自己的数据结构(链表)。

具体情况可参考system\core\init\init.c文件。

Init进程的main()函数会先调用init_parse_config_file(“/init.rc”)来解析init.rc脚本,分析出应该执行的语义,并且把脚本中描述的action和service信息分别组织成双向链表,然后执行之。

=========

 

到这里,我们就解析完所有的rc文件,并且得到了之后需要操作两个list(action_list and service_list),之后就可以准备执行了。

 

 

---------

*********

---------

posted on 2020-01-15 22:32  二的次方  阅读(803)  评论(0编辑  收藏  举报