少年辛苦终身事,莫向光阴惰寸功。这篇文章主要讲述Android6.0系统启动流程分析一:init进程相关的知识,希望能为你提供帮助。
到了android6.0,Init进程使用c+
+
来写了,
不过没有关系,
它和c写的init没有太大的区别。
Init进程的入口代码是:
system\\core\\init\\init.cpp
main函数:
int main(int argc, char** argv) {
if (!strcmp(basename(argv[0]), "
ueventd"
)) {
return ueventd_main(argc, argv);
}if (!strcmp(basename(argv[0]), "
watchdogd"
)) {
return watchdogd_main(argc, argv);
}// Clear the umask.
umask(0);
add_environment("
PATH"
, _PATH_DEFPATH);
bool is_first_stage =
(argc =
=
1) || (strcmp(argv[1], "
--second-stage"
) !=
0);
// Get the basic filesystem setup we need put together in the initramdisk
// on / and then we'
ll let the rc file figure out the rest.
if (is_first_stage) {
mount("
tmpfs"
, "
/dev"
, "
tmpfs"
, MS_NOSUID, "
mode=
0755"
);
mkdir("
/dev/pts"
, 0755);
mkdir("
/dev/socket"
, 0755);
mount("
devpts"
, "
/dev/pts"
, "
devpts"
, 0, NULL);
mount("
proc"
, "
/proc"
, "
proc"
, 0, NULL);
mount("
sysfs"
, "
/sys"
, "
sysfs"
, 0, NULL);
}// We must have some place other than / to create the device nodes for
// kmsg and null, otherwise we won'
t be able to remount / read-only
// later on. Now that tmpfs is mounted on /dev, we can actually talk
// to the outside world.
open_devnull_stdio();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
NOTICE("
init%s started!\\n"
, is_first_stage ? "
"
: "
second stage"
);
if (!is_first_stage) {
// Indicate that booting is in progress to background fw loaders, etc.
close(open("
/dev/.booting"
, O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
property_init();
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
process_kernel_cmdline();
// Propogate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
}// Set up SELinux, including loading the SELinux policy if we'
re in the kernel domain.
selinux_initialize(is_first_stage);
// If we'
re in the kernel domain, re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (is_first_stage) {
if (restorecon("
/init"
) =
=
-1) {
ERROR("
restorecon failed: %s\\n"
, strerror(errno));
security_failure();
}
char* path =
argv[0];
char* args[] =
{ path, const_cast<
char*>
("
--second-stage"
), nullptr };
if (execv(path, args) =
=
-1) {
ERROR("
execv(\\"
%s\\"
) failed: %s\\n"
, path, strerror(errno));
security_failure();
}
}// These directories were necessarily created before initial policy load
// and therefore need their security context restored to the proper value.
// This must happen before /dev is populated by ueventd.
INFO("
Running restorecon...\\n"
);
restorecon("
/dev"
);
restorecon("
/dev/socket"
);
restorecon("
/dev/__properties__"
);
restorecon_recursive("
/sys"
);
epoll_fd =
epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd =
=
-1) {
ERROR("
epoll_create1 failed: %s\\n"
, strerror(errno));
exit(1);
}signal_handler_init();
property_load_boot_defaults();
start_property_service();
init_parse_config_file("
/init.rc"
);
action_for_each_trigger("
early-init"
, action_add_queue_tail);
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
queue_builtin_action(wait_for_coldboot_done_action, "
wait_for_coldboot_done"
);
// ... so that we can start queuing up actions that require stuff from /dev.
queue_builtin_action(mix_hwrng_into_linux_rng_action, "
mix_hwrng_into_linux_rng"
);
queue_builtin_action(keychord_init_action, "
keychord_init"
);
queue_builtin_action(console_init_action, "
console_init"
);
// Trigger all the boot actions to get us started.
action_for_each_trigger("
init"
, action_add_queue_tail);
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
// wasn'
t ready immediately after wait_for_coldboot_done
queue_builtin_action(mix_hwrng_into_linux_rng_action, "
mix_hwrng_into_linux_rng"
);
// Don'
t mount filesystems or start core system services in charger mode.
char bootmode[PROP_VALUE_MAX];
if (property_get("
ro.bootmode"
, bootmode) >
0 &
&
strcmp(bootmode, "
charger"
) =
=
0) {
action_for_each_trigger("
charger"
, action_add_queue_tail);
} else {
action_for_each_trigger("
late-init"
, action_add_queue_tail);
}// Run all property triggers based on current state of the properties.
queue_builtin_action(queue_property_triggers_action, "
queue_property_triggers"
);
while (true) {
if (!waiting_for_exec) {
execute_one_command();
restart_processes();
}int timeout =
-1;
if (process_needs_restart) {
timeout =
(process_needs_restart - gettime()) * 1000;
if (timeout <
0)
timeout =
0;
}if (!action_queue_empty() || cur_action) {
timeout =
0;
}bootchart_sample(&
timeout);
epoll_event ev;
int nr =
TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &
ev, 1, timeout));
if (nr =
=
-1) {
ERROR("
epoll_wait failed: %s\\n"
, strerror(errno));
} else if (nr =
=
1) {
((void (*)()) ev.data.ptr)();
}
}return 0;
}
1.这个函数是否往下执行取决于传入的参数, 如果第0个参数的basename为ueventd, 则执行ueventd_main(argc, argv); 如果basename为watchdogd_main, 则执行watchdogd_main(argc, argv); 只有basename不为这二者时, 才会继续往下执行。
2.如果argv[1]不为”–second-stage”或者只有一个参数的话, 那么is_first_stage就为true,就会创建/dev/pts和”/dev/socket”两个设备文件节点, 并挂载一个文件系统。可以看出来init进程分两个阶段, 不同的阶段有不同的行为。具体的内涵鄙人还没搞明白。
3.启动属性服务。创建一个socket, 并在之后的死循环中监听这个socket返回的文件描述符。
3.解析init.rc。这个过程也是我最感兴趣的, 也是最重要的复杂的。
4.对各个阶段的action排序。
5.进入死循环。
6.第一次进入死循环后, action_queue里面有很多时间, 因此需要不断调用execute_one_command来执行命令。此时, action_queue_empty为假, timeout 为0, init线程不会在epoll_wait方法中休眠, 因为设置的timeout= 0哦, 这一点曾一度困扰了我。
7.所有的命令执行完后, init进程进入休眠, 监听property_set_fd和signal_read_fd两个文件描述符, 一点他们有事件过来, 立刻被唤醒, 进而做事件处理。
init.rc梳理 在我们分析init.rc的解析过程之前, 我们还需要先对init.rc有个基本的认识。
先看一张我根据理解绘制的图:
文章图片
从图来看, init.rc主要有section组成, section由on,import,section三个关键字标示。其中on标示的section叫做action。
import就不用说了, 和c语言中的include功能有点类似。
service格式如下
service <
name>
<
pathname>
[ <
argument>
]*
<
option>
<
option>
...
action后面会跟一个触发器, 然后另起一行开始放置命令( command) ,格式如下:
on <
trigger>
<
command>
<
command>
<
command>
跟在service后面的是option,跟在action后面的是command.command都会对应一个处理函数, 定义在keywords.h中:
...
KEYWORD(loglevel,COMMAND, 1, do_loglevel)
KEYWORD(mkdir,COMMAND, 1, do_mkdir)
KEYWORD(mount_all,COMMAND, 1, do_mount_all)
KEYWORD(mount,COMMAND, 3, do_mount)
...
命名也是很有规则的。比如mkdir,对应的函数就是do_mkdir。我们看看do_mkdir做了什么:
int do_mkdir(int nargs, char **args)
{
mode_t mode =
0755;
int ret;
/* mkdir <
path>
[mode] [owner] [group] */if (nargs >
=
3) {
mode =
strtoul(args[2], 0, 8);
}ret =
make_dir(args[1], mode);
/* chmod in case the directory already exists */
if (ret =
=
-1 &
&
errno =
=
EEXIST) {
ret =
fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
}
if (ret =
=
-1) {
return -errno;
}if (nargs >
=
4) {
uid_t uid =
decode_uid(args[3]);
gid_t gid =
-1;
if (nargs =
=
5) {
gid =
decode_uid(args[4]);
}if (lchown(args[1], uid, gid) =
=
-1) {
return -errno;
}/* chown may have cleared S_ISUID and S_ISGID, chmod again */
if (mode &
(S_ISUID | S_ISGID)) {
ret =
fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW);
if (ret =
=
-1) {
return -errno;
}
}
}return e4crypt_set_directory_policy(args[1]);
}
其实就是调用了make_dir并做了一些权限等方面的操作。所以, 跟在action后面的命令并不能随随便便乱加, 而是要确保这个命令被定义了, 不然就会出错。
init.rc的解析过程( 以import为例) 因为init.rc的第一行代码就是Import语句。万事开头难, 只要我们理清了第一行的解析过程, 后面行的解析分析起来就不怎么费劲了。所以下面我们主要看看init.rc中第一行的解析过程。
init.tc的解析函数为: init_parse_config_file
int init_parse_config_file(const char* path) {
INFO("
Parsing %s...\\n"
, path);
Timer t;
std::string data;
if (!read_file(path, &
data)) {
return -1;
}data.push_back('
\\n'
);
// TODO: fix parse_config.
parse_config(path, data);
dump_parser_state();
// MStar Android Patch Begin
INFO("
(Parsing %s took %.2fs.)\\n"
, path, t.duration());
// MStar Android Patch End
return 0;
}
这个函数把/init.rc中的内容读出来, 并让data这个string类型的变量指向它。
把读出来的data传递给parse_config函数做真正的解析工作。parse_config函数如下:
static void parse_config(const char *fn, const std::string&
data)
{
char *args[UEVENTD_PARSER_MAXARGS];
int nargs =
0;
parse_state state;
state.filename =
fn;
state.line =
1;
state.ptr =
strdup(data.c_str());
// TODO: fix this code!
state.nexttoken =
0;
state.parse_line =
parse_line_no_op;
for (;
;
) {
int token =
next_token(&
state);
switch (token) {
case T_EOF:
parse_line(&
state, args, nargs);
return;
case T_NEWLINE:
if (nargs) {
parse_line(&
state, args, nargs);
nargs =
0;
}
state.line+
+
;
break;
case T_TEXT:
if (nargs <
UEVENTD_PARSER_MAXARGS) {
args[nargs+
+
] =
state.text;
}
break;
}
}
}
我看到这个函数的时候, 我想起了xml解析方法之一的pull解析, 感觉挺像的。每次循环都会找到一个token, token就是一个特定的符号, 然后根据这个toke做不同的处理。这里使用到了parse_state结构, 启动以如下:
struct parse_state
{
char *ptr;
char *text;
int line;
int nexttoken;
void *context;
void (*parse_line)(struct parse_state *state, int nargs, char **args);
const char *filename;
void *priv;
};
这个就够中: ptr执行init.rc字符流的, text后面会用到, 用来保存参数, line当然就是行数了, nexttoken保存下一个token,filename保存init.rc的文件描述符, filename当然是/init.rc了.parse_line是一个函数指针。context暂时没明白…state.priv 指向Import的一个文件链表。
我们打开Init.rc看看, 从头分析它的解析过程。
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.${ro.zygote}.rc
import /init.trace.rc
...
init.rc前面几行都是import语句, 我们看看一开始的解析流程。
这个时候, parse_satate的状态为:
state.filename =
fn;
state.line =
1;
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;
step 1.第一次循环 然后进入死循环, 第一次调用next_token函数:
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;
}
这时候, init.rc中的第一个符号应该是i(impor,省去空格), 所以next_token直接进入到text:标签执行, 执行的结果是state-> text = s = ‘i’; 然后继续执行textresume:标签后面的内容:
标签后面的for死循环中, 发现第一个字符是i,于是执行default分支: *s+ + = *x+ + ; 这样直到import的t被检测完以后, 在下一次循环变得到一个空格, 于是执行x+ + ,并goto textdone.。textdown执行完后函数返回, 返回后, state-> ptr 指向’/’符号 。*s = 0; 意味着state.text就是字符串“import”,因为0就是字符串结束符了。注意返回值为T_TEXT。这个时候执行parse_config函数中的case T_TEXT:分支。
case T_TEXT:
if (nargs <
INIT_PARSER_MAXARGS) {
args[nargs+
+
] =
state.text;
}
这个时候, nargs为0, state.text位import,于是args数组的第0项就存了”import”字符串了。然后nargs+ + ,也就是等于1了。然后进入下次循环。
step 2.第二次循环 第二次循环再次调用next_token函数, 这次state-> ptr= ’\\’, 这我们分析过了。因此next_token函数不断执行defaulty分支, 最终state.text = “/init.environ.rc”, 返回类型还是T_TEXT。于是和之前一样, args[1]= ”/init.environ.rc”,nargs= 2。
step 3.第三次循环 这个时候一行结束, parse_config函数进入case T_NEWLINE:分支。
这个分支中, 首先执行lookup_keyword函数, 从名字来看是查找关键字。肯定就是import了, 它肯定是关键字。不信请看代码:
static int lookup_keyword(const char *s)
{
switch (*s+
+
) {
case '
b'
:
if (!strcmp(s, "
ootchart_init"
)) return K_bootchart_init;
break;
case '
c'
:
if (!strcmp(s, "
opy"
)) return K_copy;
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 '
e'
:
if (!strcmp(s, "
nable"
)) return K_enable;
if (!strcmp(s, "
xec"
)) return K_exec;
if (!strcmp(s, "
xport"
)) return K_export;
break;
case '
g'
:
if (!strcmp(s, "
roup"
)) return K_group;
break;
case '
h'
:
if (!strcmp(s, "
ostname"
)) return K_hostname;
break;
case '
i'
:
if (!strcmp(s, "
oprio"
)) return K_ioprio;
if (!strcmp(s, "
fup"
)) return K_ifup;
if (!strcmp(s, "
nsmod"
)) return K_insmod;
if (!strcmp(s, "
mport"
)) return K_import;
if (!strcmp(s, "
nstallkey"
)) return K_installkey;
break;
case '
k'
:
if (!strcmp(s, "
eycodes"
)) return K_keycodes;
break;
case '
l'
:
if (!strcmp(s, "
oglevel"
)) return K_loglevel;
if (!strcmp(s, "
oad_persist_props"
)) return K_load_persist_props;
if (!strcmp(s, "
oad_all_props"
)) return K_load_all_props;
break;
case '
m'
:
if (!strcmp(s, "
kdir"
)) return K_mkdir;
if (!strcmp(s, "
ount_all"
)) return K_mount_all;
if (!strcmp(s, "
ount"
)) return K_mount;
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 '
p'
:
if (!strcmp(s, "
owerctl"
)) return K_powerctl;
break;
case '
r'
:
if (!strcmp(s, "
estart"
)) return K_restart;
if (!strcmp(s, "
estorecon"
)) return K_restorecon;
if (!strcmp(s, "
estorecon_recursive"
)) return K_restorecon_recursive;
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, "
etenv"
)) return K_setenv;
if (!strcmp(s, "
etprop"
)) return K_setprop;
if (!strcmp(s, "
etrlimit"
)) return K_setrlimit;
if (!strcmp(s, "
ocket"
)) return K_socket;
if (!strcmp(s, "
tart"
)) return K_start;
if (!strcmp(s, "
top"
)) return K_stop;
if (!strcmp(s, "
wapon_all"
)) return K_swapon_all;
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 '
v'
:
if (!strcmp(s, "
erity_load_state"
)) return K_verity_load_state;
if (!strcmp(s, "
erity_update_state"
)) return K_verity_update_state;
break;
case '
w'
:
if (!strcmp(s, "
rite"
)) return K_write;
if (!strcmp(s, "
ritepid"
)) return K_writepid;
if (!strcmp(s, "
ait"
)) return K_wait;
break;
}
return K_UNKNOWN;
}
调用这个函数的时候, 我们传入的参数args[0]= ”import”.显而易见该函数返回K_import。它是一个整数。返回以后使用kw_is函数看他是不是一个Section。当然是一个section了, import也是一个section。不信看代码:
#define kw_is(kw, type) (keyword_info[kw].flags &
(type))
keyword_info定义在system/core/init/keywords.h中:
...
KEYWORD(group,OPTION,0, 0)
KEYWORD(hostname,COMMAND, 1, do_hostname)
KEYWORD(ifup,COMMAND, 1, do_ifup)
KEYWORD(import,SECTION, 1, 0)
...
截取含有import的一部分代码, 后面SECTION已经表明它是个Section了。KEYWORD自后一个参数是这个关键字对应的处理函数。比如这其中的hostname。如果你在init.rc中使用hostname 关键字, 那么最终会调用do_hostname函数来处理。
既然import是一个section。那么parce_config就会调用state.parse_line函数, 这里是一个函数指针, 其实调用的是parse_line_no_op, 不记得回去看下state的初始就知道了。
static void parse_line_no_op(struct parse_state*, int, char**) {
}
这个函数是空的。接下来调用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:
state->
context =
parse_service(state, nargs, args);
if (state->
context) {
state->
parse_line =
parse_line_service;
return;
}
break;
case K_on:
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;
}
我们当然是执行case K_import:分支了, 想都不用想。所以接下来执行parse_import方法:
static void parse_import(struct parse_state *state, int nargs, char **args)
{
struct listnode *import_list =
(listnode*) state->
priv;
char conf_file[PATH_MAX];
int ret;
if (nargs !=
2) {
ERROR("
single argument needed for import\\n"
);
return;
}ret =
expand_props(conf_file, args[1], sizeof(conf_file));
if (ret) {
ERROR("
error while handling import on line '
%d'
in '
%s'
\\n"
,
state->
line, state->
filename);
return;
}struct import* import =
(struct import*) calloc(1, sizeof(struct import));
import->
filename =
strdup(conf_file);
list_add_tail(import_list, &
import->
list);
INFO("
Added '
%s'
to import list\\n"
, import->
filename);
}
这个函数首先使用expand_props方法对args[1]也就是“/init.environ.rc”做进一步处理。这个函数如下:
int expand_props(char *dst, const char *src, int dst_size)
{
char *dst_ptr =
dst;
const char *src_ptr =
src;
int ret =
0;
int left =
dst_size - 1;
if (!src || !dst || dst_size =
=
0)
return -1;
/* - variables can either be $x.y or ${x.y}, in case they are only part
*of the string.
* - will accept $$ as a literal $.
* - no nested property expansion, i.e. ${foo.${bar}} is not supported,
*bad things will happen
*/
while (*src_ptr &
&
left >
0) {
char *c;
char prop[PROP_NAME_MAX +
1];
char prop_val[PROP_VALUE_MAX];
int prop_len =
0;
int prop_val_len;
c =
strchr(src_ptr, '
$'
);
if (!c) {
while (left-- >
0 &
&
*src_ptr)
*(dst_ptr+
+
) =
*(src_ptr+
+
);
break;
}
...
可以看出, 这个函数的作用是拓展args[1].这里不需要拓展, 因为我们的args[1]= ”/init.environ.rc”没有$符号, 所以直接就跳出循环了。这里应该是对那些有包含变量的字符串, 把变量的内容展开。
然后构建了一个import结构体。import中的filename项赋值为”/init.environ.rc”.并把它加入到import_list链表中。
import定义如下:
struct import {
struct listnode list;
const char *filename;
};
这样, 第一行就分析完了, 从而我们也彻底明白了怎么解析一个import 的section。
只要能看懂一个, 其他的就简单了。因为, 他们都是类似的。
service的解析与启动 service的解析 和import解析过程类似, 遇到service关键字后, service关键字和后面的参数会保存在args[]数组中。然后通过对args[0]提取关键字, 发现args[0]= ”service”,于是开始执行parse_new_section函数。此时这个函数必然会进入 case K_service:分支执行:
case K_service:
state->
context =
parse_service(state, nargs, args);
if (state->
context) {
state->
parse_line =
parse_line_service;
return;
}
break;
这里做了两件事情非常重要, 一件是调用parse_service解析service这个section。另一件事情是给state-> parse_line赋值为parse_line_service。也就是service 关键字所在的行后面的那些options行都是使用parse_line_service函数来解析的。我们从parse_service看起:
static void *parse_service(struct parse_state *state, int nargs, char **args)
{
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;
}service* svc =
(service*) 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 =
(service*) calloc(1, sizeof(*svc) +
sizeof(char*) * nargs);
if (!svc) {
parse_error(state, "
out of memory\\n"
);
return 0;
}
svc->
name =
strdup(args[1]);
svc->
classname =
"
default"
;
memcpy(svc->
args, args +
2, sizeof(char*) * nargs);
trigger* cur_trigger =
(trigger*) calloc(1, sizeof(*cur_trigger));
svc->
args[nargs] =
0;
svc->
nargs =
nargs;
list_init(&
svc->
onrestart.triggers);
cur_trigger->
name =
"
onrestart"
;
list_add_tail(&
svc->
onrestart.triggers, &
cur_trigger->
nlist);
list_init(&
svc->
onrestart.commands);
list_add_tail(&
service_list, &
svc->
slist);
return svc;
}
可以看到和import做的事情差不多。import解析的最后, 会创建一个import结构体, 并把它添加到import_list双向链表中。service解析从这里看, 也是构建一个service结构体, 然后把service结构体添加到service_list链表中。
我们看下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];
};
/*
【Android6.0系统启动流程分析一(init进程)】socketinfo 用来保存socket option的相关信息。
classname 给service定义一个类名, 如果多个service使用相同的类型, 可以方便进行批量操作。
nargs 保存参数的个数。
很多字段不理解, 没关系, 我们看看parse_service函数给service做了那些初始化:
1. svc-> name = strdup(args[1]); 名字就是service 关键字后面的第一个参数
2. svc-> classname = “default”; 类别名是default
3. memcpy(svc-> args, args + 2, sizeof(char*) * nargs); svc-> args[nargs] = 0;
把所有参数保存在args数组中, 并把最有一个成员只为0。
4. svc-> nargs = nargs; nargs保存参数个数
5. trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
list_init(& svc-> onrestart.triggers);
cur_trigger-> name = “onrestart”;
list_add_tail(& svc-> onrestart.triggers, & cur_trigger-> nlist);
构建一个触发器, 并把它添加到service中的onrestart.triger列表中。
因此, 我们可以知道么一个service都会有一个onrestart的action,这个action有一个触发器。这个action用来重启service。
解析完service后, 就会解析service后面的option了, 这个时候会调用parse_line_service。
static void parse_line_service(struct parse_state *state, int nargs, char **args)
{
struct service *svc =
(service*) 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_class:
if (nargs !=
2) {
pars
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Android三级缓存机制工具类的实现
- 欢迎大家提问Android技术及职业生涯等问题
- Android图表库MPAndroidChart—饼状图可以再简单一点
- Daydream VR入门基础教程,学习Google VR for Android全景应用示例SimpleVrPanorama制作VR全景应用
- Android无限广告轮播 - 自定义BannerView
- Java如何实现多线程聊天应用程序(S1(服务端编程))
- 如何使用ArrayList打印字符串的所有子序列()
- 飞行时间(ToF)传感器解读和指南
- CSS如何使用var()函数(代码示例)