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

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 2 3 4 on <trigger>     <command>     <command>     <command>

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

1 2 3 4 on boot      ifup lo      hostname localhost      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 2 3 service <name> <pathname> [ <argument> ]*        <option>        <option>

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

1 2 3 4 5 6 7 8 9 service servicemanager /system/bin/servicemanager      class  core      user system      group system      critical      onrestart restart zygote      onrestart restart media      onrestart restart surfaceflinger      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 2 3 4 5 6 7 8 9 10 int  init_parse_config_file( const  char  *fn) {      char  *data;      data = read_file(fn, 0);      if  (!data)  return  -1;      /*  实际分析init.rc文件的代码  */      parse_config(fn, data);      DUMP();      return  0; }

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

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

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

  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,否则调用出错。

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

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 int  lookup_keyword( const  char  *s) {      switch  (*s++) {      case  'c' :      if  (! strcmp (s,  "opy" ))  return  K_copy;          if  (! strcmp (s,  "apability" ))  return  K_capability;          if  (! strcmp (s,  "hdir" ))  return  K_chdir;          if  (! strcmp (s,  "hroot" ))  return  K_chroot;          if  (! strcmp (s,  "lass" ))  return  K_class;          if  (! strcmp (s,  "lass_start" ))  return  K_class_start;          if  (! strcmp (s,  "lass_stop" ))  return  K_class_stop;          if  (! strcmp (s,  "lass_reset" ))  return  K_class_reset;          if  (! strcmp (s,  "onsole" ))  return  K_console;          if  (! strcmp (s,  "hown" ))  return  K_chown;          if  (! strcmp (s,  "hmod" ))  return  K_chmod;          if  (! strcmp (s,  "ritical" ))  return  K_critical;          break ;      case  'd' :          if  (! strcmp (s,  "isabled" ))  return  K_disabled;          if  (! strcmp (s,  "omainname" ))  return  K_domainname;          break ;       … …      case  'o' :          if  (! strcmp (s,  "n" ))  return  K_on;          if  (! strcmp (s,  "neshot" ))  return  K_oneshot;          if  (! strcmp (s,  "nrestart" ))  return  K_onrestart;          break ;      case  'r' :          if  (! strcmp (s,  "estart" ))  return  K_restart;          if  (! strcmp (s,  "estorecon" ))  return  K_restorecon;          if  (! strcmp (s,  "mdir" ))  return  K_rmdir;          if  (! strcmp (s,  "m" ))  return  K_rm;          break ;      case  's' :          if  (! strcmp (s,  "eclabel" ))  return  K_seclabel;          if  (! strcmp (s,  "ervice" ))  return  K_service;          if  (! strcmp (s,  "etcon" ))  return  K_setcon;          if  (! strcmp (s,  "etenforce" ))  return  K_setenforce;          if  (! strcmp (s,  "etenv" ))  return  K_setenv;          if  (! strcmp (s,  "etkey" ))  return  K_setkey;          if  (! strcmp (s,  "etprop" ))  return  K_setprop;          if  (! strcmp (s,  "etrlimit" ))  return  K_setrlimit;          if  (! strcmp (s,  "etsebool" ))  return  K_setsebool;          if  (! strcmp (s,  "ocket" ))  return  K_socket;          if  (! strcmp (s,  "tart" ))  return  K_start;          if  (! strcmp (s,  "top" ))  return  K_stop;          if  (! strcmp (s,  "ymlink" ))  return  K_symlink;          if  (! strcmp (s,  "ysclktz" ))  return  K_sysclktz;          break ;      case  't' :          if  (! strcmp (s,  "rigger" ))  return  K_trigger;          break ;      case  'u' :          if  (! strcmp (s,  "ser" ))  return  K_user;          break ;      case  'w' :          if  (! strcmp (s,  "rite" ))  return  K_write;          if  (! strcmp (s,  "ait" ))  return  K_wait;          break ;      }      return  K_UNKNOWN; }

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

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

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

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

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

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #ifndef KEYWORD int  do_chroot( int  nargs,  char  **args); … … int  do_export( int  nargs,  char  **args); int  do_hostname( int  nargs,  char  **args); int  do_rmdir( int  nargs,  char  **args); int  do_loglevel( int  nargs,  char  **args); int  do_load_persist_props( int  nargs,  char  **args); int  do_wait( int  nargs,  char  **args); #define __MAKE_KEYWORD_ENUM__ /* "K_chdir", ENUM */ #define KEYWORD(symbol, flags, nargs, func) K_##symbol, enum  {      K_UNKNOWN, #endif      KEYWORD(capability,  OPTION,  0, 0)      KEYWORD(chdir,       COMMAND, 1, do_chdir)      KEYWORD(chroot,      COMMAND, 1, do_chroot)      KEYWORD( class ,       OPTION,  0, 0)      KEYWORD(class_start, COMMAND, 1, do_class_start)      KEYWORD(class_stop,  COMMAND, 1, do_class_stop)      KEYWORD(class_reset, COMMAND, 1, do_class_reset)      KEYWORD(console,     OPTION,  0, 0)      … …      KEYWORD(critical,    OPTION,  0, 0)      KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)      KEYWORD(ioprio,      OPTION,  0, 0) #ifdef __MAKE_KEYWORD_ENUM__      KEYWORD_COUNT, }; #undef __MAKE_KEYWORD_ENUM__ #undef KEYWORD #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 2 #define KEYWORD(symbol, flags, nargs, func) \      [ K_##symbol ] = { #symbol, func, nargs +  1 , flags, },

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int  do_chroot( int  nargs,  char  **args); … … enum { K_UNKNOWN, K_ capability, K_ chdir, … … } #define KEYWORD(symbol, flags, nargs, func) \      [ K_##symbol ] = { #symbol, func, nargs + 1, flags, }, struct  {      const  char  *name;      int  (*func)( int  nargs,  char  **args);      unsigned  char  nargs;      unsigned  char  flags; } keyword_info[KEYWORD_COUNT] = {      [ K_UNKNOWN ] = {  "unknown" , 0, 0, 0 },      [K_ capability] = { " capability " , 0, 1, OPTION },      [K_ chdir] = { "chdir" , do_chdir ,2, COMMAND},      … … #include "keywords.h" };

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

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

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

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static  void  parse_line_service( struct  parse_state *state,  int  nargs,  char  **args) {      struct  service *svc = state->context;      struct  command *cmd;      int  i, kw, kw_nargs;                    if  (nargs == 0) {          return ;      }                    svc->ioprio_class = IoSchedClass_NONE;                    kw = lookup_keyword(args[0]);      //  下面处理每一个option      switch  (kw) {      case  K_capability:          break ;      … …      case  K_group:          if  (nargs < 2) {              parse_error(state,  "group option requires a group id\n" );          else  if  (nargs > NR_SVC_SUPP_GIDS + 2) {              parse_error(state,  "group option accepts at most %d supp. groups\n" ,                          NR_SVC_SUPP_GIDS);          else  {              int  n;              svc->gid = decode_uid(args[1]);              for  (n = 2; n < nargs; n++) {                  svc->supp_gids[n-2] = decode_uid(args[n]);              }              svc->nr_supp_gids = n - 2;          }          break ;      case  K_keycodes:          if  (nargs < 2) {              parse_error(state,  "keycodes option requires atleast one keycode\n" );          else  {              svc->keycodes =  malloc ((nargs - 1) *  sizeof (svc->keycodes[0]));              if  (!svc->keycodes) {                  parse_error(state,  "could not allocate keycodes\n" );              else  {                  svc->nkeycodes = nargs - 1;                  for  (i = 1; i < nargs; i++) {                      svc->keycodes[i - 1] =  atoi (args[i]);                  }              }          }          break ;          … …       }      …… }

      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文件中每一行的行为。

 本文转自 androidguy 51CTO博客,原文链接: 本文转自 androidguy 51CTO博客,原文链接:,如需转载请自行联系原作者

,如需转载请自行联系原作者

点赞