转自:http://www.cnblogs.com/nokiaguy/p/3164799.html

Android的init过程(一)

本文使用的软件版本

Android:4.2.2

Linux内核:3.1.10

在上一篇文章中介绍了init的初始化第一阶段,也就是处理各种属性。在本文将会详细分析init最重要的一环:解析init.rc文件。

init.rc 文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否则机械地分析 init.c及其相关文件的源代码毫无意义。

为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果有编译好的Android源代码, 在<Android源代码根目录>out/target/product/generic/root目录也可找到init.rc文件。

AIL由如下4部分组成。

1.  动作(Actions)

2.  命令(Commands)

3. 服务(Services)

4.  选项(Options)

这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。可以使用反斜杠转义符在 Token中插入空格。双引号可以将多个由空格分隔的Tokens合成一个Tokens。如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是 说,可以用反斜杠将多行代码连接成一行代码。

AIL的注释与很多Shell脚本一行,以#开头。

AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。也就是说,每一个Actions或 Services确定一个Section。而所有的Commands和Options只能属于最近定义的Section。如果Commands和 Options在第一个Section之前被定义,它们将被忽略。

Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。

下面来看看Actions、Services、Commands和Options分别应如何设置。

Actions的语法格式如下:

  1. on <trigger>
  2. <command>
  3. <command>
  4. <command>

也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action。

  1. on boot
  2. ifup lo
  3. hostname localhost
  4. domainname localdomain

其中boot是触发器,下面三行是command

那么init.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。

1.  boot

这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger

2.  <name>=<value>

当属性<name>被设置成<value>时被触发。例如,

on property:vold.decrypt=trigger_reset_main

class_reset main

3.  device-added-<path>

当设备节点被添加时触发

4.  device-removed-<path>

当设备节点被移除时添加

5. service-exited-<name>

会在一个特定的服务退出时触发

Actions后需要跟若干个命令,这些命令如下:

1.  exec <path> [<argument> ]*

创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。

2.  export <name> <value>

在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)

3.  ifup <interface>

启动网络接口

4.  import <filename>

指定要解析的其他配置文件。常被用于当前配置文件的扩展

5.  hostname <name>

设置主机名

6.  chdir <directory>

改变工作目录

7.  chmod <octal-mode><path>

改变文件的访问权限

8.  chown <owner><group> <path>

更改文件的所有者和组

9.  chroot <directory>

改变处理根目录

10.  class_start<serviceclass>

启动所有指定服务类下的未运行服务。

11  class_stop<serviceclass>

停止指定服务类下的所有已运行的服务。

12.  domainname <name>

设置域名

13.  insmod <path>

加载<path>指定的驱动模块

14.  mkdir <path> [mode][owner] [group]

创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。

15. mount <type> <device> <dir> [<mountoption> ]*

试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。<mountoption>包括 "ro"、"rw"、"re

16.  setkey

保留,暂时未用

17.  setprop <name><value>

将系统属性<name>的值设为<value>。

18. setrlimit <resource> <cur> <max>

设置<resource>的rlimit (资源限制)

19.  start <service>

启动指定服务(如果此服务还未运行)。

20.stop<service>

停止指定服务(如果此服务在运行中)。

21. symlink <target> <path>

创建一个指向<path>的软连接<target>。

22. sysclktz <mins_west_of_gmt>

设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)

23.  trigger <event>

触发一个事件。用于Action排队

24.  wait <path> [<timeout> ]

等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定<timeout>,默认超时时间是5秒。

25. write <path> <string> [ <string> ]*

向<path>指定的文件写入一个或多个字符串。

Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。Services (服务)的形式如下:

  1. service <name> <pathname> [ <argument> ]*
  2. <option>
  3. <option>

例如,下面是一个标准的Service用法

  1. service servicemanager /system/bin/servicemanager
  2. class core
  3. user system
  4. group system
  5. critical
  6. onrestart restart zygote
  7. onrestart restart media
  8. onrestart restart surfaceflinger
  9. onrestart restart drm

Services的选项是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:

1.  critical

表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。

2. disabled

表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。

3.  setenv <name><value>

在进程启动时将环境变量<name>设置为<value>。

4.  socket <name><type> <perm> [ <user> [ <group> ] ]

Create a unix domain socketnamed /dev/socket/<name> and pass

its fd to the launchedprocess.  <type> must be"dgram", "stream" or "seqpacket".

User and group default to0.

创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。<type> 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。

5.  user <username>

在启动这个服务前改变该服务的用户名。此时默认为 root。

6.  group <groupname> [<groupname> ]*

在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。

7.  oneshot

服务退出时不重启。

8.  class <name>

指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。

9. onrestart

当服务重启,执行一个命令(下详)。

现在接着分析一下init是如何解析init.rc的。现在打开system/core/init/init.c文件,找到main函数。在上一篇文章中 分析了main函数的前一部分(初始化属性、处理内核命令行等),现在找到init_parse_config_file函数,调用代码如下:

init_parse_config_file("/init.rc");

这个方法主要负责初始化和分析init.rc文件。init_parse_config_file函数在init_parser.c文件中实现,代码如下:

  1. int init_parse_config_file(const char *fn)
  2. {
  3. char *data;
  4. data = read_file(fn, 0);
  5. if (!data) return -1;
  6. /* 实际分析init.rc文件的代码 */
  7. parse_config(fn, data);
  8. DUMP();
  9. return 0;
  10. }

init_parse_config_file方法开始调用了read_file函数打开了/init.rc文件,并返回了文件的内容(char*类 型),然后最核心的函数是parse_config。该函数也在init_parser.c文件中实现,代码如下:

  1. static void parse_config(const char *fn, char *s)
  2. {
  3. struct parse_state state;
  4. struct listnode import_list;
  5. struct listnode *node;
  6. char *args[INIT_PARSER_MAXARGS];
  7. int nargs;
  8.  
  9. nargs = 0;
  10. state.filename = fn;
  11. state.line = 0;
  12. state.ptr = s;
  13. state.nexttoken = 0;
  14. state.parse_line = parse_line_no_op;
  15.  
  16. list_init(&import_list);
  17. state.priv = &import_list;
  18. /* 开始获取每一个token,然后分析这些token,每一个token就是有空格、字表符和回车符分隔的字符串
  19. */
  20. for (;;) {
  21. /* next_token函数相当于词法分析器 */
  22. switch (next_token(&state)) {
  23. case T_EOF: /* init.rc文件分析完毕 */
  24. state.parse_line(&state, 0, 0);
  25. goto parser_done;
  26. case T_NEWLINE: /* 分析每一行的命令 */
  27. /* 下面的代码相当于语法分析器 */
  28. state.line++;
  29. if (nargs) {
  30. int kw = lookup_keyword(args[0]);
  31. if (kw_is(kw, SECTION)) {
  32. state.parse_line(&state, 0, 0);
  33. parse_new_section(&state, kw, nargs, args);
  34. } else {
  35. state.parse_line(&state, nargs, args);
  36. }
  37. nargs = 0;
  38. }
  39. break;
  40. case T_TEXT: /* 处理每一个token */
  41. if (nargs < INIT_PARSER_MAXARGS) {
  42. args[nargs++] = state.text;
  43. }
  44. break;
  45. }
  46. }
  47.  
  48. parser_done:
  49. /* 最后处理由import导入的初始化文件 */
  50. list_for_each(node, &import_list) {
  51. struct import *import = node_to_item(node, struct import, list);
  52. int ret;
  53.  
  54. INFO("importing '%s'", import->filename);
  55. /* 递归调用 */
  56. ret = init_parse_config_file(import->filename);
  57. if (ret)
  58. ERROR("could not import file '%s' from '%s'\n",
  59. import->filename, fn);
  60. }
  61. }

parse_config方法的代码就比较复杂了,现在先说说该方法的基本处理流程。首先会调用  list_init(&import_list)初始化一个链表,该链表是用于存储通过import语句导入的初始化文件名。然后开始开始在 for循环中分析init.rc文件中的每一行代码。最后将init.rc文件分析完后,就会进入parser_done部分,并递归调用 init_parse_config_file方法分析通过import导入的初始化文件。

通过分析parse_config方法的原理,感觉也并不是很复杂。不过分析parse_config方法的具体代码,还需要点编译原理的知识(只是概念 上的就可以)。在for循环中调用了一个next_token方法不断从init.rc文件中获取token。这里的token,就是一种编程语言的最小 单元,也就是不可再分。例如,对于传统的编程语言,if、then等关键字、变量名等标识符都属于一个token。而对于init.rc文件来 说,import、on、以及触发器的参数值,都属于一个token。

一个完整的编译器(或解析器)最开始需要进行词法和语法分析,词法分析就是在源代码文件中挑出一个个的Token,也就是说,词法分析器的返回值是 Token,而语法分析器的输入就是词法分析器的输出。也就是说,语法分析器需要分析一个个的token,而不是一个个的字符。由于init解析语言很简 单,所以就将词法和语法分析器放到了一起。词法分析器就是next_token函数,而语法分析器就是T_NEWLINE分支中的代码。这些就清楚多了。 现在先看看next_token函数(在parser.c文件中实现)是如何获取每一个token的。

  1. int next_token(struct parse_state *state)
  2. {
  3. char *x = state->ptr;
  4. char *s;
  5.  
  6. if (state->nexttoken) {
  7. int t = state->nexttoken;
  8. state->nexttoken = 0;
  9. return t;
  10. }
  11. /* 在这里开始一个字符一个字符地分析 */
  12. for (;;) {
  13. switch (*x) {
  14. case 0:
  15. state->ptr = x;
  16. return T_EOF;
  17. case '\n':
  18. x++;
  19. state->ptr = x;
  20. return T_NEWLINE;
  21. case ' ':
  22. case '\t':
  23. case '\r':
  24. x++;
  25. continue;
  26. case '#':
  27. while (*x && (*x != '\n')) x++;
  28. if (*x == '\n') {
  29. state->ptr = x+1;
  30. return T_NEWLINE;
  31. } else {
  32. state->ptr = x;
  33. return T_EOF;
  34. }
  35. default:
  36. goto text;
  37. }
  38. }
  39.  
  40. textdone:
  41. state->ptr = x;
  42. *s = 0;
  43. return T_TEXT;
  44. text:
  45. state->text = s = x;
  46. textresume:
  47. for (;;) {
  48. switch (*x) {
  49. case 0:
  50. goto textdone;
  51. case ' ':
  52. case '\t':
  53. case '\r':
  54. x++;
  55. goto textdone;
  56. case '\n':
  57. state->nexttoken = T_NEWLINE;
  58. x++;
  59. goto textdone;
  60. case '"':
  61. x++;
  62. for (;;) {
  63. switch (*x) {
  64. case 0:
  65. /* unterminated quoted thing */
  66. state->ptr = x;
  67. return T_EOF;
  68. case '"':
  69. x++;
  70. goto textresume;
  71. default:
  72. *s++ = *x++;
  73. }
  74. }
  75. break;
  76. case '\\':
  77. x++;
  78. switch (*x) {
  79. case 0:
  80. goto textdone;
  81. case 'n':
  82. *s++ = '\n';
  83. break;
  84. case 'r':
  85. *s++ = '\r';
  86. break;
  87. case 't':
  88. *s++ = '\t';
  89. break;
  90. case '\\':
  91. *s++ = '\\';
  92. break;
  93. case '\r':
  94. /* \ <cr> <lf> -> line continuation */
  95. if (x[1] != '\n') {
  96. x++;
  97. continue;
  98. }
  99. case '\n':
  100. /* \ <lf> -> line continuation */
  101. state->line++;
  102. x++;
  103. /* eat any extra whitespace */
  104. while((*x == ' ') || (*x == '\t')) x++;
  105. continue;
  106. default:
  107. /* unknown escape -- just copy */
  108. *s++ = *x++;
  109. }
  110. continue;
  111. default:
  112. *s++ = *x++;
  113. }
  114. }
  115. return T_EOF;
  116. }

next_token函数的代码还是很多的,不过原理到很简单。就是逐一读取init.rc文件(还有import导入的初始化文件)的字符,并将 由空格、“/t”和“/r”分隔的字符串挑出来,并通过state->text返回。如果返回了正常的token,next_token函数就返回 T_TEXT。如果一行结束,就返回T_NEWLINE,如果init.rc文件的内容已读取完,就返回T_EOF。当返回T_NEWLINE时,开始语 法分析(由于init初始化语言是基于行的,所以语言分析实际上就是分析init.rc文件的每一行,只是这些行已经被分解成一个个token了)。感兴 趣的读者可以详细分析一下next_token函数的代码,尽管代码很多,但并不复杂。而且还很有意思。

现在回到parse_config函数,先看一下T_TEXT分支。该分支将获得的每一行的token都存储在args数组中。现在来看 T_NEWLINE分支。该分支的代码涉及到一个state.parse_line函数指针,该函数指针指向的函数负责具体的分析工作。但我们发现,一看 是该函数指针指向了一个空函数parse_line_no_op,实际上,一开始该函数指针什么都不做,只是为了使该函数一开始不至于为null,否则调 用出错。

现在来回顾一下T_NEWLINE分支的完整代码。

  1. case T_NEWLINE:
  2. state.line++;
  3. if (nargs) {
  4. int kw = lookup_keyword(args[0]);
  5. if (kw_is(kw, SECTION)) {
  6. state.parse_line(&state, 0, 0);
  7. parse_new_section(&state, kw, nargs, args);
  8. } else {
  9. state.parse_line(&state, nargs, args);
  10. }
  11. nargs = 0;
  12. }
  13. break;

在上面的代码中首先调用了lookup_keyword方法搜索关键字。该方法的作用是判断当前行是否合法,也就是根据Init初始化语言预定义的关键字 查询,如果未查到,返回K_UNKNOWN。lookup_keyword方法在init_parser.c文件中实现,代码如下:

  1. int lookup_keyword(const char *s)
  2. {
  3. switch (*s++) {
  4. case 'c':
  5. if (!strcmp(s, "opy")) return K_copy;
  6. if (!strcmp(s, "apability")) return K_capability;
  7. if (!strcmp(s, "hdir")) return K_chdir;
  8. if (!strcmp(s, "hroot")) return K_chroot;
  9. if (!strcmp(s, "lass")) return K_class;
  10. if (!strcmp(s, "lass_start")) return K_class_start;
  11. if (!strcmp(s, "lass_stop")) return K_class_stop;
  12. if (!strcmp(s, "lass_reset")) return K_class_reset;
  13. if (!strcmp(s, "onsole")) return K_console;
  14. if (!strcmp(s, "hown")) return K_chown;
  15. if (!strcmp(s, "hmod")) return K_chmod;
  16. if (!strcmp(s, "ritical")) return K_critical;
  17. break;
  18. case 'd':
  19. if (!strcmp(s, "isabled")) return K_disabled;
  20. if (!strcmp(s, "omainname")) return K_domainname;
  21. break;

  22. case 'o':
  23. if (!strcmp(s, "n")) return K_on;
  24. if (!strcmp(s, "neshot")) return K_oneshot;
  25. if (!strcmp(s, "nrestart")) return K_onrestart;
  26. break;
  27. case 'r':
  28. if (!strcmp(s, "estart")) return K_restart;
  29. if (!strcmp(s, "estorecon")) return K_restorecon;
  30. if (!strcmp(s, "mdir")) return K_rmdir;
  31. if (!strcmp(s, "m")) return K_rm;
  32. break;
  33. case 's':
  34. if (!strcmp(s, "eclabel")) return K_seclabel;
  35. if (!strcmp(s, "ervice")) return K_service;
  36. if (!strcmp(s, "etcon")) return K_setcon;
  37. if (!strcmp(s, "etenforce")) return K_setenforce;
  38. if (!strcmp(s, "etenv")) return K_setenv;
  39. if (!strcmp(s, "etkey")) return K_setkey;
  40. if (!strcmp(s, "etprop")) return K_setprop;
  41. if (!strcmp(s, "etrlimit")) return K_setrlimit;
  42. if (!strcmp(s, "etsebool")) return K_setsebool;
  43. if (!strcmp(s, "ocket")) return K_socket;
  44. if (!strcmp(s, "tart")) return K_start;
  45. if (!strcmp(s, "top")) return K_stop;
  46. if (!strcmp(s, "ymlink")) return K_symlink;
  47. if (!strcmp(s, "ysclktz")) return K_sysclktz;
  48. break;
  49. case 't':
  50. if (!strcmp(s, "rigger")) return K_trigger;
  51. break;
  52. case 'u':
  53. if (!strcmp(s, "ser")) return K_user;
  54. break;
  55. case 'w':
  56. if (!strcmp(s, "rite")) return K_write;
  57. if (!strcmp(s, "ait")) return K_wait;
  58. break;
  59. }
  60. return K_UNKNOWN;
  61. }

lookup_keyword方法按26个字母顺序(关键字首字母)进行处理。

现在回到parse_config方法的T_NEWLIEN分支,接下来调用了kw_is宏具体判断当前行是否合法,该宏以及SECTION宏的定义如下。根据这些代码。明显是keyword_info数组中的某个元素的flags成员变量的值取最后一位。

  1. #define SECTION 0x01
  2. #define kw_is(kw, type) (keyword_info[kw].flags & (type))

现在问题又转到keyword_info数组了。该数组也在init_parser.c文件中定义,代码如下:

  1. #include "keywords.h"
  2. #define KEYWORD(symbol, flags, nargs, func) \
  3. [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
  4. struct {
  5. const char *name;
  6. int (*func)(int nargs, char **args);
  7. unsigned char nargs;
  8. unsigned char flags;
  9. } keyword_info[KEYWORD_COUNT] = {
  10. [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
  11. #include "keywords.h"
  12. };

从表面上看,keyword_info数组是一个struct数组,但本质上,是一个map。为每一个数组元素设置了一个key,例如,数组元素{ "unknown", 0, 0,0 }的key是K_UNKNOWN,而#include “keywords.h”大有玄机。上面的代码中引用了两次keywords.h文件,现在可以看一下keywords.h文件的代码。

  1. #ifndef KEYWORD
  2. int do_chroot(int nargs, char **args);

  3. int do_export(int nargs, char **args);
  4. int do_hostname(int nargs, char **args);
  5. int do_rmdir(int nargs, char **args);
  6. int do_loglevel(int nargs, char **args);
  7. int do_load_persist_props(int nargs, char **args);
  8. int do_wait(int nargs, char **args);
  9. #define __MAKE_KEYWORD_ENUM__
  10. /*
  11. "K_chdir", ENUM
  12. */
  13. #define KEYWORD(symbol, flags, nargs, func) K_##symbol,
  14. enum {
  15. K_UNKNOWN,
  16. #endif
  17. KEYWORD(capability, OPTION, 0, 0)
  18. KEYWORD(chdir, COMMAND, 1, do_chdir)
  19. KEYWORD(chroot, COMMAND, 1, do_chroot)
  20. KEYWORD(class, OPTION, 0, 0)
  21. KEYWORD(class_start, COMMAND, 1, do_class_start)
  22. KEYWORD(class_stop, COMMAND, 1, do_class_stop)
  23. KEYWORD(class_reset, COMMAND, 1, do_class_reset)
  24. KEYWORD(console, OPTION, 0, 0)

  25. KEYWORD(critical, OPTION, 0, 0)
  26. KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props)
  27. KEYWORD(ioprio, OPTION, 0, 0)
  28. #ifdef __MAKE_KEYWORD_ENUM__
  29. KEYWORD_COUNT,
  30. };
  31. #undef __MAKE_KEYWORD_ENUM__
  32. #undef KEYWORD
  33. #endif

从keywords.h文件的代码可以看出,如果未定义KEYWORD宏,则在keywords.h文件中定义一个KEYWORD宏,以及一个枚举类型, 其中K_##symbol的##表示连接的意思。而这个KEYWORD宏只用了第一个参数(symbol)。例 如,KEYWORD(chdir,       COMMAND, 1, do_chdir)就会生成K_chdir。

而在keyword_info结构体数组中再次导入keywords.h文件,这是KEYWORD宏已经在init_parser.c文件中重新定义,所以第一次导入keywords.h文件使用的是如下的宏。

  1. #define KEYWORD(symbol, flags, nargs, func) \
  2. [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },

这下就明白了,如果不使用keywords.h文件,直接将所有的代码都写到init_parser.c文件中,就会有下面的代码。

  1. int do_chroot(int nargs, char **args);

  2. enum
  3. {
  4. K_UNKNOWN,
  5. K_ capability,
  6. K_ chdir,

  7. }
  8. #define KEYWORD(symbol, flags, nargs, func) \
  9. [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
  10. struct {
  11. const char *name;
  12. int (*func)(int nargs, char **args);
  13. unsigned char nargs;
  14. unsigned char flags;
  15. } keyword_info[KEYWORD_COUNT] = {
  16. [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
  17. [K_ capability] = {" capability ", 0, 1, OPTION },
  18. [K_ chdir] = {"chdir", do_chdir ,2, COMMAND},

  19. #include "keywords.h"
  20. };

可能我们还记着lookup_keyword方法,该方法的返回值就是keyword_info数组的key。

在keywords.h前面定义的函数指针都是处理init.rc文件中service、action和command的。现在就剩下一个问题了,在哪里 为这些函数指针赋值呢,也就是说,具体处理每个部分的函数在哪里呢。现在回到前面的语法分析部分。如果当前行合法,则会执行 parse_new_section函数(在init_parser.c文件中实现),该函数将为section和action设置处理这两部分的函数。 parse_new_section函数的代码如下:

  1. void parse_new_section(struct parse_state *state, int kw,
  2. int nargs, char **args)
  3. {
  4. printf("[ %s %s ]\n", args[0],
  5. nargs > 1 ? args[1] : "");
  6. switch(kw) {
  7. case K_service: // 处理service
  8. state->context = parse_service(state, nargs, args);
  9. if (state->context) {
  10. state->parse_line = parse_line_service;
  11. return;
  12. }
  13. break;
  14. case K_on: // 处理action
  15. state->context = parse_action(state, nargs, args);
  16. if (state->context) {
  17. state->parse_line = parse_line_action;
  18. return;
  19. }
  20. break;
  21. case K_import: // 单独处理import导入的初始化文件。
  22. parse_import(state, nargs, args);
  23. break;
  24. }
  25. state->parse_line = parse_line_no_op;
  26. }

现在看一下处理service的函数(parse_line_service)。

  1. static void parse_line_service(struct parse_state *state, int nargs, char **args)
  2. {
  3. struct service *svc = state->context;
  4. struct command *cmd;
  5. int i, kw, kw_nargs;
  6.  
  7. if (nargs == 0) {
  8. return;
  9. }
  10.  
  11. svc->ioprio_class = IoSchedClass_NONE;
  12.  
  13. kw = lookup_keyword(args[0]);
  14. // 下面处理每一个option
  15. switch (kw) {
  16. case K_capability:
  17. break;

  18. case K_group:
  19. if (nargs < 2) {
  20. parse_error(state, "group option requires a group id\n");
  21. } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
  22. parse_error(state, "group option accepts at most %d supp. groups\n",
  23. NR_SVC_SUPP_GIDS);
  24. } else {
  25. int n;
  26. svc->gid = decode_uid(args[1]);
  27. for (n = 2; n < nargs; n++) {
  28. svc->supp_gids[n-2] = decode_uid(args[n]);
  29. }
  30. svc->nr_supp_gids = n - 2;
  31. }
  32. break;
  33. case K_keycodes:
  34. if (nargs < 2) {
  35. parse_error(state, "keycodes option requires atleast one keycode\n");
  36. } else {
  37. svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));
  38. if (!svc->keycodes) {
  39. parse_error(state, "could not allocate keycodes\n");
  40. } else {
  41. svc->nkeycodes = nargs - 1;
  42. for (i = 1; i < nargs; i++) {
  43. svc->keycodes[i - 1] = atoi(args[i]);
  44. }
  45. }
  46. }
  47. break;

  48. }
  49. ……
  50. }

Action的处理方式与service类似,读者可以自行查看相应的函数代码。现在一切都清楚了。处理service的函数是 parse_line_service,处理action的函数是parse_line_action。而前面的state.parse_line根据当 前是service还是action,指向这两个处理函数中的一个,并执行相应的函数处理actioncommand和serviceoption。

综合上述,实际上分析init.rc文件的过程就是通过一系列地处理,最终转换为通过parse_line_service或parse_line_action函数分析Init.rc文件中每一行的行为。

Android的init过程(二):初始化语言(init.rc)解析【转】的更多相关文章

  1. android Activity启动过程(二)从ActivityManagerService的startActivity到栈顶Activity的onPause过程

    ActivityManagerService.startActivity() ActvityiManagerService.startActivityAsUser() ActivityStackSup ...

  2. Android的init过程(二):初始化语言(init.rc)解析

    Android的init过程(一) 本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10 在上一篇文章中介绍了init的初始化第一阶段,也就是处理各种属性.在本文将会详细分析i ...

  3. Android的init过程详解(一)

    Android的init过程详解(一) Android的init过程(二):初始化语言(init.rc)解析 本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10 本文及后续几 ...

  4. Android的init过程:init.rc解析流程

    这几天打算看下安卓的代码,看优秀的源代码也是一种学习过程,看源代码的过程就感觉到,安卓确实是深受linux内核的影响,不少数据结构的使用方法全然一致.花了一中午时间,研究了下init.rc解析过程,做 ...

  5. Android的init过程详解(一)(转)

    本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10 本文及后续几篇文章将对Android的初始化(init)过程进行详细地.剥丝抽茧式地分析,并且在其中穿插了大量的知识,希望 ...

  6. i.mx6 Android5.1.1 初始化流程之init.rc解析(未完成)

    接上一篇:i.mx6 Android5.1.1 初始化流程之init进程 参考资料:http://blog.csdn.net/mr_raptor/article/category/799879 这个博 ...

  7. iOS 初始化(init、initWithNibName、initWithCoder、initWithFrame)

    很多朋友如果是初学iOS开发,可能会被其中的几个加载方法给搞得晕头转向的,但是这几个方法又是作为iOS程序员必须要我们掌握的方法,下面我将对这几个方法做一下分析和对比,看看能不能增加大家对几个方法的理 ...

  8. Android VLC播放器二次开发2——CPU类型检查+界面初始化

    上一篇讲了VLC整个程序的模块划分和界面主要使用的技术,今天分析一下VLC程序初始化过程,主要是初始化界面.加载解码库的操作.今天主要分析一下org.videolan.vlc.gui.MainActi ...

  9. Android For JNI(二)——C语言中的数据类型,输出,输入函数以及操作内存地址,内存修改器

    Android For JNI(二)--C语言中的数据类型,输出,输入函数以及操作内存地址,内存修改器 当我们把Hello World写完之后,我们就可以迈入C的大门了,今天就来讲讲基本的一些数据类型 ...

随机推荐

  1. Ubuntu中Apache修改DocumentRoot(修改网站根目录)

    今天配置好Apache+PHP+MySQL但是apache默认DocumentRoot是/var/www想把它改到我Windows下进行测试的k:/wwwroot把 apache2.conf 翻了好几 ...

  2. Speed-BI 图表功能:服装订货与销售匹配分析

    在作为一个买手我们根据对市场的预测,订了一批的货回来. 我们总会有一个疑问:我的订货与市场的需求是一致的吗?是否出现了较大偏差.这时我们通过分析两个指标:订货占比与销售占比的差异,进行订货与销售的匹配 ...

  3. Oracle数据库之PL/SQL基础

    介绍PL/SQL之前,先介绍一个图像化工具:Oracle SQL Developer 在oracle的开发过程中, 我们难免会使用第三方开发的软件来辅助我们书写SQL, pl/sql是一个不错的sql ...

  4. system执行shell命令

    system - execute a shell command #include <stdlib.h> int system (const char *command); 描述 syst ...

  5. 堡垒机 paramiko 自动登陆代码

    #!/usr/bin/env python # Copyright (C) - Robey Pointer <robeypointer@gmail.com> # # This file i ...

  6. UITextField属性

    0.      enablesReturnKeyAutomatically 默认为No,如果设置为Yes,文本框中没有输入任何字符的话,右下角的返回按钮是disabled的. 1.borderStyl ...

  7. mac 启动nfsd共享

    # cat /etc/exports /Users/guang/jumpserver -maproot=root:wheel -alldirs -rw -network 192.168.244.0 - ...

  8. CPU boot up过程

    1.   CPU0 BOOT CPU1 BOOT 通过IPC互相通信 2.   CPU1 BOOT 完后,loop,等待IPC from CPU0 3.   cpu0 写IPC通知CPU1,cpu1 ...

  9. 二叉树的先序、中序以及后序遍历(递归 && 非递归)

    树节点定义: class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } 递归建立二 ...

  10. oracle 新手遇到常见问题的解决办法

    可能照成以下问题的原因也许有很多种,但是就小白而言,我只记录自己学习过程中遇到的所有的问题.希望对一些新手 小白们有所帮助. 原因是 sys 不是sysdba 用户,你要将其作为sysdba 用户登录 ...