zoukankan      html  css  js  c++  java
  • 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_state
    2         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'
    ",
    55                            import->filename, fn);
    56             }
    57         }

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

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

     

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

    那么就进入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,service
    10                         //才是SECTION
    11                         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/random
    14                      * wasn't ready immediately after wait_for_coldboot_done
    15                      */
    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_fd
    12                 //指向的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)
    ", cur_action, cur_action->name);
    12                     //从当前action中取出第一个command
    13                     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                 //执行这个command
    21                 ret = cur_command->func(cur_command->nargs, cur_command->args);
    22                 INFO("command '%s' r=%d
    ", 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 surfaceflinger
    10                 onrestart restart drm

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

    1             on boot
    2                 ...
    3                 class_start core
    4                 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/bootanimation
    2                 class main
    3                 user graphics
    4                 group graphics
    5                 disabled
    6                 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'
    ", 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进程在设备启动后,还在忙碌地参与这系统的运行。

  • 相关阅读:
    ASCII,Unicode,UTF
    C#值类型和引用类型2
    C#中使用Foreach
    CSS基础(2)
    CSS基础
    HTML基础
    MySQL高级
    MySQL和Python交互案例练习(2)
    MySQL和Python交互案例练习(1)
    外键SQL语句的编写
  • 原文地址:https://www.cnblogs.com/haiming/p/4182468.html
Copyright © 2011-2022 走看看