本文使用的软件版本
Android:4.2.2
Linux内核:3.1.10
在上一篇文章中介绍了init的初始化第一阶段,也就是处理各种属性。在本文将会详细分析init最重要的一环:解析init.rc文件。
init.rc文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否则机械地分析init.c及其相关文件的源代码毫无意义。
为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果有编译好的Android源代码,在<Android源代码根目录>out/target/product/generic/root目录也可找到init.rc文件。
AIL由如下4部分组成。
1. 动作(Actions)
2. 命令(Commands)
3. 服务(Services)
4. 选项(Options)
这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。可以使用反斜杠转义符在Token中插入空格。双引号可以将多个由空格分隔的Tokens合成一个Tokens。如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是说,可以用反斜杠将多行代码连接成一行代码。
AIL的注释与很多Shell脚本一行,以#开头。
AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。也就是说,每一个Actions或Services确定一个Section。而所有的Commands和Options只能属于最近定义的Section。如果Commands和Options在第一个Section之前被定义,它们将被忽略。
Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。
下面来看看Actions、Services、Commands和Options分别应如何设置。
Actions的语法格式如下:
1
2
3
4
|
on <trigger>
<command>
<command>
<command>
|
也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action。
1
2
3
4
|
on boot
ifup lo
hostname localhost
domainname localdomain
|
其中boot是触发器,下面三行是command
那么init.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。
1. boot
这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger
2. <name>=<value>
当属性<name>被设置成<value>时被触发。例如,
on property:vold.decrypt=trigger_reset_main
class_reset main
3. device-added-<path>
当设备节点被添加时触发
4. device-removed-<path>
当设备节点被移除时添加
5. service-exited-<name>
会在一个特定的服务退出时触发
Actions后需要跟若干个命令,这些命令如下:
1. exec <path> [<argument> ]*
创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。
2. export <name> <value>
在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)
3. ifup <interface>
启动网络接口
4. import <filename>
指定要解析的其他配置文件。常被用于当前配置文件的扩展
5. hostname <name>
设置主机名
6. chdir <directory>
改变工作目录
7. chmod <octal-mode><path>
改变文件的访问权限
8. chown <owner><group> <path>
更改文件的所有者和组
9. chroot <directory>
改变处理根目录
10. class_start<serviceclass>
启动所有指定服务类下的未运行服务。
11 class_stop<serviceclass>
停止指定服务类下的所有已运行的服务。
12. domainname <name>
设置域名
13. insmod <path>
加载<path>指定的驱动模块
14. mkdir <path> [mode][owner] [group]
创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。
15. mount <type> <device> <dir> [<mountoption> ]*
试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。<mountoption>包括 "ro"、"rw"、"re
16. setkey
保留,暂时未用
17. setprop <name><value>
将系统属性<name>的值设为<value>。
18. setrlimit <resource> <cur> <max>
设置<resource>的rlimit (资源限制)
19. start <service>
启动指定服务(如果此服务还未运行)。
20.stop<service>
停止指定服务(如果此服务在运行中)。
21. symlink <target> <path>
创建一个指向<path>的软连接<target>。
22. sysclktz <mins_west_of_gmt>
设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)
23. trigger <event>
触发一个事件。用于Action排队
24. wait <path> [<timeout> ]
等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定<timeout>,默认超时时间是5秒。
25. write <path> <string> [ <string> ]*
向<path>指定的文件写入一个或多个字符串。
Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。Services (服务)的形式如下:
1
2
3
|
service <name> <pathname> [ <argument> ]*
<option>
<option>
|
例如,下面是一个标准的Service用法
1
2
3
4
5
6
7
8
9
|
service servicemanager /system/bin/servicemanager
class
core
user system
group system
critical
onrestart restart zygote
onrestart restart media
onrestart restart surfaceflinger
onrestart restart drm
|
Services的选项是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:
1. critical
表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。
2. disabled
表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。
3. setenv <name><value>
在进程启动时将环境变量<name>设置为<value>。
4. socket <name><type> <perm> [ <user> [ <group> ] ]
Create a unix domain socketnamed /dev/socket/<name> and pass
its fd to the launchedprocess. <type> must be"dgram", "stream" or "seqpacket".
User and group default to0.
创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。<type> 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。
5. user <username>
在启动这个服务前改变该服务的用户名。此时默认为 root。
6. group <groupname> [<groupname> ]*
在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。
7. oneshot
服务退出时不重启。
8. class <name>
指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。
9. onrestart
当服务重启,执行一个命令(下详)。
现在接着分析一下init是如何解析init.rc的。现在打开system/core/init/init.c文件,找到main函数。在上一篇文章中分析了main函数的前一部分(初始化属性、处理内核命令行等),现在找到init_parse_config_file函数,调用代码如下:
init_parse_config_file("/init.rc");
这个方法主要负责初始化和分析init.rc文件。init_parse_config_file函数在init_parser.c文件中实现,代码如下:
1
2
3
4
5
6
7
8
9
10
|
int
init_parse_config_file(
const
char
*fn)
{
char
*data;
data = read_file(fn, 0);
if
(!data)
return
-1;
/* 实际分析init.rc文件的代码 */
parse_config(fn, data);
DUMP();
return
0;
}
|
init_parse_config_file方法开始调用了read_file函数打开了/init.rc文件,并返回了文件的内容(char*类型),然后最核心的函数是parse_config。该函数也在init_parser.c文件中实现,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
static
void
parse_config(
const
char
*fn,
char
*s)
{
struct
parse_state state;
struct
listnode import_list;
struct
listnode *node;
char
*args[INIT_PARSER_MAXARGS];
int
nargs;
nargs = 0;
state.filename = fn;
state.line = 0;
state.ptr = s;
state.nexttoken = 0;
state.parse_line = parse_line_no_op;
list_init(&import_list);
state.priv = &import_list;
/* 开始获取每一个token,然后分析这些token,每一个token就是有空格、字表符和回车符分隔的字符串
*/
for
(;;) {
/* next_token函数相当于词法分析器 */
switch
(next_token(&state)) {
case
T_EOF:
/* init.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)) {
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 */
if
(nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break
;
}
}
parser_done:
/* 最后处理由import导入的初始化文件 */
list_for_each(node, &import_list) {
struct
import *import = node_to_item(node,
struct
import, list);
int
ret;
INFO(
"importing '%s'"
, import->filename);
/* 递归调用 */
ret = init_parse_config_file(import->filename);
if
(ret)
ERROR(
"could not import file '%s' from '%s'\n"
,
import->filename, fn);
}
}
|
parse_config方法的代码就比较复杂了,现在先说说该方法的基本处理流程。首先会调用 list_init(&import_list)初始化一个链表,该链表是用于存储通过import语句导入的初始化文件名。然后开始开始在for循环中分析init.rc文件中的每一行代码。最后将init.rc文件分析完后,就会进入parser_done部分,并递归调用init_parse_config_file方法分析通过import导入的初始化文件。
通过分析parse_config方法的原理,感觉也并不是很复杂。不过分析parse_config方法的具体代码,还需要点编译原理的知识(只是概念上的就可以)。在for循环中调用了一个next_token方法不断从init.rc文件中获取token。这里的token,就是一种编程语言的最小单元,也就是不可再分。例如,对于传统的编程语言,if、then等关键字、变量名等标识符都属于一个token。而对于init.rc文件来说,import、on、以及触发器的参数值,都属于一个token。
一个完整的编译器(或解析器)最开始需要进行词法和语法分析,词法分析就是在源代码文件中挑出一个个的Token,也就是说,词法分析器的返回值是Token,而语法分析器的输入就是词法分析器的输出。也就是说,语法分析器需要分析一个个的token,而不是一个个的字符。由于init解析语言很简单,所以就将词法和语法分析器放到了一起。词法分析器就是next_token函数,而语法分析器就是T_NEWLINE分支中的代码。这些就清楚多了。现在先看看next_token函数(在parser.c文件中实现)是如何获取每一个token的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
int
next_token(
struct
parse_state *state)
{
char
*x = state->ptr;
char
*s;
if
(state->nexttoken) {
int
t = state->nexttoken;
state->nexttoken = 0;
return
t;
}
/* 在这里开始一个字符一个字符地分析 */
for
(;;) {
switch
(*x) {
case
0:
state->ptr = x;
return
T_EOF;
case
'\n'
:
x++;
state->ptr = x;
return
T_NEWLINE;
case
' '
:
case
'\t'
:
case
'\r'
:
x++;
continue
;
case
'#'
:
while
(*x && (*x !=
'\n'
)) x++;
if
(*x ==
'\n'
) {
state->ptr = x+1;
return
T_NEWLINE;
}
else
{
state->ptr = x;
return
T_EOF;
}
default
:
goto
text;
}
}
textdone:
state->ptr = x;
*s = 0;
return
T_TEXT;
text:
state->text = s = x;
textresume:
for
(;;) {
switch
(*x) {
case
0:
goto
textdone;
case
' '
:
case
'\t'
:
case
'\r'
:
x++;
goto
textdone;
case
'\n'
:
state->nexttoken = T_NEWLINE;
x++;
goto
textdone;
case
'"'
:
x++;
for
(;;) {
switch
(*x) {
case
0:
/* unterminated quoted thing */
state->ptr = x;
return
T_EOF;
case
'"'
:
x++;
goto
textresume;
default
:
*s++ = *x++;
}
}
break
;
case
'\\'
:
x++;
switch
(*x) {
case
0:
goto
textdone;
case
'n'
:
*s++ =
'\n'
;
break
;
case
'r'
:
*s++ =
'\r'
;
break
;
case
't'
:
*s++ =
'\t'
;
break
;
case
'\\'
:
*s++ =
'\\'
;
break
;
case
'\r'
:
/* \ <cr> <lf> -> line continuation */
if
(x[1] !=
'\n'
) {
x++;
continue
;
}
case
'\n'
:
/* \ <lf> -> line continuation */
state->line++;
x++;
/* eat any extra whitespace */
while
((*x ==
' '
) || (*x ==
'\t'
)) x++;
continue
;
default
:
/* unknown escape -- just copy */
*s++ = *x++;
}
continue
;
default
:
*s++ = *x++;
}
}
return
T_EOF;
}
|
next_token函数的代码还是很多的,不过原理到很简单。就是逐一读取init.rc文件(还有import导入的初始化文件)的字符,并将由空格、“/t”和“/r”分隔的字符串挑出来,并通过state->text返回。如果返回了正常的token,next_token函数就返回T_TEXT。如果一行结束,就返回T_NEWLINE,如果init.rc文件的内容已读取完,就返回T_EOF。当返回T_NEWLINE时,开始语法分析(由于init初始化语言是基于行的,所以语言分析实际上就是分析init.rc文件的每一行,只是这些行已经被分解成一个个token了)。感兴趣的读者可以详细分析一下next_token函数的代码,尽管代码很多,但并不复杂。而且还很有意思。
现在回到parse_config函数,先看一下T_TEXT分支。该分支将获得的每一行的token都存储在args数组中。现在来看T_NEWLINE分支。该分支的代码涉及到一个state.parse_line函数指针,该函数指针指向的函数负责具体的分析工作。但我们发现,一看是该函数指针指向了一个空函数parse_line_no_op,实际上,一开始该函数指针什么都不做,只是为了使该函数一开始不至于为null,否则调用出错。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
case
T_NEWLINE:
state.line++;
if
(nargs) {
int
kw = lookup_keyword(args[0]);
if
(kw_is(kw, SECTION)) {
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
}
else
{
state.parse_line(&state, nargs, args);
}
nargs = 0;
}
break
;
|
在上面的代码中首先调用了lookup_keyword方法搜索关键字。该方法的作用是判断当前行是否合法,也就是根据Init初始化语言预定义的关键字查询,如果未查到,返回K_UNKNOWN。lookup_keyword方法在init_parser.c文件中实现,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
int
lookup_keyword(
const
char
*s)
{
switch
(*s++) {
case
'c'
:
if
(!
strcmp
(s,
"opy"
))
return
K_copy;
if
(!
strcmp
(s,
"apability"
))
return
K_capability;
if
(!
strcmp
(s,
"hdir"
))
return
K_chdir;
if
(!
strcmp
(s,
"hroot"
))
return
K_chroot;
if
(!
strcmp
(s,
"lass"
))
return
K_class;
if
(!
strcmp
(s,
"lass_start"
))
return
K_class_start;
if
(!
strcmp
(s,
"lass_stop"
))
return
K_class_stop;
if
(!
strcmp
(s,
"lass_reset"
))
return
K_class_reset;
if
(!
strcmp
(s,
"onsole"
))
return
K_console;
if
(!
strcmp
(s,
"hown"
))
return
K_chown;
if
(!
strcmp
(s,
"hmod"
))
return
K_chmod;
if
(!
strcmp
(s,
"ritical"
))
return
K_critical;
break
;
case
'd'
:
if
(!
strcmp
(s,
"isabled"
))
return
K_disabled;
if
(!
strcmp
(s,
"omainname"
))
return
K_domainname;
break
;
… …
case
'o'
:
if
(!
strcmp
(s,
"n"
))
return
K_on;
if
(!
strcmp
(s,
"neshot"
))
return
K_oneshot;
if
(!
strcmp
(s,
"nrestart"
))
return
K_onrestart;
break
;
case
'r'
:
if
(!
strcmp
(s,
"estart"
))
return
K_restart;
if
(!
strcmp
(s,
"estorecon"
))
return
K_restorecon;
if
(!
strcmp
(s,
"mdir"
))
return
K_rmdir;
if
(!
strcmp
(s,
"m"
))
return
K_rm;
break
;
case
's'
:
if
(!
strcmp
(s,
"eclabel"
))
return
K_seclabel;
if
(!
strcmp
(s,
"ervice"
))
return
K_service;
if
(!
strcmp
(s,
"etcon"
))
return
K_setcon;
if
(!
strcmp
(s,
"etenforce"
))
return
K_setenforce;
if
(!
strcmp
(s,
"etenv"
))
return
K_setenv;
if
(!
strcmp
(s,
"etkey"
))
return
K_setkey;
if
(!
strcmp
(s,
"etprop"
))
return
K_setprop;
if
(!
strcmp
(s,
"etrlimit"
))
return
K_setrlimit;
if
(!
strcmp
(s,
"etsebool"
))
return
K_setsebool;
if
(!
strcmp
(s,
"ocket"
))
return
K_socket;
if
(!
strcmp
(s,
"tart"
))
return
K_start;
if
(!
strcmp
(s,
"top"
))
return
K_stop;
if
(!
strcmp
(s,
"ymlink"
))
return
K_symlink;
if
(!
strcmp
(s,
"ysclktz"
))
return
K_sysclktz;
break
;
case
't'
:
if
(!
strcmp
(s,
"rigger"
))
return
K_trigger;
break
;
case
'u'
:
if
(!
strcmp
(s,
"ser"
))
return
K_user;
break
;
case
'w'
:
if
(!
strcmp
(s,
"rite"
))
return
K_write;
if
(!
strcmp
(s,
"ait"
))
return
K_wait;
break
;
}
return
K_UNKNOWN;
}
|
lookup_keyword方法按26个字母顺序(关键字首字母)进行处理。
现在回到parse_config方法的T_NEWLIEN分支,接下来调用了kw_is宏具体判断当前行是否合法,该宏以及SECTION宏的定义如下。根据这些代码。明显是keyword_info数组中的某个元素的flags成员变量的值取最后一位。
1
2
|
#define SECTION
0x01
#define kw_is(kw, type) (keyword_info[kw].flags & (type))
|
现在问题又转到keyword_info数组了。该数组也在init_parser.c文件中定义,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
#include "keywords.h"
#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;
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = {
"unknown"
, 0, 0, 0 },
#include "keywords.h"
};
|
从表面上看,keyword_info数组是一个struct数组,但本质上,是一个map。为每一个数组元素设置了一个key,例如,数组元素{ "unknown", 0, 0,0 }的key是K_UNKNOWN,而#include “keywords.h”大有玄机。上面的代码中引用了两次keywords.h文件,现在可以看一下keywords.h文件的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#ifndef KEYWORD
int
do_chroot(
int
nargs,
char
**args);
… …
int
do_export(
int
nargs,
char
**args);
int
do_hostname(
int
nargs,
char
**args);
int
do_rmdir(
int
nargs,
char
**args);
int
do_loglevel(
int
nargs,
char
**args);
int
do_load_persist_props(
int
nargs,
char
**args);
int
do_wait(
int
nargs,
char
**args);
#define __MAKE_KEYWORD_ENUM__
/*
"K_chdir", ENUM
*/
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
enum
{
K_UNKNOWN,
#endif
KEYWORD(capability, OPTION, 0, 0)
KEYWORD(chdir, COMMAND, 1, do_chdir)
KEYWORD(chroot, COMMAND, 1, do_chroot)
KEYWORD(
class
, OPTION, 0, 0)
KEYWORD(class_start, COMMAND, 1, do_class_start)
KEYWORD(class_stop, COMMAND, 1, do_class_stop)
KEYWORD(class_reset, COMMAND, 1, do_class_reset)
KEYWORD(console, OPTION, 0, 0)
… …
KEYWORD(critical, OPTION, 0, 0)
KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props)
KEYWORD(ioprio, OPTION, 0, 0)
#ifdef __MAKE_KEYWORD_ENUM__
KEYWORD_COUNT,
};
#undef __MAKE_KEYWORD_ENUM__
#undef KEYWORD
#endif
|
从keywords.h文件的代码可以看出,如果未定义KEYWORD宏,则在keywords.h文件中定义一个KEYWORD宏,以及一个枚举类型,其中K_##symbol的##表示连接的意思。而这个KEYWORD宏只用了第一个参数(symbol)。例如,KEYWORD(chdir, COMMAND, 1, do_chdir)就会生成K_chdir。
而在keyword_info结构体数组中再次导入keywords.h文件,这是KEYWORD宏已经在init_parser.c文件中重新定义,所以第一次导入keywords.h文件使用的是如下的宏。
1
2
|
#define KEYWORD(symbol, flags, nargs, func) \
[ K_##symbol ] = { #symbol, func, nargs +
1
, flags, },
|
这下就明白了,如果不使用keywords.h文件,直接将所有的代码都写到init_parser.c文件中,就会有下面的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
int
do_chroot(
int
nargs,
char
**args);
… …
enum
{
K_UNKNOWN,
K_ capability,
K_ chdir,
… …
}
#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;
} keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = {
"unknown"
, 0, 0, 0 },
[K_ capability] = {
" capability "
, 0, 1, OPTION },
[K_ chdir] = {
"chdir"
, do_chdir ,2, COMMAND},
… …
#include "keywords.h"
};
|
可能我们还记着lookup_keyword方法,该方法的返回值就是keyword_info数组的key。
在keywords.h前面定义的函数指针都是处理init.rc文件中service、action和command的。现在就剩下一个问题了,在哪里为这些函数指针赋值呢,也就是说,具体处理每个部分的函数在哪里呢。现在回到前面的语法分析部分。如果当前行合法,则会执行parse_new_section函数(在init_parser.c文件中实现),该函数将为section和action设置处理这两部分的函数。parse_new_section函数的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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;
return
;
}
break
;
case
K_on:
// 处理action
state->context = parse_action(state, nargs, args);
if
(state->context) {
state->parse_line = parse_line_action;
return
;
}
break
;
case
K_import:
// 单独处理import导入的初始化文件。
parse_import(state, nargs, args);
break
;
}
state->parse_line = parse_line_no_op;
}
|
现在看一下处理service的函数(parse_line_service)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
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;
if
(nargs == 0) {
return
;
}
svc->ioprio_class = IoSchedClass_NONE;
kw = lookup_keyword(args[0]);
// 下面处理每一个option
switch
(kw) {
case
K_capability:
break
;
… …
case
K_group:
if
(nargs < 2) {
parse_error(state,
"group option requires a group id\n"
);
}
else
if
(nargs > NR_SVC_SUPP_GIDS + 2) {
parse_error(state,
"group option accepts at most %d supp. groups\n"
,
NR_SVC_SUPP_GIDS);
}
else
{
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:
if
(nargs < 2) {
parse_error(state,
"keycodes option requires atleast one keycode\n"
);
}
else
{
svc->keycodes =
malloc
((nargs - 1) *
sizeof
(svc->keycodes[0]));
if
(!svc->keycodes) {
parse_error(state,
"could not allocate keycodes\n"
);
}
else
{
svc->nkeycodes = nargs - 1;
for
(i = 1; i < nargs; i++) {
svc->keycodes[i - 1] =
atoi
(args[i]);
}
}
}
break
;
… …
}
……
}
|
Action的处理方式与service类似,读者可以自行查看相应的函数代码。现在一切都清楚了。处理service的函数是parse_line_service,处理action的函数是parse_line_action。而前面的state.parse_line根据当前是service还是action,指向这两个处理函数中的一个,并执行相应的函数处理actioncommand和serviceoption。 综合上述,实际上分析init.rc文件的过程就是通过一系列地处理,最终转换为通过parse_line_service或parse_line_action函数分析Init.rc文件中每一行的行为。
本文转自 androidguy 51CTO博客,原文链接: 本文转自 androidguy 51CTO博客,原文链接:,如需转载请自行联系原作者
,如需转载请自行联系原作者