Android4.4的init进程
1背景
前些日子需要在科室内做关于Android系统启动流程的培训。为此,我在几年前的技术手记的基础上,重新改了一份培训文档。在重新整理文档期间,我也重读了一下Android 4.4的相关代码,发现还有一些东西是我以前一直没重视过的,所以打算写下来总结一二。
我以前之所以没有把关于Android系统启动方面的手记整理成博文,主要是因为网上已经有许多类似的文章了,再说一遍好像也没什么意思。但这次的培训既然已迫使我重整了一份文档,那么倒也不妨贴出来供大家参考。文中的某些细节是我最近新补充的内容,这样或许能和网上其他文章有所区别吧。
2概述init进程
我们先概述一下Android的init进程。init是Linux系统中,用户空间的第一个进程。它负责创建系统中最关键的几个子进程,尤其是zygote。另外,init还提供了property service(属性服务),类似于windows系统的注册表服务。有关属性服务的细节,大家可参考我写的《Android Property机制》一文,本文就不多说了。
在Android系统中,会有个init.rc脚本。Init进程一启动就会读取并解析这个脚本文件,把其中的元素整理成自己的数据结构(链表)。具体情况可参考system\core\init\init.c文件,它的main()函数会先调用init_parse_config_file(“/init.rc”)来解析init.rc脚本,分析出应该执行的语义,并且把脚本中描述的action和service信息分别组织成双向链表,然后执行之。示意图如下:
3解析init.rc脚本
3.1介绍init.rc脚本
Init.rc脚本使用的是一种初始化语言,其中包含了4类声明:
1)Action
2)Command
3)Service
4)Option
该语言规定,Action和Service是以一种“小节”(Section)的形式出现的,其中每个Action小节可以含有若干Command,而每个Service小节可以含有若干Option。小节只有起始标记,却没有明确的结束标记,也就是说,是用“后一个小节”的起始来结束“前一个小节”的。
脚本中的Action大体上表示一个“行动”,它用一系列Command共同完成该“行动”。Action需要有一个触发器(trigger)来触发它,一旦满足了触发条件,这个Action就会被加到执行队列的末尾。Action的形式如下:
- on <trigger>
- <command1>
- <command2>
- ......
Service表示一个服务程序,会在初始化时启动。因为init.rc脚本中描述的服务往往都是核心服务,所以(基本上所有的)服务会在退出时自动重启。Service的形式如下:
- service <name> <pathname> [<arguments>]*
- <option>
- <option>
- ......
Init.rc中的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 vold /system/bin/vold
- class core
- socket vold stream 0660 root mount
- ioprio be 2
- service netd /system/bin/netd
- class main
- socket netd stream 0660 root system
- socket dnsproxyd stream 0660 root inet
- socket mdns stream 0660 root system
请大家留心service里的class选项,比如上面的class core和class main。它表示该service是属于哪种类型的服务。在后文的阐述boot子阶段时,会用到这个概念。
其实,除了Action和Service,Init.rc中还有一种小节,就是Import小节。该小节表达的意思有点儿像java中的import,也就是说,Init.rc中还可以导入其他.rc脚本文件的内容。在早期的Android中,好像并不支持import语句,不过至少从Android4.0开始,添加了import语句。至于import最早出现在哪个版本,我没有考证过。import句子截选如下:
- import /init.environ.rc
- import /init.usb.rc
- import /init.${ro.hardware}.rc
- import /init.trace.rc
3.2解析
在init进程的main()函数里,会调用init_parse_config_file("/init.rc")一句来解析init.rc脚本。init_parse_config_file()的代码如下:
【system/core/init/Init_parser.c】
- 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;
- }
先用read_file()把脚本内容读入一块内存,而后调用parse_config()解析这块内存。
parse_config()的代码截选如下:
- static void parse_config(const char *fn, char *s)
- {
- . . . . . .
- for (;;) {
- switch (next_token(&state)) {
- . . . . . .
- case T_NEWLINE: // 遇到折行
- <span style="white-space:pre"> </span> state.line++;
- if (nargs) {
- <span style="white-space:pre"> </span> int kw = lookup_keyword(args[0]);
- if (kw_is(kw, SECTION)) {
- state.parse_line(&state, 0, 0); // 不同section的parse_line也不同噢
- parse_new_section(&state, kw, nargs, args);
- } else {
- state.parse_line(&state, nargs, args);
- }
- nargs = 0;
- }
- break;
- . . . . . .
- . . . . . .
- }
它在逐行分析init.rc脚本,判断每一行的第一个参数是什么类型的,如果是action或service类型的,就表示要创建一个新的section节点了,此时它会设置一下解析后续行的解析函数,也就是给state->parse_line赋值啦。针对service类型,解析后续行的函数是parse_line_service(),而针对action类型,解析后续行的函数则是parse_line_action()。
这么看来,parse_config()里有3个地方值得我们注意:
- lookup_keyword()和kw_is()
- parse_new_section()
- state.parse_line()
3.2.1查询脚本关键字
我们先介绍关于关键字查找方面的知识,在这里主要看lookup_keyword()和kw_is()。
lookup_keyword()的定义截选如下:
【system/core/init/Init_parser.c】
- 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;
- . . . . . .
- . . . . . .
kw_is()宏的定义如下:
- #define kw_is(kw, type) (keyword_info[kw].flags & (type))
基本上是查表的过程,而lookup_keyword()返回的那些K_copy、K_capability值,其实就是表项的索引号。这张关键字表的技术细节如下。
在init_parser.c文件中有下面这样的代码:
【system/core/init/Init_parser.c】
- #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"
- };
- #undef KEYWORD
这里用到了一点儿小技巧,两次include了keywords.h头文件,其实keywords.h中会先定义一次KEYWORD宏,其主要目的是为了形成一个顺序排列的enum,而后就#undef KEYWORD了。接着上面代码中再次定义了KEYWORD宏,这次的主要目的是为了形成一个struct数组,即keyword_info数组。
keywords.h的部分截选如下:
【system/core/init/Keywords.h】
- #ifndef KEYWORD
- int do_chroot(int nargs, char **args);
- int do_chdir(int nargs, char **args);
- int do_class_start(int nargs, char **args);
- . . . . . .
- . . . . . .
- #define __MAKE_KEYWORD_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)
- . . . . . .
- . . . . . .
- #ifdef __MAKE_KEYWORD_ENUM__
- KEYWORD_COUNT,
- };
- #undef __MAKE_KEYWORD_ENUM__
- #undef KEYWORD
- #endif
其中的#define KEYWORD是第一次定义KEYWORD,我们比对一下这两次定义:
- // 第一次
- #define KEYWORD(symbol, flags, nargs, func) K_##symbol,
- // 第二次
- #define KEYWORD(symbol, flags, nargs, func) \
- [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
总之,最后形成了如下数组:
表中只有3个表项的flag是SECTION,表示这是个小节,我用黄色框表示。
3.2.2解析section小节
一旦分析出某句脚本是以on或者service或者import开始,就说明一个新的小节要开始了。此时,会调用到parse_new_section(),该函数的代码如下:
- 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;
- }
很明显,解析的小节就是那三类:action小节(以on开头的),service小节和import小节。最核心的部分当然是service小节和action小节,具体解析的地方在上面代码中的parse_service()和parse_action()函数里。至于import小节,parse_import()函数只是把脚本中的所有import语句先汇总成一个链表,记入state结构中,待回到parse_config()后再做处理。
3.2.2.1解析service小节
parse_service()的代码如下:
【system/core/init/Init_parser.c】
- static void *parse_service(struct parse_state *state, int nargs, char **args)
- {
- struct service *svc;
- . . . . . .
- 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;
- }
解析service段时,会用calloc()申请一个service节点,填入service名等信息,并连入service_list总表中。注意,此时该service节点的onrestart.commands部分还是个空链表,因为我们还没有分析该service的后续脚本行呢。
parse_new_section()中为service明确指定了解析后续行的函数parse_line_service()。该函数的代码截选如下:
- static void parse_line_service(struct parse_state *state, int nargs, char **args)
- {
- struct service *svc = state->context;
- struct command *cmd;
- . . . . . .
- kw = lookup_keyword(args[0]); // 解析具体的service option也是要查关键字表的
- 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:
- . . . . . .
- . . . . . .
service的各个option会影响service节点的不同域,比如flags域、classname域、onrestart域等等。比较麻烦的是onrestart域,因为它本身又是个action节点,可携带若干个子command。
下面是service中常见的option:
1)K_capability
2)K_class
3)K_console
4)K_disabled
5)K_ioprio
6)K_group
7)K_user
8)K_keycodes
9)K_oneshot
10)K_onrestart
11)K_critical
12)K_setenv
13)K_socket
14)K_seclabel
在service小节解析完毕后,我们应该能得到类似下图这样的service节点:
3.2.2.2解析action小节
另一方面,解析action小节时的动作也很简单,会用calloc()申请一个action节点,填入action名等信息,然后连入action_list总表中。当然,此时action的commands部分也是空的。
- static void *parse_action(struct parse_state *state, int nargs, char **args)
- {
- struct action *act;
- . . . . . .
- act = calloc(1, sizeof(*act));
- act->name = args[1];
- list_init(&act->commands);
- list_init(&act->qlist);
- list_add_tail(&action_list, &act->alist);
- return act;
- }
对于action小节而言,我们指定了不同的解析后续行的函数,也就是parse_line_action()。该函数的代码截选如下:
- static void parse_line_action(struct parse_state* state, int nargs, char **args)
- {
- struct command *cmd;
- struct action *act = state->context;
- . . . . . .
- kw = lookup_keyword(args[0]); // 解析具体的action command也是要查关键字表的
- if (!kw_is(kw, COMMAND)) {
- parse_error(state, "invalid command '%s'\n", args[0]);
- return;
- }
- n = kw_nargs(kw);
- if (nargs < n) {
- parse_error(state, "%s requires %d %s\n", args[0], n - 1,
- n > 2 ? "arguments" : "argument");
- return;
- }
- cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
- cmd->func = kw_func(kw);
- cmd->nargs = nargs;
- memcpy(cmd->args, args, sizeof(char*) * nargs);
- list_add_tail(&act->commands, &cmd->clist);
- }
既然action的后续行可以包含多条command,那么parse_line_action()就必须先确定出当前分析的是什么command,这一点和parse_line_service()是一致的,都是通过调用lookup_keyword()来查询关键字的。另外,command子行的所有参数其实已被记入传进来的args参数,现在这些参数会记入command节点的args域中,而且这个command节点会链入action节点的commands链表尾部。
在action小节解析完毕后,我们应该能得到类似下图这样的action节点:
3.2.3主要形成两个双向链表
我们画了一张关于parse_config()的调用关系图,如下:
init_parse_config_file()函数会将Init.rc脚本解析成两个双向链表,对应的表头分别是service_list和action_list。双向链表示意图如下:
3.3具体执行那些action
经过解析一步,init.rc脚本中的actions被整理成双向链表了,但是这些action并没有被实际执行。现在我们就来看下一步具体执行action的流程。
在init进程的main()函数中,我们可以看到如下句子:
- int main(int argc, char **argv)
- {
- . . . . . .
- . . . . . .
- init_parse_config_file("/init.rc"); // 内部将脚本内容转换成action链表了
- action_for_each_trigger("early-init", action_add_queue_tail);
- queue_builtin_action(wait_for_coldboot_done_action,
- "wait_for_coldboot_done");
- 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");
- /* execute all the boot actions to get us started */
- action_for_each_trigger("init", action_add_queue_tail);
- . . . . . .
- . . . . . .
- }
首先,init_parse_config_file()已经把init.rc脚本里的内容转换成action链表了,接着代码运行到action_for_each_trigger(“early-init”...)一句,这一句会把action_list列表中匹配的action节点,连入action_queue队列。
3.3.1整理action_queue队列
init进程希望把系统初始化过程分割成若干“子阶段”,action_for_each_trigger()的意思就是“触发某个子阶段里的所有action”。在早期的Android中,大概就只有4、5个子阶段,现在随着Android的不断升级,子阶段也变得越来越多了。
action_for_each_trigger()的代码如下:
- void action_for_each_trigger(const char *trigger,
- void (*func)(struct action *act))
- {
- struct listnode *node;
- struct action *act;
- list_for_each(node, &action_list) {
- act = node_to_item(node, struct action, alist);
- if (!strcmp(act->name, trigger)) {
- func(act); // 只要匹配,就回调func
- }
- }
- }
可以看到是在遍历action_list链表,找寻所有“action名”和“参数trigger”匹配的节点,并回调“参数func所指的回调函数”。在前面的代码中,回调函数就是action_add_queue_tail()。
- void action_add_queue_tail(struct action *act)
- {
- if (list_empty(&act->qlist)) {
- list_add_tail(&action_queue, &act->qlist);
- }
- }
嗯,这里又出现了个action_queue队列!它和action_list列表有什么关系?
其实很简单,action_list可以被理解成一个来自init.rc的“草稿列表”,列表中的节点顺序基本上和init.rc脚本里编写section时的顺序一致,而这个顺序不一定就是合适的“运行顺序”,所以我们需要另一个按我们的要求依次串接的队列,那就是action_queue队列。另外,有些新的action并没有体现在init.rc脚本里,而是写在具体代码里的,这些action可以被称为“内建action”,我们可以通过调用queue_builtin_action()将“内建action”添加进action_list列表和action_queue队列中。
queue_builtin_action()的代码如下:
- void queue_builtin_action(int (*func)(int nargs, char **args), char *name)
- {
- struct action *act;
- struct command *cmd;
- act = calloc(1, sizeof(*act));
- act->name = name;
- list_init(&act->commands);
- list_init(&act->qlist);
- cmd = calloc(1, sizeof(*cmd));
- cmd->func = func;
- cmd->args[0] = name;
- list_add_tail(&act->commands, &cmd->clist);
- list_add_tail(&action_list, &act->alist);
- action_add_queue_tail(act);
- }
init进程里主要分割的“子阶段”如下图所示:
桔色方框表示的子阶段,是比较重要的阶段。
3.3.1.1early-init子阶段
我们先看early-init子阶段,这部分在init.rc里是这样表达的:
- 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
- # create mountpoints
- mkdir /mnt 0775 root system
这个action包含4条command,分别是write、setcon、start和mkdir。不同command对应的func回调函数也是不同的,具体对应什么,可以查看Keywords.h。
【system/core/init/Keywords.h】
- 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)
比如说start命令对应的回调函数就是do_start():
- int do_start(int nargs, char **args)
- {
- struct service *svc;
- svc = service_find_by_name(args[1]);
- if (svc) {
- service_start(svc, NULL);
- }
- return 0;
- }
启动所指定的service。
3.3.1.2boot子阶段
boot部分在init.rc里是这样表达的:
- on boot
- ifup lo
- hostname localhost
- domainname localdomain
- setrlimit 13 40 40
- . . . . . .
- write /proc/sys/vm/overcommit_memory 1
- write /proc/sys/vm/min_free_order_shift 4
- chown root system /sys/module/lowmemorykiller/parameters/adj
- chmod 0664 /sys/module/lowmemorykiller/parameters/adj
- . . . . . .
- . . . . . .
- setprop net.tcp.buffersize.default 4096,87380,110208,4096,16384,110208
- setprop net.tcp.buffersize.wifi
- 524288,1048576,2097152,262144,524288,1048576
- . . . . . .
- setprop net.tcp.default_init_rwnd 60
- class_start core
- class_start main
class_start命令对应的回调函数是do_class_start(),该函数的代码如下:
【system/core/init/Builtins.c】
- int do_class_start(int nargs, char **args)
- {
- service_for_each_class(args[1], service_start_if_not_disabled);
- return 0;
- }
- 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); // 回调service_start_if_not_disabled()
- }
- }
- }
- static void service_start_if_not_disabled(struct service *svc)
- {
- if (!(svc->flags & SVC_DISABLED)) {
- service_start(svc, NULL);
- }
- }
boot子阶段先启动的“core”类型的服务有:
core类型的服务 | 对应的可执行文件 | 说明 |
ueventd | /sbin/ueventd | |
healthd | /sbin/healthd | |
console | /system/bin/sh | |
adbd | /sbin/adbd | |
servicemanager | /system/bin/servicemanager | 大名鼎鼎的service manager service服务,Android的核心之一。 |
vold | /system/bin/vold |
而后,boot子阶段启动的“main”类型的服务有:
main类型的服务 | 对应的可执行文件 | 说明 |
netd | /system/bin/netd | |
debuggerd | /system/bin/debuggerd | |
ril-daemon | /system/bin/rild | |
surfaceflinger | /system/bin/surfaceflinger | |
zygote | /system/bin/app_process | Android创建内部创建新进程的核心服务。 |
drm | /system/bin/drmserver | |
media | /system/bin/mediaserver | |
bootanim | /system/bin/bootanimation | |
installd | /system/bin/installd | |
flash_recovery | /system/etc/install-recovery.sh | |
racoon | /system/bin/racoon | |
mtpd | /system/bin/mtpd | |
keystore | /system/bin/keystore | |
dumpstate | /system/bin/dumpstate | |
sshd | /system/bin/start-ssh | |
mdnsd | /system/bin/mdnsd |
3.3.2for循环中执行action_queue队列
现在我们继续看,动作在编排进action_queue队列之后,又是如何执行的呢?我们知道,init进程最终会进入一个for(;;)循环,在这个循环中,每次都会尝试执行一个command:
- int main(int argc, char **argv)
- {
- . . . . . .
- . . . . . .
- // 这个for循环非常重要哦!
- for(;;) {
- int nr, i, timeout = -1;
- execute_one_command();
- restart_processes();
- . . . . . .
- }
- void execute_one_command(void)
- {
- int ret;
- if (!cur_action || !cur_command || is_last_command(cur_action, cur_command))
- {
- cur_action = action_remove_queue_head();
- cur_command = NULL;
- if (!cur_action)
- return;
- INFO("processing action %p (%s)\n", cur_action, cur_action->name);
- cur_command = get_first_command(cur_action);
- } else {
- cur_command = get_next_command(cur_action, cur_command);
- }
- if (!cur_command)
- return;
- ret = cur_command->func(cur_command->nargs, cur_command->args);
- INFO("command '%s' r=%d\n", cur_command->args[0], ret);
- }
它的意思是说,执行“当前action”(cur_action)的“当前command”(cur_command)。如果执行时没有“当前action”,就尝试从action_queue队列的头部摘取一个节点。如果执行时没有“当前command”,就从“当前action”中获取下一个该执行的command。而一旦得到了该执行的command,就回调其func函数指针。
在那几个core类型的service中,有一个非常重要的service,叫做zygote,它是android内部创建新进程的核心服务,但本文就不对它细说了。
4补充说明几个运作机理知识
下面我们补充说明几个init进程里的运作机理。
4.1service是如何重启的?
关于service的重启方法,其实用到了linux的一点儿信号机制。在init进程的main()函数中,除了“early-init”、“init”等子阶段外,还有个子阶段叫作“signal_init”:
- queue_builtin_action(signal_init_action, "signal_init");
【system/core/init/Init.c】
- static int signal_init_action(int nargs, char **args)
- {
- signal_init();
- return 0;
- }
- void signal_init(void)
- {
- int s[2];
- struct sigaction act;
- memset(&act, 0, sizeof(act));
- act.sa_handler = sigchld_handler;
- act.sa_flags = SA_NOCLDSTOP;
- sigaction(SIGCHLD, &act, 0); // 向系统注册一个系统回调
- /* create a signalling mechanism for the sigchld handler */
- if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
- signal_fd = s[0]; // 以后回调函数会向这个fd写数据
- signal_recv_fd = s[1];
- fcntl(s[0], F_SETFD, FD_CLOEXEC);
- fcntl(s[0], F_SETFL, O_NONBLOCK);
- fcntl(s[1], F_SETFD, FD_CLOEXEC);
- fcntl(s[1], F_SETFL, O_NONBLOCK);
- }
- handle_signal();
- }
请注意,signal_init()中调用了sigaction(SIGCHLD,...)一句。在linux系统中,当一个进程终止或者停止时,系统会向其父进程发送SIGCHLD信号。sigaction()动作可以被理解为向系统注册一个系统回调函数。在本例中,每当有子进程终止时,系统就会回调sigchld_handler()回调函数,该函数的代码如下:
【system/core/init/Signal_handler.c】
- static void sigchld_handler(int s)
- {
- write(signal_fd, &s, 1);
- }
看到了吗?无非是向signal_init()中创建的“socket对”里的signal_fd写数据,于是“socket对”的另一个句柄signal_recv_fd就可以得到所写的数据。
在init进程的main()函数中,最终进入那个无限for循环,监听系统的风吹草动,其中就包括监听这个signal_recv_fd:
- int main(int argc, char **argv)
- {
- . . . . . .
- . . . . . .
- for(;;) {
- . . . . . .
- if (!signal_fd_init && get_signal_fd() > 0) {
- ufds[fd_count].fd = get_signal_fd(); // 就是signal_recv_fd !
- ufds[fd_count].events = POLLIN;
- ufds[fd_count].revents = 0;
- fd_count++;
- signal_fd_init = 1;
- }
- . . . . . .
- . . . . . .
- nr = poll(ufds, fd_count, timeout);
- . . . . . .
- for (i = 0; i < fd_count; i++) {
- if (ufds[i].revents == POLLIN) {
- if (ufds[i].fd == get_property_set_fd())
- handle_property_set_fd(); // 处理设置属性的命令
- else if (ufds[i].fd == get_keychord_fd())
- handle_keychord(); // 处理类似混合按键的命令,类似同时按
- // 钢琴上的若干键
- else if (ufds[i].fd == get_signal_fd())
- handle_signal(); // 处理因子进程挂掉而发来的信号
- }
- }
- }
- . . . . . .
- }
当监听到signal_recv_fd有动静时,会调用handle_signal()来处理:
- void handle_signal(void)
- {
- char tmp[32];
- /* we got a SIGCHLD - reap and restart as needed */
- read(signal_recv_fd, tmp, sizeof(tmp));
- while (!wait_for_one_process(0))
- ;
- }
wait_for_one_process()的代码截选如下:
- static int wait_for_one_process(int block)
- {
- . . . . . .
- while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1
- && errno == EINTR );
- . . . . . .
- svc = service_find_by_pid(pid); // 查询出是哪个service进程挂掉了
- . . . . . .
- svc->pid = 0;
- svc->flags &= (~SVC_RUNNING);
- if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
- svc->flags |= SVC_DISABLED;
- }
- if (svc->flags & (SVC_DISABLED | SVC_RESET) ) {
- notify_service_state(svc->name, "stopped");
- return 0;
- }
- . . . . . .
- svc->flags &= (~SVC_RESTART);
- svc->flags |= SVC_RESTARTING;
- /* Execute all onrestart commands for this service. */
- list_for_each(node, &svc->onrestart.commands) {
- cmd = node_to_item(node, struct command, clist);
- cmd->func(cmd->nargs, cmd->args);
- }
- notify_service_state(svc->name, "restarting");
- return 0;
- }
该函数的代码比较清晰,当init进程被通知某个子进程终止时,它会尝试找到这个子进程对应的service节点,并辗转给该节点的flags域添加SVC_RESTARTING标记,然后又会马上执行这个service节点中所有onrestart选项对应的动作。
代码中处理SVC_ONESHOT的地方多判断了SVC_RESTART标志,这是为什么呢?我想理由是这样的:SVC_ONESHOT表达的意思是“只打一枪”,也就是说以它装饰的service进程,就算挂掉了,也不会重新启动。然而必须兼顾到其他进程restart的情况。假如有另一个进程会连锁restart该service,此时就算该service有SVC_ONESHOT标志,它还是应该再次启动的。
svc节点的onrestart域本身就是个action类型的域:
- struct action onrestart;
现在开始遍历onrestart域里的commands列表:
- list_for_each(node, &svc->onrestart.commands) {
- cmd = node_to_item(node, struct command, clist);
- cmd->func(cmd->nargs, cmd->args);
- }
看来,service的那些onrestart子句是一次性完成的。我们以前文说的zygote服务为例,当它重启时,会执行两次do_write()以及两次do_start(),分别启动media服务和netd服务。
最后,wait_for_one_process()还会调用一下notify_service_state()。毕竟这是因为某个service挂掉了,才会再走到这里的,现在我们马上就要重新启动那个刚死的service啦,所以最好还是做一些必要的“通知动作”。请注意,这种关于重启service的“通知”并不是简单发个事件什么的,而是设置某个相应的系统属性。具体的动作请看notify_service_state()的代码:
- void notify_service_state(const char *name, const char *state)
- {
- char pname[PROP_NAME_MAX];
- int len = strlen(name);
- if ((len + 10) > PROP_NAME_MAX)
- return;
- snprintf(pname, sizeof(pname), "init.svc.%s", name);
- property_set(pname, state);
- }
看到了吗?会设置一个以“init.svc.”打头的系统属性。比如重启zygote服务,此时就会把“init.svc.zygote”属性值设为“SVC_RESTARTING”。
大家有没有注意到,wait_for_one_process()里根本没有fork动作。这也就是说,wait_for_one_process()中并不会立即重启新的service进程。大家都知道现在我们正处于init进程的无限for循环中,所以程序从wait_for_one_process()返回后,总会再次走到for循环中的restart_processes():
- int main(int argc, char **argv)
- {
- . . . . .
- for(;;) {
- int nr, i, timeout = -1;
- execute_one_command();
- restart_processes();
此时才会重启新的进程:
- static void restart_processes()
- {
- process_needs_restart = 0;
- service_for_each_flags(SVC_RESTARTING,
- restart_service_if_needed);
- }
遍历service_list列表,找出那些flags中携带有SVC_RESTARTING标志的service节点,并执行restart_service_if_needed()。
- static void restart_service_if_needed(struct service *svc)
- {
- time_t next_start_time = svc->time_started + 5;
- if (next_start_time <= gettime()) {
- svc->flags &= (~SVC_RESTARTING);
- service_start(svc, NULL);
- return;
- }
- if ((next_start_time < process_needs_restart) ||
- (process_needs_restart == 0)) {
- process_needs_restart = next_start_time;
- }
- }
注意,为了防止出现service连续紧密重启的情况,next_start_time会赋值为svc->time_started + 5,也就是说,至少得喘息个5毫秒,然后才能进行下一次重启。这就是Android中重启service的具体流程。
4.2混合按键是如何启动service的?
现在我们顺便说一下用混合按键重启service的技术,这部分内容现在已经很少用到了。至少在我们常见的项目的init.rc脚本里是搜不到“keycodes”关键字的。这个关键字是个option,如果某个service里含有keycodes选项的话,就说明设计者希望在用户按下某种组合键时,init进程能重启这个service。
这种能点击出的组合键,很像同时按下几个钢琴键而发出和旋,因此被称为keychord。在init进程的启动子过程中,“keychord(初始化)子阶段”甚至还要早于“init子阶段”呢。
- queue_builtin_action(keychord_init_action, "keychord_init");
【system/core/init/Init.c】
- static int keychord_init_action(int nargs, char **args)
- {
- keychord_init();
- return 0;
- }
- void keychord_init()
- {
- int fd, ret;
- service_for_each(add_service_keycodes);
- if (!keychords)
- return;
- fd = open("/dev/keychord", O_RDWR);
- if (fd < 0) {
- ERROR("could not open /dev/keychord\n");
- return;
- }
- fcntl(fd, F_SETFD, FD_CLOEXEC);
- ret = write(fd, keychords, keychords_length);
- if (ret != keychords_length) {
- ERROR("could not configure /dev/keychord %d (%d)\n", ret, errno);
- close(fd);
- fd = -1;
- }
- free(keychords);
- keychords = 0;
- keychord_fd = fd;
- }
【system/core/init/Keychords.c】
- void add_service_keycodes(struct service *svc)
- {
- struct input_keychord *keychord;
- int i, size;
- if (svc->keycodes) {
- /* add a new keychord to the list */
- size = sizeof(*keychord) +
- svc->nkeycodes * sizeof(keychord->keycodes[0]);
- keychords = realloc(keychords, keychords_length + size);
- if (!keychords) {
- ERROR("could not allocate keychords\n");
- keychords_length = 0;
- keychords_count = 0;
- return;
- }
- keychord = (struct input_keychord *)
- ((char *)keychords + keychords_length);
- keychord->version = KEYCHORD_VERSION;
- keychord->id = keychords_count + 1;
- keychord->count = svc->nkeycodes;
- svc->keychord_id = keychord->id;
- for (i = 0; i < svc->nkeycodes; i++) {
- keychord->keycodes[i] = svc->keycodes[i];
- }
- keychords_count++;
- keychords_length += size;
- }
- }
其中用到的keychords是个静态变量:
- static struct input_keychord *keychords = 0;
它实质上指向了一块buffer,该buffer最终会存下所有keychord信息。当我们遍历service_list列表时,一旦发现某个service节点携带有keycodes,就会从这个buffer中划分出一块,并在其中写入从service节点读取到的keycodes信息。因为不同service携带的keycode部分可能不一样,所以每次分出的那块内存的大小也不太一样。不过大体上每一小块记录的都是input_keychord结构,该结构的定义如下:
【kernel/include/linux/Keychord.h】
- struct input_keychord {
- __u16 version;
- __u16 id;
- __u16 count;
- __u16 keycodes[];
- };
另外,请注意上面代码中的这几句:
- keychord->id = keychords_count + 1;
- keychord->count = svc->nkeycodes;
- svc->keychord_id = keychord->id;
keychord信息里有个唯一的id号,而且这个id号还会回写到service节点的keychord_id域。
经过这次遍历,我们大体上可以画出下面这样的示意图:
在整理好keychords这块buffer后,keychord_init()会把它写入“/dev/keychord”设备文件。
- fd = open("/dev/keychord", O_RDWR);
- . . . . . .
- ret = write(fd, keychords, keychords_length);
这应该是向驱动层通知重要信息了。而且请注意,这个fd文件描述符会被记录下来:
- keychord_fd = fd;
记录下fd有什么用呢?很简单,init进程在最后那个for循环里,会监听这个fd,从而感知到从驱动层发来的混合按键,代码如下:
- if (!keychord_fd_init && get_keychord_fd() > 0) {
- ufds[fd_count].fd = get_keychord_fd(); // 得到的就是那个keychord文件描述符
- ufds[fd_count].events = POLLIN;
- ufds[fd_count].revents = 0;
- fd_count++;
- keychord_fd_init = 1;
- }
一旦监听到有混合按键发生了,就会走到下面的handle_keychord():
- for (i = 0; i < fd_count; i++) {
- if (ufds[i].revents == POLLIN) {
- if (ufds[i].fd == get_property_set_fd())
- handle_property_set_fd();
- else if (ufds[i].fd == get_keychord_fd())
- handle_keychord(); // 处理混合按键
- else if (ufds[i].fd == get_signal_fd())
- handle_signal();
- }
- }
【system/core/init/Keychords.c】
- void handle_keychord()
- {
- struct service *svc;
- char adb_enabled[PROP_VALUE_MAX];
- int ret;
- __u16 id;
- // Only handle keychords if adb is enabled.
- property_get("init.svc.adbd", adb_enabled);
- ret = read(keychord_fd, &id, sizeof(id));
- if (ret != sizeof(id)) {
- ERROR("could not read keychord id\n");
- return;
- }
- if (!strcmp(adb_enabled, "running")) {
- svc = service_find_by_keychord(id);
- if (svc) {
- INFO("starting service %s from keychord\n", svc->name);
- service_start(svc, NULL);
- } else {
- ERROR("service for keychord %d not found\n", id);
- }
- }
- }
此时会从/dev/keychord设备文件里读取一个id号,还记得前文说到的“id号会回写到service节点的keychord_id域”吗,现在会再次遍历service_list列表,找到那个keychord_id和读到的id匹配的service节点,然后调用service_start(svc, NULL)启动这个service。
5小结
关于init进程,我们就先说这么多吧。限于篇幅,我们不得不把很多不那么重要的细节省去,有兴趣的同学可以自行深入研究。
转自http://blog.csdn.net/codefly/article/details/48392745
Android4.4的init进程的更多相关文章
- Android4.4的zygote进程(下)
3.2.4启动Android系统服务——startSystemServer() 接下来就是启动Android的重头戏了,此时ZygoteInit的main()函数会调用startSystemServe ...
- Android4.4的zygote进程(上)
1背景 前些天为了在科室做培训,我基于Android 4.4重新整理了一份关于zygote的文档.从技术的角度看,这几年zygote并没有出现什么大的变化,所以如果有人以前研究过zygote,应该不会 ...
- Android系统init进程启动及init.rc全解析
转:https://blog.csdn.net/zhonglunshun/article/details/78615980 服务启动机制system/core/init/init.c文件main函数中 ...
- Linux---从start_kernel到init进程启动
“平安的祝福 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ” ini ...
- Linux init进程详解
init模块 一般来说,Linux程序只能用另一个Linux程序启动.例如,登录Linux终端程序Mingetty. 但终端程序又由谁启动呢?在计算机上启动Linux时,内核装入并启动init程序. ...
- imx6 启动 init进程
之前不知道imx6内核是怎么启动文件系统的init进程,查了下资料,记录于此,以后再来补充. kernel/init/main.c static noinline int init_post(void ...
- Android init进程概述
init进程,其程序位于根文件系统中,在kernle自行启动后,其中的 start_kernel 函数把根文件系统挂载到/目录后,在 rest_init 函数中通过 kernel_thread(ker ...
- init进程解析rc文件的相关函数分析
init进程的源码文件位于system/core/init,其中解析rc文件语法的代码放在五个函数中, init_parse_config_file (init_parser.c), read_fil ...
- init进程学习
linux的init进程 一个在线编辑markdown文档的编辑器,是内核启动的第一个进程,init进程有很多重要的任务,它的pit 为1,在linux shell中使用pstree命令可以看到它为其 ...
随机推荐
- Unity3d通用工具类之定时触发器
时隔多日,好不容易挤出点时间来写写博文.不容易,请送我几朵红花,点个赞也行. 今天呢,我们主要来扩展下通用工具类==>定时触发器. 顾名思义,所谓的定时触发器,就是告诉程序在过多长时间后,我要执 ...
- Android内存优化1 了解java内存分配 1
开篇废话 今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础. 一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析. 欢迎访 ...
- python编码规范、js编码规范及IDE的检查插件pylint/eslint等
一.python规范 参考:https://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/的风格规范和语 ...
- HDU 4886 TIANKENG’s restaurant(Ⅱ) hash+dfs
题意: 1.找一个字符串s使得 s不是给定母串的子串 2.且s要最短 3.s在最短情况下字典序最小 hash.,,结果t掉了...加了个姿势怪异的hash值剪枝才过.. #include <cs ...
- Python爬虫之一 PySpider 抓取淘宝MM的个人信息和图片
ySpider 是一个非常方便并且功能强大的爬虫框架,支持多线程爬取.JS动态解析,提供了可操作界面.出错重试.定时爬取等等的功能,使用非常人性化. 本篇通过做一个PySpider 项目,来理解 Py ...
- (六)SSO之CAS框架扩展 改动CAS源代码实现与ESS动态password验证对接
题记: 偶尔的偶尔我们会听到这个站点的数据泄露了,那个站点的用户数据泄露了.让用户又一次改动登录password,所以,对于用户数据安全性越发的引起我们的重视了,尤其是一些保密性要求高的站点.更须要添 ...
- [Algorithms] Build a Binary Tree in JavaScript and Several Traversal Algorithms
A binary tree is a tree where each node may only have up to two children. These children are stored ...
- Joiner的用法
Google Guava提供了Joiner类专门用来连接String. 譬如说有个String数组,里面有"a","b","c",我们可以通 ...
- vue 的 生命周期
图示: 解析: 那么下面我们来进行测试一下 <section id="app-8"> {{data}} </section> var myVue=new V ...
- windows下更换jdk运行当前jar包处理命令一则
可在文本文档中新建以下内容 set JAVA_HOME=C:\jdk1.7.0_67set CLASSPATH=.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOMe%\lib\too ...