首页 > 代码库 > Android Init进程命令的执行和服务的启动

Android Init进程命令的执行和服务的启动

  这里开始分析init进程中配置文件的解析,在配置文件中的命令的执行和服务的启动。
  首先init是一个可执行文件,它的对应的Makfile是init/Android.mk。 Android.mk定义了init
程序在编译的时候,使用了哪些源码,以及生成方式。当init程序生成之后,最终会放到/init,
即根目录的init文件。通常所说的init进程就是执行这个init程序。

  执行这个init程序的代码是在KERNEL/init/main.c文件中的kernel_init()函数里,当kernel
把一些基本的工作,比如CPU初始化,内存初始化,输入输出初始化等等完成之后,就会找到
根目录下的init程序,然后执行这个程序。

  在Android系统中,init程序对应的代码在ANDROID/system/core/init/下,用Android.mk,文件
管理编译的。这个程序的入口函数是init.c的main函数。在Android下init进程的主要功能和其它
发行版的Linux的系统差不多,都是通过解析配置文件,完成Linux系统的基本的操作,比如
用户级别,权限的设定,一些安全策略的启动,如selinux的启动,基本的文件(/dev, /sys等)创建,
然后就是其它基本的进程。 init的进程的id永远为1,在系统中最基本的进程之一。

今天我们主要目的目标如下:
  1) init进程是如何解析配置文件的
  2) init进程是如何启动其它基本的进程的

1 init进程是如何解析配置文件
  1.1 了解init.rc文件的语法(见init.rc语法)
  1.2 init.rc对应的数据结构

  parse_state这个结构体是用来跟踪整个init.rc文件解析过程的。这个在阅读代码时,把这个结构体理解为一个篮子,

这个篮子中放的都是各种各样的rc文件内容即可。

1         struct parse_state2         struct command{3             struct listnode clist;//命令数列4 5             int (*func)(int nargs, char **args);//当前命令对应的函数,比如write命令,对应do_write(xxx)6             int nargs; //参数个数7             char *args[1]; //参数数组8         };

  action就是on xxx对应的部分,action主要是由command组成。Android系统在开机执行的顺序,
是按照这个action顺序执行的。也就是说系统触发不同的trigger,就顺序执行这个trigger对应的action
下的所有命令,这个执行过程是顺序执行的,没有并行进行。而且系统在启动过程中,都是按照action去
执行的。记住,是在开机过程中才是这么执行的。

        struct action {            struct listnode alist;//系统中所有的action的队列            struct listnode qlist;//等待触发trigger的队列            struct listnode tlist;//这个队列的意义不明,好在也没人用到这个队列            unsigned hash;            const char *name;//这个名字就是triggeer的名字            struct listnode commands;//在当前这个action下所有的comman的队列            struct command *current;//当前要执行的命令        };

  service也是系统的一个SECTION,这样的SECTION仅有service, import, on xxxx这三种。对这三种
不同的SECTION解析的函数是不同的。记住on xxxx就是一个action。这个数据结构就是对应一个service,在
系统中service是不能直接执行的,它仅仅是一个service的属性信息的集合,为service的启动提供全面的信息,
但是它不是一个命令,所以不能直接执行。前面这些command可能仅仅就是在init进程中执行,但是service不同。
service启动是在一个新的进程中进行的。也就是说init进程会先fork一个进程,然后通过exec家族函数去启动
这个service。所以service是运行在一个新的进程中的。

struct service

  1.3  init.rc文件的解析

  对于init.rc文件的解析主要是通过以下代码实现的[init.c文件中]:

1 init_parse_config_file("/init.rc");

这句代码是解析所有init.rc文件的入口,其他的*.rc文件都是通过init.rc中通过import一级一级地导入的;
所以从这句代码入手是最合适的地方。 init_parse_config_file函数读取init.rc文件,并调用parse_config函数,
这个函数才是真正解析init.rc文件的地方。先看看这个函数的主要部分,如下[init_parce.c文件中]:

 1         static void parse_config(const char *fn, char *s) 2         { 3             //第一部分 4             struct parse_state state; 5             struct listnode import_list; 6             struct listnode *node; 7             char *args[INIT_PARSER_MAXARGS]; 8             int nargs; 9 10             nargs = 0;11             state.filename = fn;12             state.line = 0;13             state.ptr = s;14             state.nexttoken = 0;15             state.parse_line = parse_line_no_op;16 17             list_init(&import_list);18             state.priv = &import_list;19             //第二部分20             for (;;) {21                 switch (next_token(&state)) {22                 case T_EOF:23                     state.parse_line(&state, 0, 0);24                     goto parser_done;25                 case T_NEWLINE:26                     state.line++;27                     if (nargs) {28                         int kw = lookup_keyword(args[0]);29                         if (kw_is(kw, SECTION)) {30                             state.parse_line(&state, 0, 0);31                             parse_new_section(&state, kw, nargs, args);32                         } else {33                             state.parse_line(&state, nargs, args);34                         }35                         nargs = 0;36                     }37                     break;38                 case T_TEXT:39                     if (nargs < INIT_PARSER_MAXARGS) {40                         args[nargs++] = state.text;41                     }42                     break;43                 }44             }45         //第三部分46         parser_done:47             list_for_each(node, &import_list) {48                  struct import *import = node_to_item(node, struct import, list);49                  int ret;50 51                  INFO("importing ‘%s‘", import->filename);52                  ret = init_parse_config_file(import->filename);53                  if (ret)54                      ERROR("could not import file ‘%s‘ from ‘%s‘\n",55                            import->filename, fn);56             }57         }

把这个函数分成三部分理解会更方便,首先第一部分只是在解析过程中一些变量的初始化,主要是就是parse_state结构体
的初始化。真正解析rc文件的是在第二部分;在当前rc文件解析完成后,会处理当前文件import的其它rc文件,这些是在
第三部分做的。如果有import其它的rc文件,在第三部分中会调用init_parse_config_file函数,接着解析导入的rc文件;
从整体上看,像是一个递归函数。我们把重点放在第二部分上。

在第二部分中next_token函数相当于一个简单的词法分析器,这个函数对应的状态机如下图:

 技术分享

这个图画的不是很标准,我再大概注释下吧:对于rc文件内容逐个字母输入这个状态机,如果是字符的话就继续输入下一个字符,直到输入的是空格或\r或者\t,

那么就进入TEXT状态,并把这个token返回给parse_config函数去处理;同样,要是遇到换行,或者文件结束,都要返回给parse_config去处理。

从第二部分可以看出,rc文件的解析是以行为单位进行的,在每一行中检查到一个TEXT,就会把这个单词放入args数组中;
这样,当一行结束时,这个args数组和nargs变量就初始化完成;然后开始换行;在换行时,需要在如下代码(第二部分代码中)中进行:

 1                 case T_NEWLINE: 2                     state.line++; 3                     //nargs非0,说明这行中有命令需要解析 4                     if (nargs) { 5                         //先看看在本行中第一个词是不是关键词, 6                         //关键词列表在keywords.h文件中 7                         int kw = lookup_keyword(args[0]); 8                         //如果第一参数是关键词,那么就会返回关键词的index 9                         //然后判断这个关键词是不是SECTION,只有import,on,service10                         //才是SECTION11                         if (kw_is(kw, SECTION)) {12                             state.parse_line(&state, 0, 0);13                             //在parse_new_section中,会解析这个section,14                             //在解析过程中,最重要的是给parse_line赋值;15                             //因为parse_line是个函数指针16                             parse_new_section(&state, kw, nargs, args);17                         } else {18                             //在一个SECTION中,parse_line会保持不变的,19                             //直到遇到下个SECTION之前,这个parse_line函数20                             //会保持不变的21                             state.parse_line(&state, nargs, args);22                         }23                         nargs = 0;24                     }25                     break;

给parse_line赋值是在parse_new_section中进行的,能够赋值给parse_line的函数有parse_line_service,parse_line_action,
对这个两个函数的理解,有助于对init.rc文件解析的理解。对于这两个函数没必要逐行分析,这里不作介绍;
在lookup_keyword函数中,有个地方说明下;可能是我的基础知识不是很牢固,在看这个函数的时候,有些地方卡了一下;
在init_parse.c文件中有如下宏定义

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

而在keywords.h文件中,还有个宏定义如下:

                #define KEYWORD(symbol, flags, nargs, func, uev_func) K_##symbol,

这两个宏是一样的。 C语言中宏的作用范围只是在单文件中。宏是在编译过程中进行的替换,这个过程发生在链接之前,所以
宏的作用范围只能是在当前文件中。这样的话,在init_parse.c文件中的宏定义没人用到,因此哪个宏也是无意义的。

2 init进程是如何启动其它基本的进程的
  2.1 init进程中命令和服务启动顺序
    在正式开始介绍init进程是如何启动其它服务之前,还有一些内容要介绍,就是系统如何确定开机执行顺序的。在init.c文件中有如下代码:

 1                 action_for_each_trigger("early-init", action_add_queue_tail); 2                 queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); 3                 queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); 4                 queue_builtin_action(keychord_init_action, "keychord_init"); 5                 queue_builtin_action(console_init_action, "console_init"); 6                 action_for_each_trigger("init", action_add_queue_tail); 7                 if (!is_special) { 8                     action_for_each_trigger("early-fs", action_add_queue_tail); 9                     action_for_each_trigger("fs", action_add_queue_tail);10                     action_for_each_trigger("post-fs", action_add_queue_tail);11                     action_for_each_trigger("post-fs-data", action_add_queue_tail);12                 }13                     /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random14                      * wasn‘t ready immediately after wait_for_coldboot_done15                      */16                 queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");17                 queue_builtin_action(property_service_init_action, "property_service_init");18                 queue_builtin_action(signal_init_action, "signal_init");19                 queue_builtin_action(check_startup_action, "check_startup");20                 if (is_special) {21                     action_for_each_trigger(bootmode, action_add_queue_tail);22                 } else {23                     action_for_each_trigger("early-boot", action_add_queue_tail);24                     action_for_each_trigger("boot", action_add_queue_tail);25                 }26                 queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

  这段代码中主要的函数就两个,分别是action_for_each_trigger,queue_builtin_action,这两个函数操作同意队列,
这个队列就是action_queue。init进程启动顺序就是action在action_queue这个队列中的顺序。action_for_each_trigger
函数就是把*.rc文件中, 对应trigger的action放入action_queue队列。 而queue_builtin_action函数是临时需要在action_queue中
新增加一个action,只不过这个action仅仅有一个command,这个command就是queue_builtin_action函数第一个参数对应的函数。

  因此从这段代码中,我们可以看出,init执行顺序是:
    early-init,    wait_for_coldboot_done,   mix_hwrng_into_linux_rng,  keychord_init,console_init,   init,
    early-fs,   fs,   post-fs,   post-fs-data,   mix_hwrng_into_linux_rng,  property_service_init,signal_init,
    check_startup,  early-boot,   boot,   queue_property_triggers.
  上面这些代码仅仅是把action_queue队列配置完成,安排好每个action的顺序。 但是现在并没有开始按照这个队列去执行各个
命令或者启动各个服务。

  2.2 在init进程中执行命令和服务

  这里就开始介绍init进程是如何执行命令和启动服务的。这个过程是在如下代码开始执行的:

 1             for(;;) { 2                 int nr, i, timeout = -1; 3                 //第一部分 4                 //execute_one_command是开始执行命令的地方 5                 execute_one_command(); 6                 //如果有些进程需要重启的话在这里进行 7                 restart_processes(); 8                 //第二部分 9                 //接下来是四个socket,使用Linux的poll机制去监听10                 //这四个文件的状态,与其它进程通信;比如:11                 //当我们通过命令调用 setprop时候,就会和get_property_set_fd12                 //指向的socket通信13                 if (!property_set_fd_init && get_property_set_fd() > 0) {14                     ufds[fd_count].fd = get_property_set_fd();15                     ufds[fd_count].events = POLLIN;16                     ufds[fd_count].revents = 0;17                     fd_count++;18                     property_set_fd_init = 1;19                 }20                 if (!signal_fd_init && get_signal_fd() > 0) {21                     ufds[fd_count].fd = get_signal_fd();22                     ufds[fd_count].events = POLLIN;23                     ufds[fd_count].revents = 0;24                     fd_count++;25                     signal_fd_init = 1;26                 }27                 if (!keychord_fd_init && get_keychord_fd() > 0) {28                     ufds[fd_count].fd = get_keychord_fd();29                     ufds[fd_count].events = POLLIN;30                     ufds[fd_count].revents = 0;31                     fd_count++;32                     keychord_fd_init = 1;33                 }34 35                 if (process_needs_restart) {36                     timeout = (process_needs_restart - gettime()) * 1000;37                     if (timeout < 0)38                         timeout = 0;39                 }40 41                 if (!action_queue_empty() || cur_action)42                     timeout = 0;43                 //这里就是poll机制的利用,44                 //接收到socket通信后,对于这些事件的分配45                 nr = poll(ufds, fd_count, timeout);46                 if (nr <= 0)47                     continue;48 49                 for (i = 0; i < fd_count; i++) {50                     if (ufds[i].revents == POLLIN) {51                         if (ufds[i].fd == get_property_set_fd())52                             handle_property_set_fd();53                         else if (ufds[i].fd == get_keychord_fd())54                             handle_keychord();55                         else if (ufds[i].fd == get_signal_fd())56                             handle_signal();57                     }58                 }59             }

便于理解,把上述代码分为两个部分。这两个部分都是重点。
不过第一部分更切合我们这一小节的主题:命令的执行和服务的启动。
execute_one_command函数是命令执行和服务启动主要函数,这个函数的代码如下:

 1             void execute_one_command(void) 2             { 3                 int ret; 4  5                 if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) { 6                     //从action_queue中取出第一个action 7                     cur_action = action_remove_queue_head(); 8                     cur_command = NULL; 9                     if (!cur_action)10                         return;11                     INFO("processing action %p (%s)\n", cur_action, cur_action->name);12                     //从当前action中取出第一个command13                     cur_command = get_first_command(cur_action);14                 } else {15                     cur_command = get_next_command(cur_action, cur_command);16                 }17 18                 if (!cur_command)19                     return;20                 //执行这个command21                 ret = cur_command->func(cur_command->nargs, cur_command->args);22                 INFO("command ‘%s‘ r=%d\n", cur_command->args[0], ret);23             }

上面这个过程就是命令执行的过程.这些命令对应的函数,可以从kerwords.h中看到。如果前面的rc文件解析中,仔细分析
了整个流程的话,就很容易回忆起前面的这些内容。对于这个func也不会陌生,这些函数的实现都是在builtins.c文件中实现的。
在rc文件中出现的命令,绝大部分都是很常见的,不多做介绍。不过,下面还是会以其中一个命令说明下整个过程,这个命令就是
class_start--这个命令是专门用来启动service的,在其他Linux系统中也是没有的。
对于这个命令,首先到keywords.h中找到其对应的函数,如下:

1             KEYWORD(class_start, COMMAND, 1, do_class_start, 0)

所以class_start命令对应的函数实际上就是do_class_start。前文已经说过,这些函数都是在builtins.c文件中实现的,那么这个
函数实现如下:

 1             int do_class_start(int nargs, char **args){ 2                         /* Starting a class does not start services 3                          * which are explicitly disabled.  They must 4                          * be started individually. 5                          * */ 6                     service_for_each_class(args[1], service_start_if_not_disabled); 7                         return 0; 8             } 9 service_for_each_class函数的实现实在init_parser.c文件中,如下10             void service_for_each_class(const char *classname, void (*func)(struct service *svc)){11                 struct listnode *node;12                 struct service *svc;13                 list_for_each(node, &service_list) {14                     svc = node_to_item(node, struct service, slist);15                     if (!strcmp(svc->classname, classname)) {16                         func(svc);17                     }18                 }19             }

到这里,基本已经可以看出这个命令和service之间的关系来。通过这两个函数,实际上就是通过service_start_if_not_disabled
函数启动所有className与给出的classname相同的service。 这些classname指的就是在定义每一个service的时候,定义在class
之后的部分,比如下面这个service:

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

servicemanager服务的classname就是 core. 那么调用class_start命令的地方在哪里呢?
调用class_start命令的地方在init.rc中,当执行boot action的时候,顺序执行就能执行到这个命令,首先启动的core一级别的服务,
然后启动才是main级别的服务

1             on boot2                 ...3                 class_start core4                 class_start main

既然已经找到了命令开始的地方了,那么我们可以继续看看service到底是如何执行的。这就要看service_start_if_not_disabled函数了,
这个函数的代码如下,在builtins.c文件中:

1             static void service_start_if_not_disabled(struct service *svc){2                     if (!(svc->flags & SVC_DISABLED)) {3                                 service_start(svc, NULL);4                     }5             }

这里会检查flags中没有disabled选项的启动。在service_start函数是真正启动一个服务的地方。service_start函数在init.c文件中实现的。
当你在这个函数中看到fork()函数时候,你就明白这个服务启动了。而逐个启动每个服务,这个循环过程实在service_for_each_class中的
list_for_each中的,可以回头再看看。

到这里就是介绍完成来通过rc文件启动一个服务的过程。能读到这里,说明你真的很有耐心啊,给自己一个表扬吧。不过这时候,你也许会有
一个疑问,如果一个service中flags中有disabled项时,这样的服务是怎样启动的呢?

下面就以下面这个含有disabled项服务的启动为例,介绍下这类服务的启动过程,这个服务如下:

1             service bootanim /system/bin/bootanimation2                 class main3                 user graphics4                 group graphics5                 disabled6                 oneshot

这个服务是开机动画,如果使用的是模拟器的话,就是开机闪烁android的那个动画。这个服务中含有disabled, 通过上面的解析,我们知道这个服务
肯定不是通过class_start命令启动的。但是在开机过程中,我们的确看到来开机动画,那么这个服务是在何时由谁启动的呢?

启动开机动画是在SurfaceFlinger初始化完成时,由SurfaceFlinger间接启动这个服务的。在SurfceFlinger初始化完成时,调用来函数startBootAnim(),
这个函数通过设置一个系统属性,然后开机动画就开始来。设置这个属性的代码如下:

1             void SurfaceFlinger::startBootAnim() {2                 // 首先是先退出开机动画。如果开机动画在进行中,那么就退出这个开机动画;3                 // 如果开机动画没有运行,那么这个属性设置上也是没有影响的4                 property_set("service.bootanim.exit", "0");5                 //然后,开始播放动画。init进程会检查这个属性,然后开始动画播放6                 property_set("ctl.start", "bootanim");7             }

property_set的函数,在实现的时候,实际上是通过写socket和init进程通信。在前面也说到过,init进程在启动后,会监视四个文件的状态,这四个
文件都是socket文件,其中一直就是属性socket文件的描述符get_property_set_fd(). 通过Linux的poll机制,当有有系统属性设置进来的时候,就会
有触发调用下面的函数:
handle_property_set_fd()
这函数是在init.c文件中被调用,它的实现实在property_service.c文件中。这个函数从socket中读取出传递过来的信息。传递过来的信息都是按照键值对
封装好的。如果键值对中的name中前4个字母是"crtl.",那么就会调用handle_control_message()函数。关于函数handle_property_set_fd()的代码在property_service.c
文件中,这里就不再拿出来单独看了.
在调用到handle_control_message函数,这个函数如下:

 1             void handle_control_message(const char *msg, const char *arg) 2             { 3                 if (!strcmp(msg,"start")) { 4                     msg_start(arg); 5                 } else if (!strcmp(msg,"stop")) { 6                     msg_stop(arg); 7                 } else if (!strcmp(msg,"restart")) { 8                     msg_restart(arg); 9                 } else {10                     ERROR("unknown control msg ‘%s‘\n", msg);11                 }12             }13             static void msg_start(const char *name)14             {15                 ...16                 svc = service_find_by_name(name);17                 ...18                 service_start(svc, args);19                 ...20             }

如果命令中的start的话,这里把msg_start函数彻底简写了,仅仅保留这两个最核心的代码。根据service的名字找到这个service,
然后启动service。当你看到service_start()函数的时候,想必你已经明白来整个过程来。service_start()函数在前面有过简略
地介绍,这里也不多说来。

就是类似与这样,init进程在设备启动后,还在忙碌地参与这系统的运行。

Android Init进程命令的执行和服务的启动