文章出处:http://blog.csdn.net/shift_wwx/article/details/39232763
请转载的朋友表明出处~~
前言:之前一篇博文《Android 的init过程详解》小结了一下init的流程,这一篇小结一下init.rc。网上的资料很多也很详细,我这里也是结合自己的想法做个小结。
init.rc 文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否则机械地分析 init.c及其相关文件的源代码毫无意义。
为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果有编译好的Android源代码, 在
一、AIL组成
1. 动作(Actions)
2. 命令(Commands)
3. 服务(Services)
4. 选项(Options)
二、AIL命令书写规则
1、上面说的AIL4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。可以使用反斜杠转义符在 Token中插入空格。双引号可以将多个由空格分隔的Tokens合成一个Tokens。如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是 说,可以用反斜杠将多行代码连接成一行代码。
2、AIL的注释与很多Shell脚本一行,以#开头。
3、AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。也就是说,每一个Actions或 Services确定一个Section。而所有的Commands和Options只能属于最近定义的Section。如果Commands和 Options在第一个Section之前被定义,它们将被忽略。
4、Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。
三、四个组成的语法配置
语法中用到的关键字,可以参考system/core/init/keywords.h
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(dalvik_recache, OPTION, 0, 0)
KEYWORD(disabled, OPTION, 0, 0)
KEYWORD(domainname, COMMAND, 1, do_domainname)
KEYWORD(exec, COMMAND, 1, do_exec)
KEYWORD(export, COMMAND, 2, do_export)
KEYWORD(group, OPTION, 0, 0)
KEYWORD(hostname, COMMAND, 1, do_hostname)
KEYWORD(ifup, COMMAND, 1, do_ifup)
KEYWORD(insmod, COMMAND, 1, do_insmod)
KEYWORD(import, SECTION, 1, 0)
KEYWORD(keycodes, OPTION, 0, 0)
KEYWORD(mkdir, COMMAND, 1, do_mkdir)
KEYWORD(mount_all, COMMAND, 1, do_mount_all)
KEYWORD(mount, COMMAND, 3, do_mount)
KEYWORD(on, SECTION, 0, 0)
KEYWORD(oneshot, OPTION, 0, 0)
KEYWORD(onrestart, OPTION, 0, 0)
KEYWORD(powerctl, COMMAND, 1, do_powerctl)
KEYWORD(restart, COMMAND, 1, do_restart)
KEYWORD(restorecon, COMMAND, 1, do_restorecon)
KEYWORD(rm, COMMAND, 1, do_rm)
KEYWORD(rmdir, COMMAND, 1, do_rmdir)
KEYWORD(seclabel, OPTION, 0, 0)
KEYWORD(service, SECTION, 0, 0)
KEYWORD(setcon, COMMAND, 1, do_setcon)
KEYWORD(setenforce, COMMAND, 1, do_setenforce)
KEYWORD(setenv, OPTION, 2, 0)
KEYWORD(setkey, COMMAND, 0, do_setkey)
KEYWORD(setprop, COMMAND, 2, do_setprop)
KEYWORD(setrlimit, COMMAND, 3, do_setrlimit)
KEYWORD(setsebool, COMMAND, 2, do_setsebool)
KEYWORD(socket, OPTION, 0, 0)
KEYWORD(start, COMMAND, 1, do_start)
KEYWORD(stop, COMMAND, 1, do_stop)
KEYWORD(swapon_all, COMMAND, 1, do_swapon_all)
KEYWORD(trigger, COMMAND, 1, do_trigger)
KEYWORD(symlink, COMMAND, 1, do_symlink)
KEYWORD(sysclktz, COMMAND, 1, do_sysclktz)
KEYWORD(user, OPTION, 0, 0)
KEYWORD(wait, COMMAND, 1, do_wait)
KEYWORD(write, COMMAND, 2, do_write)
KEYWORD(copy, COMMAND, 2, do_copy)
KEYWORD(chown, COMMAND, 2, do_chown)
KEYWORD(chmod, COMMAND, 2, do_chmod)
KEYWORD(loglevel, COMMAND, 1, do_loglevel)
KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props)
KEYWORD(ubiattach, COMMAND, 1, do_ubiattach)
KEYWORD(ubidetach, COMMAND, 1, do_ubidetach)
KEYWORD(ioprio, OPTION, 0, 0)
KEYWORD(e2fsck, COMMAND, 2, do_e2fsck)
KEYWORD(confirm_formated, COMMAND, 3, do_confirm_formated)
KEYWORD(display_logo, COMMAND, 1, do_display_logo )
例如:关键字class_start
KEYWORD(class_start, COMMAND, 1, do_class_start)
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;
}
可以看出是启动所有not disabled的services
1、 action
1.1 语法格式:
on
也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。
例一:
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16
# Set the security context for the init process.
# This should occur before anything else (e.g. ueventd) is started.
setcon u:r:init:s0
start ueventd
例二:
on boot
# basic network init
ifup lo
hostname localhost
domainname localdomain
例三:
on property:vold.decrypt=trigger_reset_main
class_reset main
都是以on 开头的,
然后不同的trigger触发不同的command。其他的触发器1.2中有列出。
在例一中就是在early-init时,做了三件事情,例如最后的start ueventd,就是启动ueventd的service
在例三中是在property vold.decrypt值为trigger_reset_main的时候重启main
1.2 触发器
1) boot、early-init等,还有几个之前一篇博文上有说明
2)
就是在值等于多少的时候触发,如上面的例三
3) device-added-
当设备节点被添加的时候触发
4) device-removed-
当设备节点被移除的时候触发
5) service-exited-
当一个特定的服务退出的时候触发
1.3 action中出现的命令
1)exec
创建和执行一个程序(
2)export
在全局环境中将
export EXTERNAL_STORAGE /storage/emulated/legacy
启动网络接口
4)hostname
设置主机名
5)domainname
设置域名
6)import
指定要解析的其他配置文件。常被用于当前配置文件的扩展
import /init.${ro.hardware}.rc
7)chdir
改变工作目录
8)chmod
改变文件的访问权限
chmod 0660 /sys/fs/cgroup/memory/tasks
9)chown 更改文件的所有者和组
chown root system /sys/fs/cgroup/memory/tasks
10)chroot 改变处理根目录
11)class_start
启动所有指定服务类下的未运行服务。
on charger
class_start charger
12)class_stop 停止指定服务类下的所有已运行的服务。
13)class_reset
重启指定的服务
on property:vold.decrypt=trigger_reset_main
class_reset main
14)insmod
加载
insmod /system/lib/audio_data.ko
15)mkdir
创建一个目录
mkdir /data/misc 01771 system misc
mkdir /data/misc/adb 02750 system shell
16)mount
试图在目录
on fs
setprop ro.crypto.umount_sd false
#mount ext4 /dev/block/system /system wait ro noatime nodiratime noauto_da_alloc
mount ext4 /dev/block/mmcblk0p2 /system wait ro noatime nodiratime noauto_da_alloc
#e2fsck -y /dev/block/data
#mount ext4 /dev/block/data /data noatime nodiratime norelatime nosuid nodev noauto_da_alloc
mount ext4 /dev/block/mmcblk0p4 /data noatime nodiratime norelatime nosuid nodev noauto_da_alloc
#e2fsck -y /dev/block/cache
#mount ext4 /dev/block/cache /cache noatime nodiratime norelatime nosuid nodev noauto_da_alloc
mount ext4 /dev/block/mmcblk0p3 /cache noatime nodiratime norelatime nosuid nodev noauto_da_alloc
17)setkey
保留,暂时未用
18)setprop
将系统属性
setprop ro.crypto.umount_sd false
19)setrlimit
设置
20)start
启动指定服务(如果此服务还未运行)。
start ueventd
21)stop
停止指定服务(如果此服务在运行中)。
22)symlink
创建一个指向
23)sysclktz
设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)
24)trigger
触发一个事件。用于Action排队
25)wait
等待一个文件是否存在,当文件存在时立即返回,或到
26)write
向
write /sys/fs/cgroup/memory/sw/memory.swappiness 100
27)copy
将特定文件copy到指定的path
28)powerctl
从code中,可以看出是android reboot时候的各种模式,ANDROID_RB_RESTART、ANDROID_RB_POWEROFF、ANDROID_RB_RESTART2
2、 service
Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。
2.1 语法格式:
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
例二:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
2.2 服务支持的格式
1)critical
表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。
2)disabled
This service will not automatically start with its class.
It must be explicitly started by name.
这个关键字的service,是不会自动start的,必须要单独指定service name去start才行
3)setenv
在进程启动时将环境变量
4)socket
Create a unix domain socketnamed /dev/socket/
its fd to the launchedprocess.
User and group default to0.
创建一个unix域的名为/dev/socket/5)user
在启动这个服务前改变该服务的用户名。此时默认为 root。
6)group
在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。
7)oneshot
表示该服务只启动一次,而如果没有oneshot选项,这个可执行程序将一直存在——如果可执行程序被杀死,则会重新启动
8)class
指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务
9)onrestart
当服务重启,执行一个命令(下详)
。
。
。
其余的关键字,可以参考keywords.h,或者是system/core/init下的readme.txt
四、init.rc加载过程
在上一篇博文《Android 的init过程详解》中,简单的说了一下init.rc的加载
在init.c中的main函数中:
if (!strcmp(bootmode,"factory"))
init_parse_config_file("/init.factorytest.rc");
else if (!strcmp(bootmode,"factory2"))
init_parse_config_file("/init.factorytest2.rc");
else
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;
}
显示读文件读出来,然后执行关键函数parse_config。
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;
for (;;) {
switch (next_token(&state)) {
case T_EOF:
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:
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;
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);
}
}
如果仔细研读,发现code的其实很简单的,首先是读出import的文件,然后再对每个文件进行递归解析。
首先,code最开始是初始化一个链表,用于存储import的文件。
list_init(&import_list);
state.priv = &import_list;
for循环主要关键是next_token函数,根据函数的返回值判断各种状态,主要由三种,即T_EOF(文件结束)、T_NEWLINE(新的一行)、T_TEXT(读到了非空格等字符的token)。
根据code中,会将读出来的字符串保存到args中:
args[nargs++] = state.text;
刚开始的时候,我有点疑问,这里保存的是字符指针,那么如果没有碰到字符串结束符,应该是指针以后的所有字符,后来,在next_token函数中注意到了一句话:
textdone:
state->ptr = x;
*s = 0;
return T_TEXT;
在解析text 结束后,会将最后的一个字符置0,这就是字符串结束符。那也就是说之前指针数组args存放的是到空格、退格符、制表符为止的一个字符串,这就是token的真正意义了。
根据code:
case T_TEXT:
if (nargs < INIT_PARSER_MAXARGS) {
args[nargs++] = state.text;
}
break;
}
可以看出会一直解析这样的token,当然个数是有限制的,最大是64,也就是说init.rc中命令行关键字及option加起来个数最多是64。
循环解析,一直到了一行结束。
next_token函数中可以看出,在换行的时候
case '\n':
state->nexttoken = T_NEWLINE;
x++;
goto textdone;
if (state->nexttoken) {
int t = state->nexttoken;
state->nexttoken = 0;
return t;
}
这样就是解析这一行的时候:
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函数就是为了解析这一行的第一个关键字类型。source code应该很简单。
主要是这个判断很关键:
#define kw_is(kw, type) (keyword_info[kw].flags & (type))
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"
};
这里初始化挺好玩的,将keywords.h中的定义include进来了,第一次碰见这样玩的,学习了。
KEYWORD(import, SECTION, 1, 0)
KEYWORD(on, SECTION, 0, 0)
KEYWORD(service, SECTION, 0, 0)
而KEYWORD 定义在init_parser.c中:
#define KEYWORD(symbol, flags, nargs, func) \
[ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
这样就清楚了,keyword_info[KEYWORD_COUNT]如果是完整列出来应该是:
keyword_info[KEYWORD_COUNT] = {
[ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
[K_ capability] = {" capability ", 0, 1, OPTION },
[K_ chdir] = {"chdir", do_chdir ,2, COMMAND},
...
...
根据上面分析,如果type是SECTION,就会
state.parse_line(&state, 0, 0);
parse_new_section(&state, kw, nargs, args);
可以state.parse_line在函数的最开始赋值为:
state.parse_line = parse_line_no_op;
可是函数parase_line_no_op是个空的:
void parse_line_no_op(struct parse_state *state, int nargs, char **args)
{
}
为什么会这样设计呢?等会做解释。
接下来看一下另一个函数了:
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:
state->context = parse_service(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_service;
return;
}
break;
case K_on:
case K_e2fsck:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
case K_import:
parse_import(state, nargs, args);
break;
}
state->parse_line = parse_line_no_op;
}
1、parse_service(state, nargs, args);
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
struct service *svc;
if (nargs < 3) {
parse_error(state, "services must have a name and a program\n");
return 0;
}
if (!valid_name(args[1])) {
parse_error(state, "invalid service name '%s'\n", args[1]);
return 0;
}
svc = service_find_by_name(args[1]);
if (svc) {
parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
return 0;
}
nargs -= 2;
svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
if (!svc) {
parse_error(state, "out of memory\n");
return 0;
}
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);
list_add_tail(&service_list, &svc->slist);
return svc;
}
1)service的命名规则可以看valid_name函数,service name不能有数字、下划线、横线
2、state->parse_line = parse_line_service;
这里会将state中parse_line做了改变,记得之前赋值的另一个函数parase_line_no_op。
终于明白了为什么刚开始parase_line_no_op为空函数了吧?
到此可以看到了service所有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;
if (nargs == 0) {
return;
}
svc->ioprio_class = IoSchedClass_NONE;
kw = lookup_keyword(args[0]);
switch (kw) {
case K_capability:
break;
case K_class:
if (nargs != 2) {
parse_error(state, "class option requires a classname\n");
} else {
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:
if (nargs != 3) {
parse_error(state, "ioprio optin usage: ioprio
case K_on:
case K_e2fsck:
state->context = parse_action(state, nargs, args);
if (state->context) {
state->parse_line = parse_line_action;
return;
}
break;
也就是说最后处理是在parse_line_action函数中。就不做细细分析了。
至此init.rc的解析过程就全部结束了。
总结一下解析过程:
1、init_parse_config_file(const char *fn)引入init.rc
2、read_file
3、parse_config做递归的解析*.rc
1)next_token函数解析每一个token
2)parse_new_section根据token 的type选择parse_service、parse_action、parse_import
3)state.parse_line(&state, nargs, args);
参考文献:
http://www.cnblogs.com/nokiaguy/p/3164799.html