Android4.4之init
来源:互联网 发布:highcharts ajax json 编辑:程序博客网 时间:2024/04/28 01:45
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;973974 if (!strcmp(basename(argv[0]), "ueventd"))975 return ueventd_main(argc, argv);976977 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()
982983 /* Get the basic filesystem setup we need put984 * together in the initramdisk on / and then we'll985 * let the rc file figure out the rest.986 */987 mkdir("/dev", 0755);988 mkdir("/proc", 0755);989 mkdir("/sys", 0755);990991 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);997998 /* 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 the1002 * device nodes for kmsg and null, otherwise we won't1003 * be able to remount / read-only later on.1004 * Now that tmpfs is mounted on /dev, we can actually1005 * 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 }395396 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__";3839 if (klog_fd >= 0) return; /* Already initialized */4041 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);10121013 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;404405 /* Hardware string was provided on kernel command line */406 if (hardware[0])407 return;408409 fd = open("/proc/cpuinfo", O_RDONLY);410 if (fd < 0) return;411412 n = read(fd, data, 1023);413 close(fd);414 if (n < 0) return;415416 data[n] = 0;417 hw = strstr(data, "\nHardware");418 rev = strstr(data, "\nRevision");419420 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 }434435 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);774775 /* 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 params777 * as props.778 */779 import_kernel_cmdline(0, import_kernel_nv);780 if (qemu[0])781 import_kernel_cmdline(1, import_kernel_nv);782783 /* now propogate the info given on command line to internal variables784 * used by init as well as the current required properties785 */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;449450 fd = open("/proc/cmdline", O_RDONLY);451 if (fd >= 0) {452 int n = read(fd, cmdline, 1023);453 if (n < 0) n = 0;454455 /* get rid of trailing newline, it happens */456 if (n > 0 && cmdline[n-1] == '\n') n--;457458 cmdline[n] = 0;459 close(fd);460 } else {461 cmdline[0] = 0;462 }463464 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);690691 if (value == 0) return;692 *value++ = 0;693 if (name_len == 0) return;694695 if (for_emulator) {696 /* in the emulator, export any kernel option with the697 * ro.kernel. prefix */698 char buff[PROP_NAME_MAX];699 int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );700701 if (len < (int)sizeof(buff))702 property_set( buff, value );703 return;704 }705706 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;712713 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 };734735 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 else740 property_set(prop_map[i].dest_prop, prop_map[i].def_val);741 }742743 ret = property_get("ro.boot.console", tmp);744 if (ret)745 strlcpy(console, tmp, sizeof(console));746747 /* save a copy for init's usage during boot */748 property_get("ro.bootmode", tmp);749 strlcpy(bootmode, tmp, sizeof(bootmode));750751 /* if this was given on kernel command line, override what we read752 * 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);757758 snprintf(tmp, PROP_VALUE_MAX, "%d", revision);759 property_set("ro.revision", tmp);760761 /* 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 else767 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);10181019 cb.func_audit = audit_callback;1020 selinux_set_callback(SELINUX_CB_AUDIT, cb);10211022 selinux_initialize();1023 /* These directories were necessarily created before initial policy load1024 * 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");10331034 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 a103 named class may be started or stopped together. A service104 is in the class "default" if one is not specified via the105 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;409410 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;354355 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;361362 list_init(&import_list);363 state.priv = &import_list;364365 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 }390391parser_done:392 list_for_each(node, &import_list) {393 struct import *import = node_to_item(node, struct import, list);394 int ret;395396 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 command27{28 /* list of commands in an action */29 struct listnode clist;//用于将command链入一个双链队列3031 int (*func)(int nargs, char **args);//command语句所对应的函数指针32 int nargs;//command语句所表示的命令对应的函数的参数个数33 char *args[1];//命令对应的函数的参数34};3536struct 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链入队列,暂时在代码中看到4344 unsigned hash;//在目前init实现中未使用45 const char *name;//action的trigger(用来标记action的执行时机)4647 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};5960struct svcenvinfo {61 struct svcenvinfo *next;62 const char *name;63 const char *value;64};6581struct service {82 /* list of all services */83 struct listnode slist;//用于将action链入service_list队列8485 const char *name;//service的name86 const char *classname;//设置service的类别,感觉有点像开机启动service的优先级,默认的class名称为default,还有core、main、charger8788 unsigned flags;////位图变量,其各个位代表不同的servcie的属性(对应service中的option字段)89 pid_t pid;////当service对应的程序执行时,存放其进程号 90 time_t time_started; //进程启动时间 91 time_t time_crashed; //存放第一次进程崩溃时间92 int nr_crashed; //存放进程崩溃次数 9394 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所隶属的附件组的数目 9899 char *seclabel;//存放selinux所需要的security context 100101 struct socketinfo *sockets;// 为service创建的sockets 102 struct svcenvinfo *envvars;// 为service设置的环境变量 103104 struct action onrestart; //服务重启时,执行的命令105106 /* keycodes for triggering this service via /dev/keychord */107 int *keycodes;//keycodes相关,init中未使用108 int nkeycodes;109 int keychord_id;110111 int ioprio_class;// io优先级 112 int ioprio_pri;113114 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.c39static 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_state25{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 listnode27{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;7273 if (state->nexttoken) {74 int t = state->nexttoken;75 state->nexttoken = 0;76 return t;77 }7879 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 }106107textdone: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-init13 # Set init and its forked children's oom_adj.14 write /proc/1/oom_adj -161516 # 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:s01920 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"5960#define KEYWORD(symbol, flags, nargs, func) \61 [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },6263struct {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;847848 if (nargs == 0) {849 return;850 }851852 kw = lookup_keyword(args[0]);853 if (!kw_is(kw, COMMAND)) {854 parse_error(state, "invalid command '%s'\n", args[0]);855 return;856 }857858 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/ueventd409 class core410 critical411 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 }624625 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 }630631 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;653654 if (nargs == 0) {655 return;656 }657658 svc->ioprio_class = IoSchedClass_NONE;659660 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);683684 if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {685 parse_error(state, "priority value must be range 0 - 7\n");686 break;687 }688689 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 }747748 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;815816 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);10421043 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");10471048 /* execute all the boot actions to get us started */1049 action_for_each_trigger("init", action_add_queue_tail);10501051 /* 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 }10581059 /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random1060 * wasn't ready immediately after wait_for_coldboot_done1061 */1062 queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");10631064 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");10671068 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 }10741075 /* run all property triggers based on current state of the properties */1076 queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");107710781079#if BOOTCHART1080 queue_builtin_action(bootchart_init_action, "bootchart_init");1081#endif1082 ...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;573574 act = calloc(1, sizeof(*act));575 act->name = name;576 list_init(&act->commands);577 list_init(&act->qlist);578579 cmd = calloc(1, sizeof(*cmd));580 cmd->func = func;581 cmd->args[0] = name;582 list_add_tail(&act->commands, &cmd->clist);583584 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;10851086 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;534535 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 }545546 if (!cur_command)547 return;548549 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;522523 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;421422 if (next_start_time <= gettime()) {423 svc->flags &= (~SVC_RESTARTING);424 service_start(svc, NULL);425 return;426 }427428 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 and792 * fire up the property service. This must happen793 * after the ro.foo properties are set above so794 * 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;584585 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();590591 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);595596 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;11341135 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
799800static 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];134135 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);140141 /* 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 }150151 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;11341135 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];124125 /* 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;5354 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);5758 svc = service_find_by_pid(pid);59 if (!svc) {60 ERROR("untracked pid %d exited\n", pid);61 return 0;62 }6364 NOTICE("process '%s', pid %d exited\n", svc->name, pid);6566 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 }7071 /* 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 }7778 svc->pid = 0;79 svc->flags &= (~SVC_RUNNING);8081 /* 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 }8687 /* 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 }9293 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 }108109 svc->flags &= (~SVC_RESTART);110 svc->flags |= SVC_RESTARTING;111112 /* 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
- Android4.4之init
- Android4.4的init进程
- Android4.4的init进程
- Android4.4的init进程
- Android4.0之init读取init.ic的过程
- Android4.4的init进程详解
- Android4.4之Keyguard
- Android4.4之WebView
- android4.4 之service (下)
- Android4.4之Keyguard--KeyguardSecurityModel
- Android4.4之Keyguard--KeyguardUpdateMonitor
- Android4.4之Keyguard--KeyguardMessageArea
- Android4.4之Keyguard--KeyguardSecurityViewHelper
- Android4.4之Keyguard--KeyguardSecurityView
- Android4.4之Keyguard--KeyguardHostView
- Android4.4之Keyguard--KeyguardViewManager
- Android4.4 之Bluetooth整理
- Android4.4 之Bluetooth整理
- svn客户端无法在连接服务器端的svn
- TextureView、SurfaceTexture、Surface
- 两列布局的多种方式
- 追求极简生活
- 真正意义上的编程第一天
- Android4.4之init
- CSS清除浮动方法集合
- Android 屏幕适配扫盲、教程
- Serial Chart软件使用说明
- LeetCode #11 Container With Most Water
- org.springframework.beans.factory.BeanCreationException: Error creating bean 
- LinkedList的方法分析
- IntelliJ IDEA使用技巧
- 时频特性分析(Matlab)