android是基于Linux内核的操作系统,所以系统的第一个用户空间进程是init进程, pid固定是1。
1.系统属性的设置
来看一下源码
/system/core/init/init.c -> main()
962int main(int argc, char **argv)
963{
964 int fd_count = 0;
965 struct pollfd ufds[4];
966 char *tmpdev;
967 char* debuggable;
968 char tmp[32];
969 int property_set_fd_init = 0;
970 int signal_fd_init = 0;
971 int keychord_fd_init = 0;
972 bool is_charger = false;
973
974 if (!strcmp(basename(argv[0]), "ueventd"))
975 return ueventd_main(argc, argv);
976
977 if (!strcmp(basename(argv[0]), "watchdogd"))
978 return watchdogd_main(argc, argv);
979 ...
980 }
首先简单介绍下ueventd和watchdogd
*ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点)
watchdog也是一个用户空间的守护进程,可以定期对系统进行检测*
eventd和watchdog是init的软连接,就是说events和watchdog程序都是执行init.cpp->main()
在这里main函数的参数argv的第一个,argv[0]为自身运行目录路径和程序名,从而执行相应的代码
ok,简单了解,这里并不是关注的重点,接着往下看。
/system/core/init/init.c -> main()
989 int main(int argc, char** argv) {
997 ...
998 // Clear the umask.
999 umask(0);
1000 ...
1001 }
清理umask(进程的文件模型创建掩码),这个主要是文件权限的问题。
Linux内核给每一个进程都设定了一个掩码,当程序调用open、mkdir等函数创建文件或目录时,传入open的mode会先和掩码做运算,得到的文件mode,才是文件真正的mode。
譬如要创建一个目录,并设定它的文件权限为0777,mkdir(“testdir”, 0777)
但实际上写入的文件权限却未必是777,因为mkdir系统调用在创建testdir时,会将0777与当前进程的掩码(称为umask)运算,具体运算方法为 0777&~umask作为testdir的真正权限。因此上述init中首先调用umask(0)将进程掩码清0,这样调用open/mkdir等函数创建文件或目录时,传入的mode就会作为实际的值写入文件系统。
/system/core/init/init.c -> main()
982
983 /* Get the basic filesystem setup we need put
984 * together in the initramdisk on / and then we'll
985 * let the rc file figure out the rest.
986 */
987 mkdir("/dev", 0755);
988 mkdir("/proc", 0755);
989 mkdir("/sys", 0755);
990
991 mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
992 mkdir("/dev/pts", 0755);
993 mkdir("/dev/socket", 0755);
994 mount("devpts", "/dev/pts", "devpts", 0, NULL);
995 mount("proc", "/proc", "proc", 0, NULL);
996 mount("sysfs", "/sys", "sysfs", 0, NULL);
997
998 /* indicate that booting is in progress to background fw loaders, etc */
999 close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));
这里创建目录,并挂载内核文件系统。
/dev是内存文件系统,不会保存,每次开机都要重新创建。
检测/dev/.booting文件是否可读写和创建
接着看/system/core/init/init.c -> main()
1001 /* We must have some place other than / to create the
1002 * device nodes for kmsg and null, otherwise we won't
1003 * be able to remount / read-only later on.
1004 * Now that tmpfs is mounted on /dev, we can actually
1005 * talk to the outside world.
1006 */
1007 open_devnull_stdio();
1008 klog_init();
看一下open_devnull_stdio()
/system/core/init/init.c -> open_devnull_stdio()
378void open_devnull_stdio(void)
379{
380 int fd;
381 static const char *name = "/dev/__null__";
382 if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {
383 fd = open(name, O_RDWR);
384 unlink(name);
385 if (fd >= 0) {
386 dup2(fd, 0);
387 dup2(fd, 1);
388 dup2(fd, 2);
389 if (fd > 2) {
390 close(fd);
391 }
392 return;
393 }
394 }
395
396 exit(1);
397}
这里/sys/fs/selinux/null并不存在会打开失败,从而创建/dev/null目录项并标准输入(0),标准输出(1),标准错误文件描述符(2)定向到/dev/null,这样输入输出就没有了。
为什么要把stdio重定向到/dev/null设备呢?因为此时系统刚开始启动,用于接收init进程标准输出、标准错误的设备节点还不存在,所以直接把它们重定向到/dev/null了。
没有标准输出怎么打印日志?
接着看init.c->klog_init()
35void klog_init(void)
36{
37 static const char *name = "/dev/__kmsg__";
38
39 if (klog_fd >= 0) return; /* Already initialized */
40
41 if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
42 klog_fd = open(name, O_WRONLY);
43 if (klog_fd < 0)
44 return;
45 fcntl(klog_fd, F_SETFD, FD_CLOEXEC);
46 unlink(name);
47 }
48}
49
并未初始化过,因此这里klog_fd为0,然后调用mknod创建设备节点文件/dev/kmsg.
其实这里的/dev/kmsg与/dev/kmsg是同一节点,他们的主设备号都为1,从设备号都为11。
同样上面的/dev/null与/dev/null也是同一节点,他们的主设备号都为1,从设备号都为3。
然后打开该文件将文件描述符保存到变量klog_fd中,接着调用fcntl(klog_fd, F_SETFD, FD_CLOEXEC),这里FD_CLOEXEC的值为1,表示当程序执行exec函数时本fd将被系统自动关闭,不传递给exec创建的新进程。
随后调用unlink来删除/dev/kmsg文件.
unlink功能描述:从文件系统中删除一个名称。如果名称是文件的最后一个连接,并且没有其它进程将文件打开,名称对应的文件会实际被删除。
继续看init.c
1007 int main(int argc, char **argv){
1008 ...
1009 property_init();
1010 ...
1011 }
这里是属性服务的初始化,可以参考这篇文章,讲的很详细了
接着看init.c->main();
1009 int main(int argc, char **argv){
1010 ...
1011 get_hardware_name(hardware, &revision);
1012
1013 process_kernel_cmdline();
1014 ...
1015 }
先看下init.c->get_hardware_name();
399void get_hardware_name(char *hardware, unsigned int *revision)
400{
401 char data[1024];
402 int fd, n;
403 char *x, *hw, *rev;
404
405 /* Hardware string was provided on kernel command line */
406 if (hardware[0])
407 return;
408
409 fd = open("/proc/cpuinfo", O_RDONLY);
410 if (fd < 0) return;
411
412 n = read(fd, data, 1023);
413 close(fd);
414 if (n < 0) return;
415
416 data[n] = 0;
417 hw = strstr(data, "\nHardware");
418 rev = strstr(data, "\nRevision");
419
420 if (hw) {
421 x = strstr(hw, ": ");
422 if (x) {
423 x += 2;
424 n = 0;
425 while (*x && *x != '\n') {
426 if (!isspace(*x))
427 hardware[n++] = tolower(*x);
428 x++;
429 if (n == 31) break;
430 }
431 hardware[n] = 0;
432 }
433 }
434
435 if (rev) {
436 x = strstr(rev, ": ");
437 if (x) {
438 *revision = strtoul(x + 2, 0, 16);
439 }
440 }
441}
get_hardware_name()函数从”/proc/cpuinfo”文件读取相应字符串到data中
最终将SMDK4x12转成小写保存在init的hardware,将0008转成16进制保存在init的revision中
strstr()函数的功能:就是在第一个参数中查找第二个参数第一次出现的地址,将地址赋值给一个字符指针,接着就可以利用这个字符指针找到从这个地址开始往后的字符。
再看下init.c->process_kernel_cmdline();
770static void process_kernel_cmdline(void)
771{
772 /* don't expose the raw commandline to nonpriv processes */
773 chmod("/proc/cmdline", 0440);
774
775 /* first pass does the common stuff, and finds if we are in qemu.
776 * second pass is only necessary for qemu to export all kernel params
777 * as props.
778 */
779 import_kernel_cmdline(0, import_kernel_nv);
780 if (qemu[0])
781 import_kernel_cmdline(1, import_kernel_nv);
782
783 /* now propogate the info given on command line to internal variables
784 * used by init as well as the current required properties
785 */
786 export_kernel_boot_props();
787}
443void import_kernel_cmdline(int in_qemu,
444 void (*import_kernel_nv)(char *name, int in_qemu))
445{
446 char cmdline[1024];
447 char *ptr;
448 int fd;
449
450 fd = open("/proc/cmdline", O_RDONLY);
451 if (fd >= 0) {
452 int n = read(fd, cmdline, 1023);
453 if (n < 0) n = 0;
454
455 /* get rid of trailing newline, it happens */
456 if (n > 0 && cmdline[n-1] == '\n') n--;
457
458 cmdline[n] = 0;
459 close(fd);
460 } else {
461 cmdline[0] = 0;
462 }
463
464 ptr = cmdline;
465 while (ptr && *ptr) {
466 char *x = strchr(ptr, ' ');
467 if (x != 0) *x++ = 0;
468 import_kernel_nv(ptr, in_qemu);
469 ptr = x;
470 }
471}
686static void import_kernel_nv(char *name, int for_emulator)
687{
688 char *value = strchr(name, '=');
689 int name_len = strlen(name);
690
691 if (value == 0) return;
692 *value++ = 0;
693 if (name_len == 0) return;
694
695 if (for_emulator) {
696 /* in the emulator, export any kernel option with the
697 * ro.kernel. prefix */
698 char buff[PROP_NAME_MAX];
699 int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
700
701 if (len < (int)sizeof(buff))
702 property_set( buff, value );
703 return;
704 }
705
706 if (!strcmp(name,"qemu")) {
707 strlcpy(qemu, value, sizeof(qemu));
708 } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
709 const char *boot_prop_name = name + 12;
710 char prop[PROP_NAME_MAX];
711 int cnt;
712
713 cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
714 if (cnt < PROP_NAME_MAX)
715 property_set(prop, value);
716 }
717}
首先将/proc/cmdline文件权限修改为只有root用户和root组用户可读写。
然后import_kernel_cmdline()函数的第一个参数in_qemu表示是否是虚拟机。
这里将/proc/cmdline的内容读进内存,然后以空格将整个字符串拆分成小的字符串
以我的samsung GT-N5100为例,/proc/cmdline内容为
然后分别调用import_kernel_nv()将拆分后的小字符串传入(注意这里的in_qemu为0,表示非虚拟机)。
import_kernel_nv()中将传入的字符串在以’=’拆分成name和value。此时for_emulator为0,所以跳到判断name是否等于’qemu’,又因为我/proc/cmdline中并没有qemu的name,所以并不会给qemu数组写入任何值,接着最终只会将格式为androidboot.XXX的name改成ro.boot.XXX,并将修改后的name和value写进android的系统属性中。
回到process_kernel_cmdline()由于前面并没有往qemu中写入任何值,所以qemu[0]为0,接着会执行export_kernel_boot_props()。
init.c->export_kernel_boot_props()
719static void export_kernel_boot_props(void)
720{
721 char tmp[PROP_VALUE_MAX];
722 int ret;
723 unsigned i;
724 struct {
725 const char *src_prop;
726 const char *dest_prop;
727 const char *def_val;
728 } prop_map[] = {
729 { "ro.boot.serialno", "ro.serialno", "", },
730 { "ro.boot.mode", "ro.bootmode", "unknown", },
731 { "ro.boot.baseband", "ro.baseband", "unknown", },
732 { "ro.boot.bootloader", "ro.bootloader", "unknown", },
733 };
734
735 for (i = 0; i < ARRAY_SIZE(prop_map); i++) {
736 ret = property_get(prop_map[i].src_prop, tmp);
737 if (ret > 0)
738 property_set(prop_map[i].dest_prop, tmp);
739 else
740 property_set(prop_map[i].dest_prop, prop_map[i].def_val);
741 }
742
743 ret = property_get("ro.boot.console", tmp);
744 if (ret)
745 strlcpy(console, tmp, sizeof(console));
746
747 /* save a copy for init's usage during boot */
748 property_get("ro.bootmode", tmp);
749 strlcpy(bootmode, tmp, sizeof(bootmode));
750
751 /* if this was given on kernel command line, override what we read
752 * before (e.g. from /proc/cpuinfo), if anything */
753 ret = property_get("ro.boot.hardware", tmp);
754 if (ret)
755 strlcpy(hardware, tmp, sizeof(hardware));
756 property_set("ro.hardware", hardware);
757
758 snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
759 property_set("ro.revision", tmp);
760
761 /* TODO: these are obsolete. We should delete them */
762 if (!strcmp(bootmode,"factory"))
763 property_set("ro.factorytest", "1");
764 else if (!strcmp(bootmode,"factory2"))
765 property_set("ro.factorytest", "2");
766 else
767 property_set("ro.factorytest", "0");
768}
这里很简单,就是设置一些系统属性。接着往下看main()。
init.c->main()
1015 union selinux_callback cb;
1016 cb.func_log = klog_write;
1017 selinux_set_callback(SELINUX_CB_LOG, cb);
1018
1019 cb.func_audit = audit_callback;
1020 selinux_set_callback(SELINUX_CB_AUDIT, cb);
1021
1022 selinux_initialize();
1023 /* These directories were necessarily created before initial policy load
1024 * and therefore need their security context restored to the proper value.
1025 * This must happen before /dev is populated by ueventd.
1026 */
1027 restorecon("/dev");
1028 restorecon("/dev/socket");
1029 restorecon("/dev/__properties__");
1030 restorecon_recursive("/sys");
1031
这段代码是4.1之后添加的,有关SELinux的初始化和检查,暂时还没有研究过SELinux,而且这里与android启动的关系也不大,暂时先不看。
init.c->main()
1032 is_charger = !strcmp(bootmode, "charger");
1033
1034 INFO("property init\n");
1035 if (!is_charger)
1036 property_load_boot_defaults();
如果当前启动模式不是充电模式,将从/default.prop文件中加载默认的一些属性设置。
2. init.rc的解析
接下来好戏开始了,开始对init.rc进行解析
关于init.rc的具体语法可以看下/system/core/init/readme.txt,这里简单介绍下
init.rc主要定义两类结构:action与service
action
action是一组命令的集合
38Actions take the form of:
40on <trigger>
41 <command>
42 <command>
43 <command>
先说下command,command的格式如下
command-name <parament1> [parament2...]
<>表示必须存在的参数,[]表示可选参数
可以这样理解,每个command表示一个要执行的动作,对应一个函数当action被触发时,会依次执行每个command对应的函数。
trigger是一个触发器,表示什么时候执行这个action,在init.rc中主要有两种类型:
1普通型
这种类型的trigger直接以字符串表示,用以描述一个时间节点,当该trigger对应的action加入到全局action_list中后(该过程下面会讲述),在init.c->main()中可以通过相应的时间节点找出相应的action并将他们按序加入到一个待执行队列中稍后依次触发他们。
2属性型
trigger为property:<name>=<value>
这种类型的action只有在property name的值为value时才会被触发。
service
service表示一个可执行程序
51service <name> <pathname> [ <argument> ]*
52 <option>
53 <option>
54 ...
name字段为service的名字
pathname为该service对应的二进制程序的路径
随后是该程序的参数列表
option是该service的属性,具体option可以看下/system/core/init/readme.txt的描述。
这里提一下有一个class属性,
101class <name>
102 Specify a class name for the service. All services in a
103 named class may be started or stopped together. A service
104 is in the class "default" if one is not specified via the
105 class option.
该属性name在init.rc主要有core, main,charger三种,如果service没有定义该属性则name默认为default。他的作用将service归属于一个class组,然后可以在Actions中通过class_start、class_stop、class_reset等命令启动、停止、重启动相应class组中的所有service。
补充下除了action和service,还有一个import是用来导入其他init脚本文件的
好了,来具体看一下解析init.rc的代码
init.c->main()
1036int main(int argc, char **argv)
1037 ...
1038 INFO("reading config file\n");
1039 init_parse_config_file("/init.rc");
1040 ...
1041}
/system/core/init/init_parser.c
404int init_parse_config_file(const char *fn)
405{
406 char *data;
407 data = read_file(fn, 0);
408 if (!data) return -1;
409
410 parse_config(fn, data);
411 DUMP();
412 return 0;
413}
先将/init.rc的内容读入内存,然后调用parse_config()函数进行解析。
/system/core/init/init_parser.c
347static void parse_config(const char *fn, char *s)
348{
349 struct parse_state state;
350 struct listnode import_list;
351 struct listnode *node;
352 char *args[INIT_PARSER_MAXARGS];
353 int nargs;
354
355 nargs = 0;
356 state.filename = fn;
357 state.line = 0;
358 state.ptr = s;
359 state.nexttoken = 0;
360 state.parse_line = parse_line_no_op;
361
362 list_init(&import_list);
363 state.priv = &import_list;
364
365 for (;;) {
366 switch (next_token(&state)) {
367 case T_EOF:
368 state.parse_line(&state, 0, 0);
369 goto parser_done;
370 case T_NEWLINE:
371 state.line++;
372 if (nargs) {
373 int kw = lookup_keyword(args[0]);
374 if (kw_is(kw, SECTION)) {
375 state.parse_line(&state, 0, 0);
376 parse_new_section(&state, kw, nargs, args);
377 } else {
378 state.parse_line(&state, nargs, args);
379 }
380 nargs = 0;
381 }
382 break;
383 case T_TEXT:
384 if (nargs < INIT_PARSER_MAXARGS) {
385 args[nargs++] = state.text;
386 }
387 break;
388 }
389 }
390
391parser_done:
392 list_for_each(node, &import_list) {
393 struct import *import = node_to_item(node, struct import, list);
394 int ret;
395
396 INFO("importing '%s'", import->filename);
397 ret = init_parse_config_file(import->filename);
398 if (ret)
399 ERROR("could not import file '%s' from '%s'\n",
400 import->filename, fn);
401 }
402}
parse_config函数比较复杂主要做了
1. 解析字符串中所有的action,并将其链入action_list队列
2. 解析字符串中所有的service,并将他们链入service_list队列
3. 将import的文件读进内存,然后重复前面两步。
ok,在接下来具体分析之前先简单看一下有关的几个数据结构:
/system/core/init/init.h
26struct command
27{
28 /* list of commands in an action */
29 struct listnode clist;//用于将command链入一个双链队列
30
31 int (*func)(int nargs, char **args);//command语句所对应的函数指针
32 int nargs;//command语句所表示的命令对应的函数的参数个数
33 char *args[1];//命令对应的函数的参数
34};
35
36struct action {
37 /* node in list of all actions */
38 struct listnode alist;//用于将action链入action_list队列
39 /* node in the queue of pending actions */
40 struct listnode qlist;//用于将action链入action_queue队列
41 /* node in list of actions for a trigger */
42 struct listnode tlist;//也是用于将action链入队列,暂时在代码中看到
43
44 unsigned hash;//在目前init实现中未使用
45 const char *name;//action的trigger(用来标记action的执行时机)
46
47 struct listnode commands;//该action所有struct command链表
48 struct command *current;//在Actions执行时,存储当前正在被执行的struct command指针。
49};
51struct socketinfo {
52 struct socketinfo *next;
53 const char *name;
54 const char *type;
55 uid_t uid;
56 gid_t gid;
57 int perm;
58};
59
60struct svcenvinfo {
61 struct svcenvinfo *next;
62 const char *name;
63 const char *value;
64};
65
81struct service {
82 /* list of all services */
83 struct listnode slist;//用于将action链入service_list队列
84
85 const char *name;//service的name
86 const char *classname;//设置service的类别,感觉有点像开机启动service的优先级,默认的class名称为default,还有core、main、charger
87
88 unsigned flags;////位图变量,其各个位代表不同的servcie的属性(对应service中的option字段)
89 pid_t pid;////当service对应的程序执行时,存放其进程号
90 time_t time_started; //进程启动时间
91 time_t time_crashed; //存放第一次进程崩溃时间
92 int nr_crashed; //存放进程崩溃次数
93
94 uid_t uid;//该servcie对应进程的uid
95 gid_t gid;//该service对应进程的gidinit_parse_config_file("/init.rc");
96 gid_t supp_gids[NR_SVC_SUPP_GIDS];//该service对应进程的附加群组id
97 size_t nr_supp_gids;//该service所隶属的附件组的数目
98
99 char *seclabel;//存放selinux所需要的security context
100
101 struct socketinfo *sockets;// 为service创建的sockets
102 struct svcenvinfo *envvars;// 为service设置的环境变量
103
104 struct action onrestart; //服务重启时,执行的命令
105
106 /* keycodes for triggering this service via /dev/keychord */
107 int *keycodes;//keycodes相关,init中未使用
108 int nkeycodes;
109 int keychord_id;
110
111 int ioprio_class;// io优先级
112 int ioprio_pri;
113
114 int nargs;//对应service语句传入的参数数目
115 /* "MUST BE AT THE END OF THE STRUCT" */
116 char *args[1];//存放service语句实际传入的参数,其长度将会被修正为nargs+1
117}; /* ^-------'args' MUST be at the end of this struct! */
另外还有一点先说明下,init_parse.c中定义了三个全局的双链队列:
/system/core/init/init_parser.c
39static list_declare(service_list);
40static list_declare(action_list);
41static list_declare(action_queue);
service_list是全局service链表,解析启动脚本过程中,service对应数据结构struct service将会挂载到这里。action_list是全局Actions链表,Actions对应数据结构struct action将会挂载到这里。至于action_queue在解析完毕之后,实际执行Actions时才会用到。
/system/core/init/parser.h
24struct parse_state
25{
26 char *ptr;
27 char *text;
28 int line;
29 int nexttoken;
30 void *context;
31 void (*parse_line)(struct parse_state *state, int nargs, char **args);
32 const char *filename;
33 void *priv;
34};
ptr是还未解析的字符串
text当前读出的内容
line当然就是行数了
nexttoken保存下一个要执行的操作(0或T_NEWLINE)
filename当前解析字符串所属文件的文件名
context当前正在解析的action或者service对象
parse_line解析command(parse_line_action())或者option(parse_line_service())的函数指针,或者如果如果当前并没有在解析一个action或service这个函数指针指向parse_line_no_op()是一个空实现。
state.priv指向一个链表,是当前文件import的所有文件。
/system/core/include/cutils/list.h
26struct listnode
27{
28 struct listnode *next;
29 struct listnode *prev;
30};
/system/core/libcutils/list.c
19void list_init(struct listnode *node)
20{
21 node->next = node;
22 node->prev = node;
23}
/system/core/libcutils/list.c
25void list_add_tail(struct listnode *head, struct listnode *item)
26{
27 item->next = head;
28 item->prev = head->prev;
29 head->prev->next = item;
30 head->prev = item;
31}
这里listnode的作用很明显也很简单,就是listnode的宿主数据结构可以通过listnode链入一个双向的队列中。
ok,来看代码:
/system/core/init/parser.c
68int next_token(struct parse_state *state)
69{
70 char *x = state->ptr;
71 char *s;
72
73 if (state->nexttoken) {
74 int t = state->nexttoken;
75 state->nexttoken = 0;
76 return t;
77 }
78
79 for (;;) {
80 switch (*x) {
81 case 0:
82 state->ptr = x;
83 return T_EOF;
84 case '\n':
85 x++;
86 state->ptr = x;
87 return T_NEWLINE;
88 case ' ':
89 case '\t':
90 case '\r':
91 x++;
92 continue;
93 case '#':
94 while (*x && (*x != '\n')) x++;
95 if (*x == '\n') {
96 state->ptr = x+1;
97 return T_NEWLINE;
98 } else {
99 state->ptr = x;
100 return T_EOF;
101 }
102 default:
103 goto text;
104 }
105 }
106
107textdone:
108 state->ptr = x;
109 *s = 0;
110 return T_TEXT;
111text:
112 state->text = s = x;
113textresume:
114 for (;;) {
115 switch (*x) {
116 case 0:
117 goto textdone;
118 case ' ':
119 case '\t':
120 case '\r':
121 x++;
122 goto textdone;
123 case '\n':
124 state->nexttoken = T_NEWLINE;
125 x++;
126 goto textdone;
127 case '"':
128 x++;
129 for (;;) {
130 switch (*x) {
131 case 0:
132 /* unterminated quoted thing */
133 state->ptr = x;
134 return T_EOF;
135 case '"':
136 x++;
137 goto textresume;
138 default:
139 *s++ = *x++;
140 }
141 }
142 break;
143 case '\\':
144 x++;
145 switch (*x) {
146 case 0:
147 goto textdone;
148 case 'n':
149 *s++ = '\n';
150 break;
151 case 'r':
152 *s++ = '\r';
153 break;
154 case 't':
155 *s++ = '\t';
156 break;
157 case '\\':
158 *s++ = '\\';
159 break;
160 case '\r':
161 /* \ <cr> <lf> -> line continuation */
162 if (x[1] != '\n') {
163 x++;
164 continue;
165 }
166 case '\n':
167 /* \ <lf> -> line continuation */
168 state->line++;
169 x++;
170 /* eat any extra whitespace */
171 while((*x == ' ') || (*x == '\t')) x++;
172 continue;
173 default:
174 /* unknown escape -- just copy */
175 *s++ = *x++;
176 }
177 continue;
178 default:
179 *s++ = *x++;
180 }
181 }
182 return T_EOF;
183}
next_token()的作用是忽略state->ptr(还未解析的字符串)中的注释,从中读出字符串并保存在state->text中直到遇到空格或换行。
当遇到空格时返回T_TEXT,当遇到换行时返回T_NEWLINE。
截取init.rc中部分内容为例:
12on early-init
13 # Set init and its forked children's oom_adj.
14 write /proc/1/oom_adj -16
15
16 # Set the security context for the init process.
17 # This should occur before anything else (e.g. ueventd) is started.
18 setcon u:r:init:s0
19
20 start ueventd
第一次执行next_token()读到on和early-init之间的空格,将on的开始地址保存在state->text中。state->ptr指针增加三位,从early-init开始。然后返回T_TEXT。
回到parse_config()中如果next_token()返回的是T_TEXT,而且nargs(此时为0)小于64,则将state->text以nargs为脚标存进args数组中。此时,args[0]存放的就是指向”on”开始字符串的首地址。
然后继续执行next_token(),这一次读到了第一行尾。并将early-init字符串的开始地址保存在state->text中。state->ptr指针增加11位,从第二行开始。将state->nexttoken 的值设置为T_NEWLINE;然后返回T_TEXT。
再回到parse_config()中这时next_token()返回的还是T_TEXT,所以将”early-init”开始字符串的首地址存进args[1]中。
这时再进入next_token(),因为state->nexttoken的值为T_NEWLINE(2),非0,所以先将state->nexttoken设为0,然后返回T_NEWLINE。
回到parse_config()中这时next_token()返回的就是T_NEWLINE了,所以先将行号增加1,然后判断nargs(此时为2)非0,接着调用lookup_keyword()并将args[0]也就是”on”的首地址传了过去。
看一下lookup_keyword()
79int lookup_keyword(const char *s)
80{
81 switch (*s++) {
80 ...
128 case 'o':
129 if (!strcmp(s, "n")) return K_on;
130 if (!strcmp(s, "neshot")) return K_oneshot;
131 if (!strcmp(s, "nrestart")) return K_onrestart;
132 break;
133 ...
169 return K_UNKNOWN;
170}
卧槽,K_on在哪,瞬间蒙逼了…看下/system/core/init/init_parser.c中的这块代码
58#include "keywords.h"
59
60#define KEYWORD(symbol, flags, nargs, func) \
61 [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
62
63struct {
64 const char *name;
65 int (*func)(int nargs, char **args);
66 unsigned char nargs;
67 unsigned char flags;
68} keyword_info[KEYWORD_COUNT] = {
69 [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
70#include "keywords.h"
71};
72#undef KEYWORD
这里的代码很巧妙,先理解下define中的#和##:
#是把参数字符串化;##是一个连接符号,用于把参数连在一起
这里keywords.h被include两遍,在第一次include keywords.h时KEYWORD宏被定义为下面形式
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
所以keywords.h中的enum会改为下面形式
43enum {
44 K_UNKNOWN,
46 K_capability,
47 K_chdir,
49 ...
68 K_on,
69 ...
100 K_ioprio,
102 KEYWORD_COUNT,
103};
之后KEYWORD宏被undef了,在init_parser.c中重新被定义为
60#define KEYWORD(symbol, flags, nargs, func) \
61 [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
所以在第二次include keywords.h时keywords.h就变成了
68 keyword_info[KEYWORD_COUNT] = {
69 [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
70 [K_capability]={"capability",0, 0, 0x04},
71 ...
72 [K_on]={"on", 0,0,0x01},
73 ...
74};
ok,这下子就明朗的。lookup_keyword()返回枚举类型K_on,然后通过宏kw_is(kw, type) (keyword_info[kw].flags & (type))
检查K_on是否是一个SECTION,结果为true,接着执行下面两句
375 state.parse_line(&state, 0, 0);
376 parse_new_section(&state, kw, nargs, args);
这时state的函数指针parse_line指向parse_line_no_op,是一个空操作,所以往下看parse_new_section()函数
/system/core/init/init_parser.c
320void parse_new_section(struct parse_state *state, int kw,
321 int nargs, char **args)
322{
323 printf("[ %s %s ]\n", args[0],
324 nargs > 1 ? args[1] : "");
325 switch(kw) {
326 case K_service:
327 state->context = parse_service(state, nargs, args);
328 if (state->context) {
329 state->parse_line = parse_line_service;
330 return;
331 }
332 break;
333 case K_on:
334 state->context = parse_action(state, nargs, args);
335 if (state->context) {
336 state->parse_line = parse_line_action;
337 return;
338 }
339 break;
340 case K_import:
341 parse_import(state, nargs, args);
342 break;
343 }
344 state->parse_line = parse_line_no_op;
345}
parseaction()通过参数生成一个action结构,并把它保存在state->context中,然后将state->parse_line函数指针指向parse_line_service(),然后parse_new_section()就返回了。来具体看下parse_action():
/system/core/init/init_parser.c
821static void *parse_action(struct parse_state *state, int nargs, char **args)
822{
823 struct action *act;
824 if (nargs < 2) {
825 parse_error(state, "actions must have a trigger\n");
826 return 0;
827 }
828 if (nargs > 2) {
829 parse_error(state, "actions may not have extra parameters\n");
830 return 0;
831 }
832 act = calloc(1, sizeof(*act));
833 act->name = args[1];
834 list_init(&act->commands);
835 list_init(&act->qlist);
836 list_add_tail(&action_list, &act->alist);
837 /* XXX add to hash */
838 return act;
839}
这里一开始先检查了下action的参数个数是否符合语法规定,然后调用calloc()申请一个action节点,接着将trigger(这里也就是early-init)保存到了act->name。然后初始化了act->qlist和act->commands,并将该action链入action_list队列尾部,这几点不多说了。最后返回该action。
现在就返回到init_parser.c的->parse_config()了,然后把nargs置为0就开始了下一次for循环。略过#开头的注释行解析,next_token()函数解析完14 write /proc/1/oom_adj -16
这一行后,nargs为3,args数组前三项分别保存了”write”,”/proc/1/oom_adj”,”-16”三个字符串。所以lookup_keyword()返回K_write, 接着kw_is(kw, SECTION)为false,因此执行state.parse_line(&state, nargs, args);
。这里parse_line函数指针在之前parse_new_section()中指向了parse_line_action(),来具体看一下:
/system/core/init/init_parser.c
841static void parse_line_action(struct parse_state* state, int nargs, char **args)
842{
843 struct command *cmd;
844 struct action *act = state->context;
845 int (*func)(int nargs, char **args);
846 int kw, n;
847
848 if (nargs == 0) {
849 return;
850 }
851
852 kw = lookup_keyword(args[0]);
853 if (!kw_is(kw, COMMAND)) {
854 parse_error(state, "invalid command '%s'\n", args[0]);
855 return;
856 }
857
858 n = kw_nargs(kw);
859 if (nargs < n) {
860 parse_error(state, "%s requires %d %s\n", args[0], n - 1,
861 n > 2 ? "arguments" : "argument");
862 return;
863 }
864 cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
865 cmd->func = kw_func(kw);
866 cmd->nargs = nargs;
867 memcpy(cmd->args, args, sizeof(char*) * nargs);
868 list_add_tail(&act->commands, &cmd->clist);
869}
这里就是申请一个command节点,将cmd->func函数指针指向初始化keyword_info时定义的函数指针,这里是声明在keywords.h中的do_write()函数,然后将nargs保存在cmd->nargs中,将args数组保存在cmd->args中,最后将command链入act->commands的队列尾。
到这里基本上就能够明白action的解析过程了,解析完的action都被保存在了全局的action_list中,后面会根据trigger也就是act->name将action链入action_queue中然后依次执行,这个后面 action的执行 章节再看。
看完action再来看下service,以下面代码为例
408service ueventd /sbin/ueventd
409 class core
410 critical
411 seclabel u:r:ueventd:s0
类似action的解析,解析完第一行后args数组前三项分别存放了”service”,”evened”和”/sbin/ueventd”字符串的首地址。这里传入parse_line()的nargs为0,所以可以直接忽略这部调用,因为无论是在parse_line_service()还是在parse_line_action()中,当nargs为0都没有做任何事情。进入parse_new_section()中,此时kw=K_service,所以调用parse_service()解析service,并将解析的service保存到state->context,然后将state->parse_line函数指针指向parse_line_service(),接着就返回了。来看下parse_service():
/system/core/init/init_parser.c
613static void *parse_service(struct parse_state *state, int nargs, char **args)
614{
615 struct service *svc;
616 if (nargs < 3) {
617 parse_error(state, "services must have a name and a program\n");
618 return 0;
619 }
620 if (!valid_name(args[1])) {
621 parse_error(state, "invalid service name '%s'\n", args[1]);
622 return 0;
623 }
624
625 svc = service_find_by_name(args[1]);
626 if (svc) {
627 parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
628 return 0;
629 }
630
631 nargs -= 2;
632 svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
633 if (!svc) {
634 parse_error(state, "out of memory\n");
635 return 0;
636 }
637 svc->name = args[1];
638 svc->classname = "default";
639 memcpy(svc->args, args + 2, sizeof(char*) * nargs);
640 svc->args[nargs] = 0;
641 svc->nargs = nargs;
642 svc->onrestart.name = "onrestart";
643 list_init(&svc->onrestart.commands);
644 list_add_tail(&service_list, &svc->slist);
645 return svc;
646}
service_find_by_name()是根据传过来的service name去全局serviced队列service_list中查找相同name的service,主要是为了避免重复定义同名service。接着调用calloc()申请一个service节点,将service name(这里是”ueventd”)赋值给svc->name,svc->classname默认设为”default”,nargs赋值给svc->nargs,初始化该service的onrestart.commands队列,最后将该service链入全局service_list,然后返回该service。
先在可以返回到parse_config()中继续调用next_token()读出内容了,当读完上面示例的第二行后,args数组前两项分别存放了”class”和”core”字符串的首地址,nargs为2。接着得到kw为K_class不是一个SECTION所以执行state.parse_line()也就是前面设置的parse_line_service():
/system/core/init/init_parser.c
648static void parse_line_service(struct parse_state *state, int nargs, char **args)
649{
650 struct service *svc = state->context;
651 struct command *cmd;
652 int i, kw, kw_nargs;
653
654 if (nargs == 0) {
655 return;
656 }
657
658 svc->ioprio_class = IoSchedClass_NONE;
659
660 kw = lookup_keyword(args[0]);
661 switch (kw) {
662 case K_capability:
663 break;
664 case K_class:
665 if (nargs != 2) {
666 parse_error(state, "class option requires a classname\n");
667 } else {
668 svc->classname = args[1];
669 }
670 break;
671 case K_console:
672 svc->flags |= SVC_CONSOLE;
673 break;
674 case K_disabled:
675 svc->flags |= SVC_DISABLED;
676 svc->flags |= SVC_RC_DISABLED;
677 break;
678 case K_ioprio:
679 if (nargs != 3) {
680 parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");
681 } else {
682 svc->ioprio_pri = strtoul(args[2], 0, 8);
683
684 if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {
685 parse_error(state, "priority value must be range 0 - 7\n");
686 break;
687 }
688
689 if (!strcmp(args[1], "rt")) {
690 svc->ioprio_class = IoSchedClass_RT;
691 } else if (!strcmp(args[1], "be")) {
692 svc->ioprio_class = IoSchedClass_BE;
693 } else if (!strcmp(args[1], "idle")) {
694 svc->ioprio_class = IoSchedClass_IDLE;
695 } else {
696 parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");
697 }
698 }
699 break;
700 case K_group:
701 if (nargs < 2) {
702 parse_error(state, "group option requires a group id\n");
703 } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
704 parse_error(state, "group option accepts at most %d supp. groups\n",
705 NR_SVC_SUPP_GIDS);
706 } else {
707 int n;
708 svc->gid = decode_uid(args[1]);
709 for (n = 2; n < nargs; n++) {
710 svc->supp_gids[n-2] = decode_uid(args[n]);
711 }
712 svc->nr_supp_gids = n - 2;
713 }
714 break;
715 case K_keycodes:
716 if (nargs < 2) {
717 parse_error(state, "keycodes option requires atleast one keycode\n");
718 } else {
719 svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));
720 if (!svc->keycodes) {
721 parse_error(state, "could not allocate keycodes\n");
722 } else {
723 svc->nkeycodes = nargs - 1;
724 for (i = 1; i < nargs; i++) {
725 svc->keycodes[i - 1] = atoi(args[i]);
726 }
727 }
728 }
729 break;
730 case K_oneshot:
731 svc->flags |= SVC_ONESHOT;
732 break;
733 case K_onrestart:
734 nargs--;
735 args++;
736 kw = lookup_keyword(args[0]);
737 if (!kw_is(kw, COMMAND)) {
738 parse_error(state, "invalid command '%s'\n", args[0]);
739 break;
740 }
741 kw_nargs = kw_nargs(kw);
742 if (nargs < kw_nargs) {
743 parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,
744 kw_nargs > 2 ? "arguments" : "argument");
745 break;
746 }
747
748 cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
749 cmd->func = kw_func(kw);
750 cmd->nargs = nargs;
751 memcpy(cmd->args, args, sizeof(char*) * nargs);
752 list_add_tail(&svc->onrestart.commands, &cmd->clist);
753 break;
754 case K_critical:
755 svc->flags |= SVC_CRITICAL;
756 break;
757 case K_setenv: { /* name value */
758 struct svcenvinfo *ei;
759 if (nargs < 2) {
760 parse_error(state, "setenv option requires name and value arguments\n");
761 break;
762 }
763 ei = calloc(1, sizeof(*ei));
764 if (!ei) {
765 parse_error(state, "out of memory\n");
766 break;
767 }
768 ei->name = args[1];
769 ei->value = args[2];
770 ei->next = svc->envvars;
771 svc->envvars = ei;
772 break;
773 }
774 case K_socket: {/* name type perm [ uid gid ] */
775 struct socketinfo *si;
776 if (nargs < 4) {
777 parse_error(state, "socket option requires name, type, perm arguments\n");
778 break;
779 }
780 if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")
781 && strcmp(args[2],"seqpacket")) {
782 parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");
783 break;
784 }
785 si = calloc(1, sizeof(*si));
786 if (!si) {
787 parse_error(state, "out of memory\n");
788 break;
789 }
790 si->name = args[1];
791 si->type = args[2];
792 si->perm = strtoul(args[3], 0, 8);
793 if (nargs > 4)
794 si->uid = decode_uid(args[4]);
795 if (nargs > 5)
796 si->gid = decode_uid(args[5]);
797 si->next = svc->sockets;
798 svc->sockets = si;
799 break;
800 }
801 case K_user:
802 if (nargs != 2) {
803 parse_error(state, "user option requires a user id\n");
804 } else {
805 svc->uid = decode_uid(args[1]);
806 }
807 break;
808 case K_seclabel:
809 if (nargs != 2) {
810 parse_error(state, "seclabel option requires a label string\n");
811 } else {
812 svc->seclabel = args[1];
813 }
814 break;
815
816 default:
817 parse_error(state, "invalid option '%s'\n", args[0]);
818 }
819}
这里就是根据kw的值设置前面创建的service的相应的属性或标记位,比较简单不多描述了。
init.rc中import的作用类似于include,就是导入其他的文件内容。
最后返回到init_parse_config_file()中,DUMP()是跟log有关的函数不需关注。
这样基本上init.rc的解析基本上就结束了。到此已经把init.rc及其导入文件中所有的action和service已经解析完了并分别链入了全局的action_list和service_list中,那么他们什么时候执行呢?带着问题,来看下下一节:action_queue的链入。
3. action_queue的链入
回到/system/core/init/init.c->main(),接下来就是从action_list中取出相应时间节点的action并将其链入action_queue中等待执行。看代码:
/system/core/init/init.c->main()
1039int main(int argc, char **argv){
1040 ...
1041 action_for_each_trigger("early-init", action_add_queue_tail);
1042
1043 queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
1044 queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
1045 queue_builtin_action(keychord_init_action, "keychord_init");
1046 queue_builtin_action(console_init_action, "console_init");
1047
1048 /* execute all the boot actions to get us started */
1049 action_for_each_trigger("init", action_add_queue_tail);
1050
1051 /* skip mounting filesystems in charger mode */
1052 if (!is_charger) {
1053 action_for_each_trigger("early-fs", action_add_queue_tail);
1054 action_for_each_trigger("fs", action_add_queue_tail);
1055 action_for_each_trigger("post-fs", action_add_queue_tail);
1056 action_for_each_trigger("post-fs-data", action_add_queue_tail);
1057 }
1058
1059 /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
1060 * wasn't ready immediately after wait_for_coldboot_done
1061 */
1062 queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
1063
1064 queue_builtin_action(property_service_init_action, "property_service_init");
1065 queue_builtin_action(signal_init_action, "signal_init");
1066 queue_builtin_action(check_startup_action, "check_startup");
1067
1068 if (is_charger) {
1069 action_for_each_trigger("charger", action_add_queue_tail);
1070 } else {
1071 action_for_each_trigger("early-boot", action_add_queue_tail);
1072 action_for_each_trigger("boot", action_add_queue_tail);
1073 }
1074
1075 /* run all property triggers based on current state of the properties */
1076 queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
1077
1078
1079#if BOOTCHART
1080 queue_builtin_action(bootchart_init_action, "bootchart_init");
1081#endif
1082 ...
1083}
这里主要涉及两个函数action_for_each_trigger()和queue_builtin_action(),分别来看下
action_for_each_trigger()两个参数:一个为action的trigger;另一个为一个函数指针,init.c->main()中该函数指针传的都是action_add_queue_tail()。看下代码:
504void action_for_each_trigger(const char *trigger,
505 void (*func)(struct action *act))
506{
507 struct listnode *node;
508 struct action *act;
509 list_for_each(node, &action_list) {
510 act = node_to_item(node, struct action, alist);
511 if (!strcmp(act->name, trigger)) {
512 func(act);
513 }
514 }
515}
588void action_add_queue_tail(struct action *act)
589{
590 if (list_empty(&act->qlist)) {
591 list_add_tail(&action_queue, &act->qlist);
592 }
593}
代码简单到掉眼泪,就是循环遍历全局队列action_list的每个节点,如果发现某个action的trigger与传过来的相同,就调用action_add_queue_tail()将该action链入action_queue队列的尾部。
在看下queue_builtin_action()函数的两个参数:第一个为一个函数指针,第二个看形参名就知道是action的trigger。来具体看下代码:
569void queue_builtin_action(int (*func)(int nargs, char **args), char *name)
570{
571 struct action *act;
572 struct command *cmd;
573
574 act = calloc(1, sizeof(*act));
575 act->name = name;
576 list_init(&act->commands);
577 list_init(&act->qlist);
578
579 cmd = calloc(1, sizeof(*cmd));
580 cmd->func = func;
581 cmd->args[0] = name;
582 list_add_tail(&act->commands, &cmd->clist);
583
584 list_add_tail(&action_list, &act->alist);
585 action_add_queue_tail(act);
586}
代码也是非常之简单,主要做了三件事情:
1)构造一个触发器为name的struct action结构体,并创建一个struct command,对应函数为行参fund
2)将struct action添加到action_list链表末尾。
3)将struct action添加到action_queue链表末尾。
好的现在万事俱备,只差执行action_queue队列了。
4.action的执行
1083 for(;;) {
1084 int nr, i, timeout = -1;
1085
1086 execute_one_command();
1087 restart_processes();
1088 ...
1089 }
现在到了init.c->main()最后的for循环了。
execute_one_command()的作用就是执行action的一条command。
restart_processes()作用就是执行标志为SVC_RESTARTING的进程,fork一个新进程。
分别看下代码:
531void execute_one_command(void)
532{
533 int ret;
534
535 if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
536 cur_action = action_remove_queue_head();
537 cur_command = NULL;
538 if (!cur_action)
539 return;
540 INFO("processing action %p (%s)\n", cur_action, cur_action->name);
541 cur_command = get_first_command(cur_action);
542 } else {
543 cur_command = get_next_command(cur_action, cur_command);
544 }
545
546 if (!cur_command)
547 return;
548
549 ret = cur_command->func(cur_command->nargs, cur_command->args);
550 INFO("command '%s' r=%d\n", cur_command->args[0], ret);
551}
595struct action *action_remove_queue_head(void)
596{
597 if (list_empty(&action_queue)) {
598 return 0;
599 } else {
600 struct listnode *node = list_head(&action_queue);
601 struct action *act = node_to_item(node, struct action, qlist);
602 list_remove(node);
603 list_init(node);
604 return act;
605 }
606}
514static struct command *get_next_command(struct action *act, struct command *cmd)
515{
516 struct listnode *node;
517 node = cmd->clist.next;
518 if (!node)
519 return NULL;
520 if (node == &act->commands)
521 return NULL;
522
523 return node_to_item(node, struct command, clist);
524}
execute_one_command()先判断当前action为空,或者当前command为空,或者当前command是action的最后一个command,就会会通过action_remove_queue_head()从action_queue头部取下一个listnode并把它转换成action结构,并将cur_action指针指向它。然后从该action的command队列中取出它的第一个listnode并把它转换成command结构,接着再将cur_command指针指向这个command。
如果前面一步的if判断结果为false,说明当前有一个action在执行,并且它的command还没有执行完,所以通过get_next_command()取出它的下一个command。
最后就执行了前面得到的command的func函数指针指向的函数。
再看下restart_processes():
434static void restart_processes()
435{
436 process_needs_restart = 0;
437 service_for_each_flags(SVC_RESTARTING,
438 restart_service_if_needed);
439}
491void service_for_each_flags(unsigned matchflags,
492 void (*func)(struct service *svc))
493{
494 struct listnode *node;
495 struct service *svc;
496 list_for_each(node, &service_list) {
497 svc = node_to_item(node, struct service, slist);
498 if (svc->flags & matchflags) {
499 func(svc);
500 }
501 }
502}
418static void restart_service_if_needed(struct service *svc)
419{
420 time_t next_start_time = svc->time_started + 5;
421
422 if (next_start_time <= gettime()) {
423 svc->flags &= (~SVC_RESTARTING);
424 service_start(svc, NULL);
425 return;
426 }
427
428 if ((next_start_time < process_needs_restart) ||
429 (process_needs_restart == 0)) {
430 process_needs_restart = next_start_time;
431 }
432}
service_for_each_flags()遍历service_list列表,找出那些flags中携带有SVC_RESTARTING标志的service节点,并执行restart_service_if_needed()。注意:为了防止出现service频繁重启,本次启动距离上次启动时间不得少于5秒。
service的具体启动service_start()函数后面有时间单独写篇文章介绍。
这样在这个无限循环中,action_queue里面每个action的每个command最终都会被执行,每个满足条件的需重启的service也会被启动。
for循环中还有这样一段代码:
1089 if (!property_set_fd_init && get_property_set_fd() > 0) {
1090 ufds[fd_count].fd = get_property_set_fd();
1091 ufds[fd_count].events = POLLIN;
1092 ufds[fd_count].revents = 0;
1093 fd_count++;
1094 property_set_fd_init = 1;
1095 }
1096 if (!signal_fd_init && get_signal_fd() > 0) {
1097 ufds[fd_count].fd = get_signal_fd();
1098 ufds[fd_count].events = POLLIN;
1099 ufds[fd_count].revents = 0;
1100 fd_count++;
1101 signal_fd_init = 1;
1102 }
1103 if (!keychord_fd_init && get_keychord_fd() > 0) {
1104 ufds[fd_count].fd = get_keychord_fd();
1105 ufds[fd_count].events = POLLIN;
1106 ufds[fd_count].revents = 0;
1107 fd_count++;
1108 keychord_fd_init = 1;
1109 }
这三个描述符分别是用来监听属性改变、子进程死亡和组合按键的。他们分别是在
queue_builtin_action(property_service_init_action, “property_service_init”);
queue_builtin_action(signal_init_action, “signal_init”);
queue_builtin_action(keychord_init_action, “keychord_init”);
中被创建。
组合按键关键字是keycodes,如果某个service中含有keycodes选项,那么当用户按下某种组合键时,该service将被重启。init.rc中并没有包含该关键字的service,不详细阐述了。
/system/core/init/init.c
789static int property_service_init_action(int nargs, char **args)
790{
791 /* read any property files on system or data and
792 * fire up the property service. This must happen
793 * after the ro.foo properties are set above so
794 * that /data/local.prop cannot interfere with them.
795 */
796 start_property_service();
797 return 0;
798}
/system/core/init/property_service.c
581void start_property_service(void)
582{
583 int fd;
584
585 load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
586 load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
587 load_override_properties();
588 /* Read persistent properties after all default values have been loaded. */
589 load_persistent_properties();
590
591 fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
592 if(fd < 0) return;
593 fcntl(fd, F_SETFD, FD_CLOEXEC);
594 fcntl(fd, F_SETFL, O_NONBLOCK);
595
596 listen(fd, 8);
597 property_set_fd = fd;
598}
创建一个socket用于进程间通信,对应的socket文件为/dev/socket/property_service,然后将该文件的文件描述符保存在property_set_fd中。这样当某个进程调用property_set来设置属性时,就会通过该socket给init进程发送一个消息,最终init会在poll中发现socket文件中有内容可读:
1131 nr = poll(ufds, fd_count, timeout);
1132 if (nr <= 0)
1133 continue;
1134
1135 for (i = 0; i < fd_count; i++) {
1136 if (ufds[i].revents == POLLIN) {
1137 if (ufds[i].fd == get_property_set_fd())
1138 handle_property_set_fd();
在poll中如果描述符对应的文件有内容可读,则ufds[i].revents会被置为POLLIN。
接下来的调用流程为:
handle_property_set_fd() -> property_set()-> property_changed() -> queue_property_triggers()->action_add_queue_tail()
在queue_property_triggers函数中从全局action_list中匹配 property:=类型的Actions,如果属性数值满足,则将该Actions加入到action-queue中,这样该Actions中的command将会在之后的execute_one_command()被调用。
signal_init也是类似的流程
/system/core/init/init.c
799
800static int signal_init_action(int nargs, char **args)
801{
802 signal_init();
803 return 0;
804}
/system/core/init/signal_handler.c
131void signal_init(void)
132{
133 int s[2];
134
135 struct sigaction act;
136 memset(&act, 0, sizeof(act));
137 act.sa_handler = sigchld_handler;
138 act.sa_flags = SA_NOCLDSTOP;
139 sigaction(SIGCHLD, &act, 0);
140
141 /* create a signalling mechanism for the sigchld handler */
142 if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {
143 signal_fd = s[0];
144 signal_recv_fd = s[1];
145 fcntl(s[0], F_SETFD, FD_CLOEXEC);
146 fcntl(s[0], F_SETFL, O_NONBLOCK);
147 fcntl(s[1], F_SETFD, FD_CLOEXEC);
148 fcntl(s[1], F_SETFL, O_NONBLOCK);
149 }
150
151 handle_signal();
152}
36static void sigchld_handler(int s)
37{
38 write(signal_fd, &s, 1);
39}
linux系统中子进程死亡时会向父进程发送一个SIGCHLD信号,sigaction(SIGCHLD, &act, 0)就是注册收到子进程SIGCHLD信号后的回调方法。所以每当有子进程终止时,系统就会回调sigchld_handler()函数。sigchld_handler()非常简单,就是是向signal_init()中创建的“socket对”里的signal_fd写数据,然后init.c在poll函数中就能通过“socket对”的另一个句柄signal_recv_fd得到所写的数据。然后调用handle_signal():
1131 nr = poll(ufds, fd_count, timeout);
1132 if (nr <= 0)
1133 continue;
1134
1135 for (i = 0; i < fd_count; i++) {
1136 if (ufds[i].revents == POLLIN) {
1137 if (ufds[i].fd == get_property_set_fd())
1138 handle_property_set_fd();
1139 else if (ufds[i].fd == get_keychord_fd())
1140 handle_keychord();
1141 else if (ufds[i].fd == get_signal_fd())
1142 handle_signal();
1143 }
1144 }
/system/core/init/signal_handler.c
121void handle_signal(void)
122{
123 char tmp[32];
124
125 /* we got a SIGCHLD - reap and restart as needed */
126 read(signal_recv_fd, tmp, sizeof(tmp));
127 while (!wait_for_one_process(0))
128 ;
129}
44static int wait_for_one_process(int block)
45{
46 pid_t pid;
47 int status;
48 struct service *svc;
49 struct socketinfo *si;
50 time_t now;
51 struct listnode *node;
52 struct command *cmd;
53
54 while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );
55 if (pid <= 0) return -1;
56 INFO("waitpid returned pid %d, status = %08x\n", pid, status);
57
58 svc = service_find_by_pid(pid);
59 if (!svc) {
60 ERROR("untracked pid %d exited\n", pid);
61 return 0;
62 }
63
64 NOTICE("process '%s', pid %d exited\n", svc->name, pid);
65
66 if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
67 kill(-pid, SIGKILL);
68 NOTICE("process '%s' killing any children in process group\n", svc->name);
69 }
70
71 /* remove any sockets we may have created */
72 for (si = svc->sockets; si; si = si->next) {
73 char tmp[128];
74 snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
75 unlink(tmp);
76 }
77
78 svc->pid = 0;
79 svc->flags &= (~SVC_RUNNING);
80
81 /* oneshot processes go into the disabled state on exit,
82 * except when manually restarted. */
83 if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
84 svc->flags |= SVC_DISABLED;
85 }
86
87 /* disabled and reset processes do not get restarted automatically */
88 if (svc->flags & (SVC_DISABLED | SVC_RESET) ) {
89 notify_service_state(svc->name, "stopped");
90 return 0;
91 }
92
93 now = gettime();
94 if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
95 if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
96 if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
97 ERROR("critical process '%s' exited %d times in %d minutes; "
98 "rebooting into recovery mode\n", svc->name,
99 CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
100 android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
101 return 0;
102 }
103 } else {
104 svc->time_crashed = now;
105 svc->nr_crashed = 1;
106 }
107 }
108
109 svc->flags &= (~SVC_RESTART);
110 svc->flags |= SVC_RESTARTING;
111
112 /* Execute all onrestart commands for this service. */
113 list_for_each(node, &svc->onrestart.commands) {
114 cmd = node_to_item(node, struct command, clist);
115 cmd->func(cmd->nargs, cmd->args);
116 }
117 notify_service_state(svc->name, "restarting");
118 return 0;
119}
wait_for_one_process()中先找到挂掉的进程对应的service节点,并将该节点的flags添加SVC_RESTARTING标记,然后执行这个service节点中所有onrestart选项对应的动作。注意这里并没有直接重启service,而是在init.c->main()中的下一次for循环中,通过调用restart_processes()找到flags有SVC_RESTARTING标记的service重启他们。
本来想写的尽量详细,后来。。后来我就学乖了。。
参考博客:
http://blog.csdn.net/prife/article/details/41777245
http://blog.csdn.net/chenyufei1013/article/details/7927923