zoukankan      html  css  js  c++  java
  • [深入理解Android卷一全文-第三章]深入理解init

    因为《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容。



    第3章  深入理解init

    本章主要内容

    ·  深入分析init。

    本章涉及的源代码文件名称及位置

    以下是本章分析的源代码文件名称及其位置。

    ·  init.c

    system/core/init/init.c

    ·  parser.c

    system/core/init/parser.c

    ·  builtins.c

    system/core/init/builtins.c

    ·  keywords.h

    system/core/init/keywords/h

    ·  init.rc

    system/core/rootdir/init.rc

    ·  properties_service.c

    system/core/init/properties_service.c

    ·  libc_init_dynamic.c

    bionic/libc/bionic/libc_init_common.c

    ·  libc_init_common.c

    bionic/libc/bionic/libc_init_common.c

    ·  properties.c

    system/core/libcutils/properties.c

    3.1  概述

     init是一个进程,确切地说,它是Linux系统中用户空间的第一个进程。因为Android是基于Linux内核的,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。

    作为天字第一号的进程。init被赋予了非常多极其重要的工作职责,本章将关注当中两个比較重要的职责:

    ·  init进程负责创建系统中的几个关键进程。尤其是下一章要介绍的Zygote。它更是Java世界的开创者。那么,init进程是怎样创建Zygote的呢?

    ·  Android系统有非常多属性,于是init就提供了一个property service(属性服务)来管理它们。那么这个属性服务是怎么工作的呢?

    如上所述,本章将通过以下双方面内容来分析init:

    ·  init怎样创建zygote。

    ·  init的属性服务是怎样工作的。

    3.2  init分析

    init进程的入口函数是main,它的代码例如以下所看到的:

    [-->init.c]

    int main(int argc, char **argv)

    {

        intdevice_fd = -1;

        intproperty_set_fd = -1;

        intsignal_recv_fd = -1;

        intkeychord_fd = -1;

        int fd_count;

        ints[2];

        intfd;

        structsigaction act;

        chartmp[PROP_VALUE_MAX];

        structpollfd ufds[4];

        char*tmpdev;

        char*debuggable;

       

       //设置子进程退出的信号处理函数,该函数为sigchld_handler。

       act.sa_handler = sigchld_handler;

        act.sa_flags= SA_NOCLDSTOP;

       act.sa_mask = 0;

       act.sa_restorer = NULL;

       sigaction(SIGCHLD, &act, 0);

      

       ......//创建一些文件夹。并挂载设备。这些是和Linux相关的,不拟做过多讨论。

       mkdir("/dev/socket", 0755);

       mount("devpts", "/dev/pts", "devpts", 0,NULL);

       mount("proc", "/proc", "proc", 0, NULL);

       mount("sysfs", "/sys", "sysfs", 0, NULL);

        //重定向标准输入/输出/错误输出到/dev/_null_。

    open_devnull_stdio();

    /*

    设置init的日志输出设备为/dev/__kmsg__,只是该文件打开后,会立即被unlink了,

    这样,其它进程就无法打开这个文件读取日志信息了。

    */

       log_init();

       

       //上面涉及非常多和Linux系统相关的知识,不熟悉的读者可自行研究,它们不影响我们的分析

       //解析init.rc配置文件

       parse_config_file("/init.rc");

        ......

        //以下这个函数通过读取/proc/cpuinfo得到机器的Hardware名。我的HTCG7手机为bravo。

       get_hardware_name();

    snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware);

    //解析这个和机器相关的配置文件。我的G7手机相应文件为init.bravo.rc。

       parse_config_file(tmp);

    /*

    解析完上述两个配置文件后,会得到一系列的Action(动作),以下两句代码将运行那些处于

    early-init阶段的Action。init将动作运行的时间划分为四个阶段:early-init、init、

    early-boot、boot。

    因为有些动作必须在其它动作完毕后才干运行,所以就有了先后之分。哪些

    动作属于哪个阶段由配置文件决定。

    后面会介绍配置文件的相关知识。

    */

       action_for_each_trigger("early-init", action_add_queue_tail);

       drain_action_queue();

    /*

    创建利用Uevent和Linux内核交互的socket。关于Uevent的知识。第9章中对

    Vold进行分析时会做介绍。

        */

       device_fd = device_init();

        //初始化和属性相关的资源

    property_init();

    //初始化/dev/keychord设备,这和调试有关。本书不讨论它的使用方法。读者能够自行研究,

    //内容比較简单。

       keychord_fd = open_keychord();

        ......

    /*

      INIT_IMAGE_FILE定义为”/initlogo.rle”,以下这个函数将载入这个文件作为系统的开机

     画面,注意,它不是开机动画控制程序bootanimation载入的开机动画文件。

    */

    if(load_565rle_image(INIT_IMAGE_FILE) ) {

       /*

    假设载入initlogo.rle文件失败(可能是没有这个文件)。则会打开/dev/ty0设备。并

    输出”ANDROID”的字样作为开机画面。在模拟器上看到的开机画面就是它。

    */

          ......

          }

       }

        if(qemu[0])

           import_kernel_cmdline(1);

       ......

    //调用property_set函数设置属性项,一个属性项包括属性名和属性值。

       property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown");

        ......//运行位于init阶段的动作

       action_for_each_trigger("init", action_add_queue_tail);

       drain_action_queue();

        //启动属性服务

       property_set_fd = start_property_service();

    /*

    调用socketpair函数创建两个已经connect好的socket。

    socketpair是Linux的系统调用,

    不熟悉的读者能够利用man socketpair查询相关信息。

    后面就会知道它们的用处了。

    */

        if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {

           signal_fd = s[0];

           signal_recv_fd = s[1];

            ......

        }

        ......

        //运行配置文件里early-boot和boot阶段的动作。

       action_for_each_trigger("early-boot", action_add_queue_tail);

       action_for_each_trigger("boot", action_add_queue_tail);

       drain_action_queue();

    ......    

       

    //init关注来自四个方面的事情。

        ufds[0].fd= device_fd;//device_fd用于监听来自内核的Uevent事件

       ufds[0].events = POLLIN;

       ufds[1].fd = property_set_fd;//property_set_fd用于监听来自属性server的事件

    ufds[1].events= POLLIN;

    //signal_recv_fd由socketpair创建。它的事件来自另外一个socket。

       ufds[2].fd = signal_recv_fd;

       ufds[2].events = POLLIN;

       fd_count = 3;

    if(keychord_fd > 0) {

       //假设keychord设备初始化成功,则init也会关注来自这个设备的事件。

           ufds[3].fd = keychord_fd;

           ufds[3].events = POLLIN;

           fd_count++;

    }

    ......

    #if BOOTCHART

        ......//与Boot char相关。不做讨论了。

    /*

    Boot chart是一个小工具,它能对系统的性能进行分析,并生成系统启动过程的图表,

    以提供一些有价值的信息。而这些信息最大的用处就是帮助提升系统的启动速度。

        */

    #endif

      for(;;) {

            //从此init将进入一个无限循环。

           int nr, i, timeout = -1;

           for (i = 0; i < fd_count; i++)

               ufds[i].revents = 0;

           

            //在循环中运行动作

           drain_action_queue();

           restart_processes(); //重新启动那些已经死去的进程

    ......

    #if BOOTCHART

            ...... // Boot Chart相关

    #endif

            //调用poll等待一些事情的发生

            nr= poll(ufds, fd_count, timeout);

           ......

           //ufds[2]保存的是signal_recv_fd,用于接收来自socket的消息。

            if(ufds[2].revents == POLLIN) {

               //有一个子进程去世,init要处理这个事情

                read(signal_recv_fd, tmp, sizeof(tmp));

               while (!wait_for_one_process(0))

                   ;

               continue;

            }

            if(ufds[0].revents == POLLIN)

               handle_device_fd(device_fd);//处理Uevent事件

            if(ufds[1].revents == POLLIN)

               handle_property_set_fd(property_set_fd);//处理属性服务的事件。

            if(ufds[3].revents == POLLIN)

               handle_keychord(keychord_fd);//处理keychord事件。

        }

        return0;

    }

    从上面的代码中可知,init的工作任务还是非常重的。

    上面的代码虽已省略了不少行,可结果还是非常长,只是从本章要分析的两个知识点来看。可将init的工作流程精简为以下四点:

    ·  解析两个配置文件,当中,将分析对init.rc文件的解析。

    ·  运行各个阶段的动作,创建Zygote的工作就是在当中的某个阶段完毕的。

    ·  调用property_init初始化属性相关的资源,而且通过property_start_service启动属性服务。

    ·  init进入一个无限循环,而且等待一些事情的发生。重点关注init怎样处理来自socket和来自属性server相关的事情。

    精简工作流程。是以后分析代码时经常使用的方法。读者在分析代码的过程中。也可使用这样的方法。

    3.2.1  解析配置文件

    依据上面的代码可知,在init中会解析两个配置文件,当中一个是系统配置文件init.rc。另外一个是和硬件平台相关的配置文件。以HTC G7手机为例,这个配置文件名称为init.bravo.rc,当中bravo是硬件平台的名称。对这两个配置文件进行解析,调用的是同一个parse_config_file函数。以下就来看这个函数,在分析过程中以init.rc为主。

    [-->parser.c]

    int parse_config_file(const char *fn)

    {

    char *data;

    data = read_file(fn, 0);//读取配置文件的内容,这个文件是init.rc。

    if (!data) return -1;

    parse_config(fn,data); //调用parse_config做真正的解析

    return 0;

    }

    读取完文件的内容后,将调用parse_config进行解析。这个函数的代码例如以下所看到的:

    [-->parser.c]

    static void parse_config(const char *fn, char*s)

    {

    struct parse_state state;

    char *args[SVC_MAXARGS];

    int nargs;

    nargs = 0;

    state.filename = fn;

    state.line = 1;

    state.ptr = s;

    state.nexttoken = 0;

    state.parse_line = parse_line_no_op; //设置解析函数。不同的内容用不同的解析函数

    for (;;) {

        switch(next_token(&state)) {

          case T_EOF:

               state.parse_line(&state, 0, 0);

               return;

          caseT_NEWLINE:

               if (nargs) {

                  //得到关键字的类型

                   int kw = lookup_keyword(args[0]);

                   if (kw_is(kw, SECTION)) { //推断关键字类型是不是SECTION。

                        state.parse_line(&state,0, 0);

                       parse_new_section(&state,kw, nargs, args);//解析这个SECTION。

                   } else {

                       state.parse_line(&state, nargs, args);

                   }

                   nargs = 0;

               }

               break;

           case T_TEXT:

              ......

               break;

            }

        }

    }

    上面就是parse_config函数,代码虽短,实际却比較复杂。从总体来说。parse_config首先会找到配置文件的一个section,然后针对不同的 section使用不同的解析函数来解析。那么,什么是section呢?这和init.rc文件的组织结构有关。

    先不必急着去看init.rc。还是先到代码中去寻找答案。

    1. 关键字定义

    keywords.h这个文件定义了init中使用的关键字,它的使用方法非常有意思,先来看这个文件。代码例如以下所看到的:

    [-->keywords.h]

    #ifndef KEYWORD //假设未定义KEYWORD宏,则走以下的分支

    ......//声明一些函数,这些函数就是前面所说Action的运行函数。

    int do_class_start(int nargs, char **args);

    int do_class_stop(int nargs, char **args);

    ......

    int do_restart(int nargs, char **args);

    ......

    #define __MAKE_KEYWORD_ENUM__  //定义一个宏

    /*

    定义KEYWORD宏,尽管有四个參数。只是这里仅仅用第一个,当中K_##symbol中的##表示连接

    的意思。即最后得到的值为K_symbol。symbol事实上就是init.rc中的关键字

    */

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

    enum { //定义一个枚举。这个枚举定义了各个关键字的枚举值。

       K_UNKNOWN,

    #endif

    ......

    //依据上面KEYWORD的定义,这里将得到一个枚举值K_class,

       KEYWORD(class,       OPTION,  0, 0)

       KEYWORD(class_start, COMMAND, 1, do_class_start)//K_class_start,

    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)//K_class_stop,

    KEYWORD(on,          SECTION, 0, 0)//K_on。

       KEYWORD(oneshot,     OPTION,  0, 0)

       KEYWORD(onrestart,   OPTION,  0, 0)

       KEYWORD(restart,     COMMAND, 1,do_restart)

       KEYWORD(service,     SECTION, 0,0)

        ......

       KEYWORD(socket,      OPTION,  0, 0)

       KEYWORD(start,       COMMAND, 1,do_start)

       KEYWORD(stop,        COMMAND, 1,do_stop)

        ......

    #ifdef __MAKE_KEYWORD_ENUM__

       KEYWORD_COUNT,

    };

    #undef __MAKE_KEYWORD_ENUM__

    #undef KEYWORD //取消KEYWORD宏定义

    #endif

    keywords.h好像没什么奇特,只是是个简单的头文件。

    为什么说它的使用方法非常有意思呢?来看代码中是怎样使用它的,例如以下所看到的:

    [-->parser.c]

    ......//parser.c中将包括keywords.h头文件,而且还不仅仅一次。!

    //第一次包括keywords.h。依据keywords.h的代码。我们首先会得到一个枚举定义

    #include "keywords.h"

    /*

    又一次定义KEYWORD宏。这回四个參数全用上了。看起来好像是一个结构体。

    当中#symbol表示

    一个字符串,其值为“symbol”。

    */

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

        [K_##symbol ] = { #symbol, func, nargs + 1, flags, },

    //定义一个结构体keyword_info数组,它用来描写叙述关键字的一些属性。请注意里面的凝视内容。

    struct {

        constchar *name;  //关键字的名。

        int(*func)(int nargs, char **args);//相应关键字的处理函数。

    unsignedchar nargs;//參数个数,每个关键字的參数个数是固定的。

    //关键字的属性,有三种属性,COMMAND、OPTION和SECTION。当中COMMAND有相应的处理函数

       unsigned char flags;

    } keyword_info[KEYWORD_COUNT] = {

    [ K_UNKNOWN ] = { "unknown", 0, 0, 0},

    /*

    第二次包括keywords.h,因为已经又一次定了KEYWORD宏,所以曾经那些作为枚举值的关键字

    如今变成keyword_info数组的索引了。

    */

    #include "keywords.h"   

    };

    #undef KEYWORD

    //一些辅助宏,帮助我们高速操作keyword_info中的内容。

    #define kw_is(kw, type) (keyword_info[kw].flags& (type))

    #define kw_name(kw) (keyword_info[kw].name)

    #define kw_func(kw) (keyword_info[kw].func)

    #define kw_nargs(kw) (keyword_info[kw].nargs)

    如今领略了keywords.h的奇妙之处了吧?原来它干了两件事情:

    ·  第一次包括keyworks.h时。它声明了一些诸如do_classstart这样的函数,另外还定义了一个枚举,枚举值为K_class。K_mkdir等关键字。

    ·  第二次包括keywords.h后。得到了一个keyword_info结构体数组,这个keyword_info结构体数组曾经面定义的枚举值为索引,存储相应的关键字信息,这些信息包括关键字名、处理函数、处理函数的參数个数,以及属性。

    眼下,关键字信息中最重要的就是symbol和flags了。什么样的关键字被觉得是section呢?依据keywords.h的定义,symbol为以下两个的关键字表示section:

    KEYWORD(on,          SECTION, 0, 0)

    KEYWORD(service,     SECTION, 0, 0)

    有了上面的知识。再来看配置文件init.rc的内容。

    2. init.rc的解析

    init.rc的内容例如以下所看到的:(我们截取了部分内容,注意。当中的凝视符号是#。)

    [-->init.rc]

    on init  #依据上面的分析。on关键字标示一个section,相应的名字是”init”

     ......  #以下全部的内容都属于这个section,直到下一个section開始时。

     exportPATH /sbin:/system/sbin:/system/bin:/system/xbin

     exportLD_LIBRARY_PATH /system/lib

     exportANDROID_BOOTLOGO 1 #依据keywords.h的定义。export表示一个COMMAND

    export ANDROID_ROOT /system

     exportANDROID_ASSETS /system/app

    ...... #省略部分内容

    on boot  #这是一个新的section。名为”boot”

       ifup lo#这是一个COMMAND

       hostname localhost

       domainname localdomain

        ......

       #class_start也是一个COMMAND,相应函数为do_class_start。非常重要,切记。

        class_startdefault 

        ......

    #以下这个section的意思是:待属性persist.service.adb.enable的值变为1后。

    #须要运行相应的COMMAND,这个COMMAND是start adbd

         onproperty:persist.service.adb.enable=1

             start adbd //start是一个COMMAND

         on property:persist.service.adb.enable=0

             stopadbd

        ......

    #service也是section的标示,相应section的名为“zygote“

    service zygote /system/bin/app_process -Xzygote/system/bin –zygote        

     --start-system-server

        socketzygote stream 666  #socket关键字表示OPTION

       onrestart write /sys/android_power/request_state wake #onrestart也是OPTION

       onrestart write /sys/power/state on

       onrestart restart media

    #一个section,名为”media”

    service media /system/bin/mediaserver

        usermedia

        groupsystem audio camera graphics inet net_bt net_bt_admin net_raw

    iopriort 4

    从上面对init.rc的分析中可知:

    ·  一个section的内容从这个标示section的关键字開始,到下一个标示section的地方结束。

    ·  init.rc中出现了名为boot和init的section,这里的boot和init,就是前面介绍的动作运行四个阶段中的boot和init。也就是说,在boot阶段运行的动作都是由boot这个section定义的。

    另外还可发现。zygote被放在了一个servicesection中。

    以下以zygote这个section为例,介绍service是怎样解析的。

    3.2.2  解析service

    zygote相应的service section内容是:

    [-->init.rc::zygote]

    service zygote /system/bin/app_process -Xzygote/system/bin –zygote --start-system-server

    socketzygote stream 666  #socket是OPTION

    #以下的onrestart是OPTION,而write和restart是COMMAND

        onrestartwrite /sys/android_power/request_state wake

       onrestart write /sys/power/state on

    onrestartrestart media

    解析section的入口函数是parse_new_section,它的代码例如以下所看到的:

    [-->parser.c]

    void parse_new_section(struct parse_state*state, int kw,

                           int nargs, char **args)

    {

       switch(kw) {

        caseK_service:  //解析service,用parse_service和parse_line_service

           state->context = parse_service(state, nargs, args);

            if(state->context) {

               state->parse_line = parse_line_service;

                return;

            }

           break;

        caseK_on: //解析on section

            ......//读者能够自己研究

           break;

        }

       state->parse_line = parse_line_no_op;

    }

    当中,service解析时,用到了parse_service和parse_line_service两个函数,在分别介绍它们之前,先看init是怎样组织这个service的。

    1. service结构体

    init中使用了一个叫service的结构体来保存和service section相关的信息,最好还是来看这个结构体。代码例如以下所看到的:

    [-->init.h::service结构体定义]

    struct service {

     //listnode是一个特殊的结构体,在内核代码中用得非常多,主要用来将结构体链接成一个

      //双向链表。init中有一个全局的service_list,专门用来保存解析配置文件后得到的service。

       struct listnode slist; 

        constchar *name; //service的名字,相应我们这个样例就是”zygote”。

        constchar *classname; //service所属class的名字,默认是”defult”

       unsigned flags;//service的属性

        pid_tpid;    //进程号

        time_ttime_started;   //上一次启动的时间

        time_ttime_crashed;  //上一次死亡的时间

        intnr_crashed;        //死亡次数

         uid_tuid;     //uid,gid相关

        gid_tgid;

        gid_tsupp_gids[NR_SVC_SUPP_GIDS];

        size_tnr_supp_gids;

       /*

    有些service须要使用socket,以下这个socketinfo用来描写叙述socket的相关信息。

    我们的zygote也使用了socket,配置文件里的内容是socket zygote stream 666。

    它表示将创建一个AF_STREAM类型的socket(事实上就是TCP socket)。该socket的名为“zygote”。

    读写权限是666。

       */

    structsocketinfo *sockets; 

    //service一般运行在单独的一个进程中。envvars用来描写叙述创建这个进程时所需的环境变量信息。

        structsvcenvinfo *envvars; 

       /*

      尽管关键字onrestart标示一个OPTION,但是这个OPTION后面一般跟着COMMAND,

     以下这个action结构体可用来存储command信息,立即就会分析到它。

    */

        structaction onrestart;

       

        //和keychord相关的内容

        int*keycodes;

        intnkeycodes;

        intkeychord_id;

        //io优先级设置

        intioprio_class;

        intioprio_pri;

        //參数个数

        intnargs;

        //用于存储參数

        char*args[1];

    }; 

    我们如今已了解的service的结构体,相对来说还算是清晰易懂的。

    而zygote中的那三个onrestart该怎么表示呢?请看service中使用的这个action结构体:

    [-->init.h::action结构体定义]

    struct action {

    /*

    一个action结构体可存放在三个双向链表中,当中alist用于存储全部action,

    qlist用于链接那些等待运行的action,tlist用于链接那些待某些条件满足后

    就须要运行的action。

    */

        structlistnode alist;

       structlistnode qlist;

        structlistnode tlist;

       unsigned hash;

        constchar *name;

       

       //这个OPTION相应的COMMAND链表。以zygote为例,它有三个onrestart option,所以

      //它相应会创建三个command结构体。

        structlistnode commands;

        structcommand *current;

    };

    了解了上面的知识后。你能否猜到parse_service和parse_line_service的作用了呢?立即就来看它们。

    2. parse_service

    parse_service的代码例如以下所看到的:

    [-->parser.c]

    static void *parse_service(struct parse_state*state, int nargs, char **args)

    {

        structservice *svc; //声明一个service结构体

        ......

        //init维护了一个全局的service链表,先推断是否已经有同名的service了。

        svc =service_find_by_name(args[1]);

        if(svc) {

           ......  //假设有同名的service。则不能继续后面的操作。

           return 0;

        }

       

    nargs-= 2;

        svc =calloc(1, sizeof(*svc) + sizeof(char*) * nargs);

        ......

       svc->name = args[1];

    svc->classname= "default";//设置classname为”default”。这个非常关键。

       memcpy(svc->args, args + 2, sizeof(char*) * nargs);

       svc->args[nargs] = 0;

       svc->nargs = nargs;

    svc->onrestart.name= "onrestart";

       list_init(&svc->onrestart.commands);

        //把zygote这个service加到全局链表service_list中。

       list_add_tail(&service_list, &svc->slist);

        returnsvc;

    }

    parse_service函数仅仅是搭建了一个service的架子,详细的内容尚需由后面的解析函数来填充。来看service的另外一个解析函数parse_line_service。

    3. parse_line_service

    parse_line_service的代码例如以下所看到的:

    [-->parser.c]

    static void parse_line_service(structparse_state *state, int nargs,

    char **args)

    {

        structservice *svc = state->context;

        structcommand *cmd;

        int i,kw, kw_nargs;

        ......

       svc->ioprio_class = IoSchedClass_NONE;

        //事实上还是依据关键字来做各种处理。

        kw =lookup_keyword(args[0]);

        switch(kw) {

        caseK_capability:

           break;

        caseK_class:

            if(nargs != 2) {

               ......

            }else {

               svc->classname = args[1];

            }

           break;

        ......

    caseK_oneshot:

       /*

    这是service的属性。它一共同拥有五个属性。分别为:

    SVC_DISABLED:不随class自己主动启动。以下将会看到class的作用。

    SVC_ONESHOT:退出后不须要重新启动,也就是这个service仅仅启动一次就能够了。

    SVC_RUNNING:正在运行,这是service的状态。

    SVC_RESTARTING:等待重新启动,这也是service的状态。

    SVC_CONSOLE:该service须要使用控制台 。

    SVC_CRITICAL:假设在规定时间内该service不断重新启动,则系统会重新启动并进入恢复模式。

    zygote没有使用不论什么属性,这表明它:会随着class的处理自己主动启动;

    退出后会由init重新启动;不使用控制台;即使不断重新启动也不会导致系统进入恢复模式。

           */

           svc->flags |= SVC_ONESHOT;

           break;

        caseK_onrestart: //依据onrestart的内容,填充action结构体的内容

           nargs--;

           args++;

            kw= lookup_keyword(args[0]);

            ......

            //创建command结构体

           cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);

           cmd->func = kw_func(kw);

           cmd->nargs = nargs;

           memcpy(cmd->args, args, sizeof(char*) * nargs);

            //把新建的command加入到双向链表中。

           list_add_tail(&svc->onrestart.commands, &cmd->clist);

           break;

        ......

        caseK_socket: { //创建socket相关信息

           struct socketinfo *si;

            ......

            si= calloc(1, sizeof(*si));

            if(!si) {

               parse_error(state, "out of memory ");

               break;

            }

           si->name = args[1]; //socket的名字

           si->type = args[2]; //socket的类型

           si->perm = strtoul(args[3], 0, 8); //socket的读写权限

            if(nargs > 4)

               si->uid = decode_uid(args[4]);

            if(nargs > 5)

               si->gid = decode_uid(args[5]);

           si->next = svc->sockets;

           svc->sockets = si;

           break;

        }

        ......

       default:

           parse_error(state, "invalid option '%s' ", args[0]);

        }

    }

    parse_line_service将依据配置文件的内容填充service结构体。那么。zygote解析完后会得到什么呢?图3-1表示了zygote解析后的结果:


    图3-1  zygote解析结果示意图

    从上图中可知:

    ·  service_list链表将解析后的service全部链接到了一起,而且是一个双向链表,前向节点用prev表示,后向节点用next表示。

    ·  socketinfo也是一个双向链表,因为zygote仅仅有一个socket,所以画了一个虚框socket做为链表的示范。

    ·  onrestart通过commands指向一个commands链表,zygote有三个commands。

    zygote这个service解析完了,如今就是“万事俱备,仅仅欠东风”了。

    接下来要了解的是。init是怎样控制service的。

    3.2.3  init控制service

    先看service是怎样启动的。

    1.启动zygote

    init.rc中有这样一句话:

    #class_start是一个COMMAND,相应的函数为do_class_start。非常重要。切记。

     class_startdefault

    class_start标示一个COMMAND。相应的处理函数为do_class_start,它位于boot section的范围内。为什么说它非常重要呢?

    还记得init进程中的四个运行阶段吗?当init进程运行到以下几句话时。do_class_start就会被运行了。

    //将bootsection节的command加入到运行队列

    action_for_each_trigger("boot",action_add_queue_tail);

    //运行队列里的命令,class但是一个COMMAND,所以它相应的do_class_start会被运行。

    drain_action_queue();

    以下来看do_class_start函数:

    [-->builtins.c]

    int do_class_start(int nargs, char **args)

    {

    /*

    args为do_class_start的參数。init.rc中仅仅有一个參数,就是default。

    以下这个函数将从service_list中寻找classname为”default”的service,然后

    调用service_start_if_not_disabled函数。

    如今读者明确了service结构体中

    classname的作用了吗?

    */

    service_for_each_class(args[1],service_start_if_not_disabled);

    return 0;

    }

    我们已经知道,zygote这个service的classname的值就是“default”,所以会针对这个service调用service_start_if_not_disabled,这个函数的代码是:

    [-->parser.c]

    static void service_start_if_not_disabled(structservice *svc)

    {

    if (!(svc->flags & SVC_DISABLED)) {

         service_start(svc,NULL); //zygote可没有设置SVC_DISABLED

     }

    }

    service_start函数的代码例如以下所看到的:

    [-->init.c]

    void service_start(struct service *svc, constchar *dynamic_args)

    {

        structstat s;

        pid_tpid;

        intneeds_console;

        int n;

       svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING));

       svc->time_started = 0;

       

        if(svc->flags & SVC_RUNNING) {

           return;//假设这个service已在运行。则不用处理

        }

      /*

    service一般运行于另外一个进程中,这个进程也是init的子进程,所以启动service前须要推断

    相应的可运行文件是否存在,zygote相应的可运行文件是/system/bin/app_process

    */

        if(stat(svc->args[0], &s) != 0) {

          svc->flags |= SVC_DISABLED;

           return;

        }

        ......

       pid =fork(); //调用fork创建子进程

    if(pid == 0) {

        //pid为零,我们在子进程中

           struct socketinfo *si;

           struct svcenvinfo *ei;

           char tmp[32];

           int fd, sz;

           

    //得到属性存储空间的信息并加到环境变量中,后面在属性服务一节中会碰到使用它的地方。

           get_property_workspace(&fd, &sz);

           add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);

            //加入环境变量信息

           for (ei = svc->envvars; ei; ei = ei->next)

               add_environment(ei->name, ei->value);

            //依据socketinfo创建socket

           for (si = svc->sockets; si; si = si->next) {

               int s = create_socket(si->name,

                                      !strcmp(si->type,"dgram") ?

                                      SOCK_DGRAM :SOCK_STREAM,

                                      si->perm,si->uid, si->gid);

               if (s >= 0) {

                   //在环境变量中加入socket信息。

                    publish_socket(si->name, s);

               }

            }

           ......//设置uid,gid等

         setpgid(0, getpid());

           if(!dynamic_args) {

            /*

    运行/system/bin/app_process。这样就进入到app_process的main函数中了。

    fork、execve这两个函数都是Linux系统上经常使用的系统调用。

            */

                if (execve(svc->args[0], (char**)svc->args, (char**) ENV) < 0) {

                  ......

               }

            }else {

              ......

        }

       ......//父进程init的处理。设置service的信息,如启动时间、进程号,以及状态等。

       svc->time_started = gettime();

       svc->pid = pid;

       svc->flags |= SVC_RUNNING;

    //每个service都有一个属性,zygote的属性为init.svc.zygote,如今设置它的值为running

       notify_service_state(svc->name, "running");

    }

    原来,zygote是通过fork和execv共同创建的。但service结构中的那个onrestart好像没有派上用场。原因何在?

    2. 重新启动zygote

    依据名字。就可猜到onrestart应该是在zygote重新启动时用的。以下先看在zygote死后。它的父进程init会有什么动作:

    [-->init.c]

    static void sigchld_handler(int s)

    {  //当子进程退出时,init的这个信号处理函数会被调用

       write(signal_fd, &s, 1); //往signal_fd write数据

    }

    signal_fd,就是在init中通过socketpair创建的两个socket中的一个,既然会往这个signal_fd中发送数据。那么另外一个socket就一定能接收到,这样就会导致init从poll函数中返回:

    [-->init.rc::main函数代码片断]

     nr =poll(ufds, fd_count, timeout);

     ......

     if(ufds[2].revents == POLLIN) {

       read(signal_recv_fd, tmp, sizeof(tmp));

           while (!wait_for_one_process(0))//调用wait_for_one_process函数处理

             ;

           continue;

     }

     ......

    //直接看这个wait_for_one_process函数:

    static int wait_for_one_process(int block)

    {

        pid_tpid;

        intstatus;

        structservice *svc;

        structsocketinfo *si;

        time_tnow;

        structlistnode *node;

        structcommand *cmd;

    while( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 &&

    errno == EINTR );

        if(pid <= 0) return -1;

        //找到死掉的那个service。如今应该找到了代表zygote的那个service。

    svc = service_find_by_pid(pid);

       ......

    if(!(svc->flags & SVC_ONESHOT)) {

        //杀掉zygote创建的全部子进程,这就是zygote死后,Java世界崩溃的原因。

           kill(-pid, SIGKILL);

       }

        //清理socket信息,不清晰的读者能够通过命令man 7 AF_UNIX查询一下相关知识。

        for(si = svc->sockets; si; si = si->next) {

           char tmp[128];

           snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s",si->name);

            unlink(tmp);

        }

       svc->pid = 0;

       svc->flags &= (~SVC_RUNNING);

       if(svc->flags & SVC_ONESHOT) {

           svc->flags |= SVC_DISABLED;

        }

       ......

    now= gettime();

    /*

    假设设置了SVC_CRITICAL标示,则4分钟内该服务重新启动次数不能超过4次。否则

    机器会重新启动进入recovery模式。依据init.rc的配置。仅仅有servicemanager进程

    享有此种待遇。

    */

        if(svc->flags & SVC_CRITICAL) {

            if(svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {

               if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {

                  ......

                   sync();

                   __reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,

                            LINUX_REBOOT_CMD_RESTART2, "recovery");

                   return 0;

               }

            }else {

               svc->time_crashed = now;

               svc->nr_crashed = 1;

            }

        }

       svc->flags |= SVC_RESTARTING;

    //设置标示为SVC_RESTARTING,然后运行该service onrestart中的COMMAND。这些内容就

    //非常easy了。读者能够自行学习。

       list_for_each(node, &svc->onrestart.commands) {

           cmd = node_to_item(node, struct command, clist);

           cmd->func(cmd->nargs, cmd->args);

    }

    //设置init.svc.zygote的值为restarting。

       notify_service_state(svc->name, "restarting");

        return0;

    }

    通过上面的代码,可知道onrestart的作用了,但zygote本身又在哪里重新启动的呢?答案就在以下的代码中:

    [-->init.c::main函数代码片断]

    for(;;) {

           int nr, i, timeout = -1;

           for (i = 0; i < fd_count; i++)

               ufds[i].revents = 0;

           drain_action_queue(); //poll函数返回后,会进入下一轮的循环

           restart_processes(); //这里会重新启动全部flag标志为SVC_RESTARTING的service。

           ......

    }

    这样。zygote又回来了!

    3.2.4  属性服务

    我们知道,Windows平台上有一个叫注冊表的东西。注冊表能够存储一些相似key/value的键值对。

    一般而言,系统或某些应用程序会把自己的一些属性存储在注冊表中。即使下次系统重新启动或应用程序重新启动,它还能够依据之前在注冊表中设置的属性。进行相应的初始化工作。Android平台也提供了一个类型机制。可称之为属性服务(property service)。

    应用程序可通过这个属性机制。查询或设置属性。

    读者能够用adb shell登录到真机或模拟器上,然后用getprop命令查看当前系统中有哪些属性。即如我的HTC G7測试结果,如图3-2所看到的:(图中仅仅显示了部分属性)


    图3-2  HTC G7属性示意图

    这个属性服务是怎么实现的呢?以下来看代码。当中与init.c和属性服务有关的代码有以下两行:

    property_init();

    property_set_fd = start_property_service();

    分别来看看它们。

    1. 属性服务初始化

    (1)创建存储空间

    先看property_init函数,代码例如以下所看到的:

    [-->property_service.c]

    void property_init(void)

    {

    init_property_area();//初始化属性存储区域

    //载入default.prop文件

       load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);

    }

    在properyty_init函数中。先调用init_property_area函数,创建一块用于存储属性的存储区域。然后载入default.prop文件里的内容。再看init_property_area是怎样工作的,它的代码例如以下所看到的:

    [-->property_service.c]

    static int init_property_area(void)

    {

       prop_area *pa;

       if(pa_info_array)

           return -1;

    /*

    初始化存储空间。PA_SIZE是这块存储空间的总大小,为32768字节,pa_workspace

    为workspace类型的结构体,以下是它的定义:

    typedef struct {

        void *data;   //存储空间的起始地址

        size_tsize;  //存储空间的大小

        int fd;   //共享内存的文件描写叙述符

    } workspace;

    init_workspace函数调用Android系统提供的ashmem_create_region函数创建一块

    共享内存。

    关于共享内存的知识我们在第7章会接触,这里,仅仅需把它当做一块普通的内存就

    能够了。

        */

       if(init_workspace(&pa_workspace, PA_SIZE))

           return -1;

       fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

      

    //在32768个字节的存储空间中,有PA_INFO_START(1024)个字节用来存储头部信息

       pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);

        pa =pa_workspace.data;

       memset(pa, 0, PA_SIZE);

       pa->magic = PROP_AREA_MAGIC;

       pa->version = PROP_AREA_VERSION;

    //__system_property_area__这个变量由bionic libc库输出,有什么用呢?

           __system_property_area__ = pa;

        return0;

    }

    上面的内容比較简单,只是最后的赋值语句但是大有来头。

    __system_property_area__是bionic libc库中输出的一个变量。为什么这里要给它赋值呢?

    原来。尽管属性区域是由init进程创建,但Android系统希望其它进程也能读取这块内存里的东西。为做到这一点。它便做了以下两项工作:

    ·  把属性区域创建在共享内存上,而共享内存是能够跨进程的。这一点。已经在上面的代码中见到了,init_workspace函数内部将创建这个共享内存。

    ·  怎样让其它进程知道这个共享内存呢?Android利用了gcc的constructor属性。这个属性指明了一个__libc_prenit函数,当bionic libc库被载入时,将自己主动调用这个__libc_prenit,这个函数内部就将完毕共享内存到本地进程的映射工作。

    (2)client进程获取存储空间

    关于上面的内容。来看相关代码:

    [-->libc_init_dynamic.c]

    //constructor属性指示载入器载入该库后,首先调用__libc_prenit函数。

    这一点和Windows上

    //动态库的DllMain函数相似

    void __attribute__((constructor))__libc_prenit(void);

    void __libc_prenit(void)

    {

        ......

         __libc_init_common(elfdata); //调用这个函数

        ......

    }

    __libc_init_common函数为:

    [-->libc_init_common.c]

    void __libc_init_common(uintptr_t *elfdata)

    {

       ......

       __system_properties_init();//初始化client的属性存储区域

    }

    [-->system_properties.c]

    int __system_properties_init(void)

    {

       prop_area *pa;

        int s,fd;

       unsigned sz;

        char*env;

    .....

    //还记得在启动zygote一节中提到的加入环境变量的地方吗?属性存储区域的相关信息

    //就是在那儿加入的,这里须要取出来使用了。

        env =getenv("ANDROID_PROPERTY_WORKSPACE");

        //取出属性存储区域的文件描写叙述符。

    关于共享内存的知识,第7章中将会进行介绍。

        fd =atoi(env);

        env =strchr(env, ',');

        if(!env) {

           return -1;

        }

        sz =atoi(env + 1);

    //映射init创建的那块内存到本地进程空间,这样本地进程就能够使用这块共享内存了。

    //注意,映射的时候指定了PROT_READ属性,所以client进程仅仅能读属性,而不能设置属性。

        pa =mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0);

       

        if(pa== MAP_FAILED) {

           return -1;

        }

       if((pa->magic != PROP_AREA_MAGIC) || (pa->version !=PROP_AREA_VERSION)) {

           munmap(pa, sz);

           return -1;

        }

       __system_property_area__ = pa;

        return0;

    }

    上面代码中非常多地方和共享内存有关,在第7章中会对与共享内存有关问题进行介绍,读者也可先行学习有关共享内存的知识。

    总之。通过这样的方式。client进程能够直接读取属性空间,但没有权限设置属性。client进程又是怎样设置属性呢?

    2. 启动属性server

    (1)启动属性server

    init进程会启动一个属性server,而client仅仅能通过和属性server交互才干设置属性。先来看属性server的内容,它由start_property_service函数启动,代码例如以下所看到的:

    [-->Property_servie.c]

    int start_property_service(void)

    {

        intfd;

      

       /*

           载入属性文件,事实上就是解析这些文件里的属性。然后把它设置到属性空间中去。Android系统

          一共提供了四个存储属性的文件。它们各自是:

         #definePROP_PATH_RAMDISK_DEFAULT "/default.prop"

    #define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"

    #define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"

    #define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"

    */

      

       load_properties_from_file(PROP_PATH_SYSTEM_BUILD);

       load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);

    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);

    //有一些属性是须要保存到永久介质上的。这些属性文件则由以下这个函数载入,这些文件

    //存储在/data/property文件夹下,而且这些文件的文件名称必须以persist.开头。

    这个函数

    //非常easy,读者可自行研究。

        load_persistent_properties();

       //创建一个socket。用于IPC通信。

        fd =create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);

        if(fd< 0) return -1;

       fcntl(fd, F_SETFD, FD_CLOEXEC);

       fcntl(fd, F_SETFL, O_NONBLOCK);

       listen(fd, 8);

        returnfd;

    }

    属性服务创建了一个用来接收请求的socket,可这个请求在哪里被处理呢?事实上,在init中的for循环那里已经进行相关处理了。

    (2)处理设置属性请求

    接收请求的地方是在init进程中。代码例如以下所看到的:

    [-->init.c::main函数片断]

    if (ufds[1].revents == POLLIN)

               handle_property_set_fd(property_set_fd);

    当属性server收到client请求时。init会调用handle_property_set_fd进行处理。这个函数的代码例如以下所看到的:

    [-->property_service.c]

    void handle_property_set_fd(int fd)

    {

       prop_msg msg;

        int s;

        int r;

        intres;

        structucred cr;

        structsockaddr_un addr;

       socklen_t addr_size = sizeof(addr);

       socklen_t cr_size = sizeof(cr);

        //先接收TCP连接

        if ((s= accept(fd, (struct sockaddr *) &addr, &addr_size)) < 0) {

           return;

        }

        //取出client进程的权限等属性。

        if(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {

            ......

           return;

        }

       //接收请求数据

        r = recv(s,&msg, sizeof(msg), 0);

       close(s);

        ......

       switch(msg.cmd) {

        casePROP_MSG_SETPROP:

           msg.name[PROP_NAME_MAX-1] = 0;

           msg.value[PROP_VALUE_MAX-1] = 0;

            /*

    假设是ctl开头的消息,则觉得是控制消息。控制消息用来运行一些命令。比如用

    adb shell登录后,输入setprop ctl.start bootanim就能够查看开机动画了。

    关闭的话就输入setpropctl.stop bootanim,是不是非常有意思呢?

            */

           if(memcmp(msg.name,"ctl.",4) == 0) {

               if (check_control_perms(msg.value, cr.uid, cr.gid)) {

                   handle_control_message((char*) msg.name + 4, (char*) msg.value);

               }

               ......

            }else {

               //检查client进程是否有足够的权限

               if (check_perms(msg.name, cr.uid, cr.gid)) {

                   //然后调用property_set设置。

                   property_set((char*) msg.name, (char*) msg.value);

               }

               ......

            }

           break;

       default:

           break;

        }

    }

    当client的权限满足要求时。init就调用property_set进行相关处理。这个函数比較简单,代码例如以下所看到的:

    [-->property_service.c]

    int property_set(const char *name, const char*value)

    {

       prop_area *pa;

       prop_info *pi;

        intnamelen = strlen(name);

        intvaluelen = strlen(value);

        ......

        //从属性存储空间中寻找是否已经存在该属性

        pi =(prop_info*) __system_property_find(name);

    if(pi!= 0) {

        //假设属性名以ro.开头,则表示是仅仅读的,不能设置,所以直接返回。

           if(!strncmp(name, "ro.", 3)) return -1;

            pa= __system_property_area__;

            //更新该属性的值

           update_prop_info(pi, value, valuelen);

           pa->serial++;

           __futex_wake(&pa->serial, INT32_MAX);

    }else {

       //假设没有找到相应的属性,则觉得是添加属性,所以须要新创建一项。

    注意。Android支持

       //最多247项属性,假设眼下属性的存储空间中已经有247项。则直接返回。

            pa= __system_property_area__;

           if(pa->count == PA_COUNT_MAX) return -1;

            pi= pa_info_array + pa->count;

           pi->serial = (valuelen << 24);

           memcpy(pi->name, name, namelen + 1);

            memcpy(pi->value, value, valuelen +1);

           pa->toc[pa->count] =

               (namelen << 24) | (((unsigned) pi) - ((unsigned) pa));

           pa->count++;

           pa->serial++;

           __futex_wake(&pa->serial, INT32_MAX);

        }

        //有一些特殊的属性须要特殊处理,这里。主要是以net.change开头的属性。

        if(strncmp("net.", name, strlen("net.")) == 0)  {

            if(strcmp("net.change", name) == 0) {

               return 0;

            }

           property_set("net.change", name);

        } elseif (persistent_properties_loaded &&

           strncmp("persist.", name,strlen("persist.")) == 0) {

           //假设属性名以persist.开头。则须要把这些值写到相应文件里去。

          write_persistent_property(name, value);

    }

    /*

    还记得init.rc中的以下这句话吗?

    on property:persist.service.adb.enable=1

             startadbd

    当persist.service.adb.enable属性置为1后,就会运行start adbd这个command,

    这是通过property_changed函数来完毕的。它非常easy。读者能够自己阅读。

    */

       property_changed(name, value);

        return0;

    }

    好,属性服务端的工作已经了解了,以下看client是怎样设置属性的。

    (3)client发送请求

    client通过property_set发送请求。property_set由libcutils库提供,代码例如以下所看到的:

    [-->properties.c]

    int property_set(const char *key, const char*value)

    {

       prop_msg msg;

       unsigned resp;

       ......

       msg.cmd = PROP_MSG_SETPROP;//设置消息码为PROP_MSG_SETPROP。

       strcpy((char*) msg.name, key);

       strcpy((char*) msg.value, value);

        //发送请求

        returnsend_prop_msg(&msg);

    }

    static int send_prop_msg(prop_msg *msg)

    {

        int s;

        int r;

        //建立和属性server的socket连接

        s =socket_local_client(PROP_SERVICE_NAME,

                               ANDROID_SOCKET_NAMESPACE_RESERVED,

                                SOCK_STREAM);

        if(s< 0) return -1;

        //通过socket发送出去

       while((r = send(s, msg, sizeof(prop_msg), 0)) < 0) {

           if((errno == EINTR) || (errno == EAGAIN)) continue;

           break;

        }

        if(r== sizeof(prop_msg)) {

            r= 0;

        } else{

            r= -1;

        }

       close(s);

        returnr;

    }

    至此,属性server就介绍完了。总体来说,还算比較简单。

    3.3  本章小结

    本章解说了init进程怎样解析zygote,以及属性server的工作原理,旨在帮助读者认识这个天字号第一进程。

    从总体来说,init.rc的解析难度相对最大。相信读者通过以上实例分析,已经理解了init.rc的解析原理。

    另外。inti涉及非常多和Linux系统相关的知识,有兴趣的读者能够自行研究。

  • 相关阅读:
    C++ Primer Chap13
    Visual Studio 2005调试ASP脚本程序的方法
    我所关注的Tech•Ed 2010(一)云计算
    解决VS2008中日文代码注释乱码的问题
    [翻译]为EXPRESSION WEB 4添翼—如何支持HTML5设计开发!
    文件数量较多的情况下如何提高刻录速度(调用IMAPI2实现DVD刻录功能)
    如何用C++实现安全Remove USB Device
    Microsoft Press ebookProgramming Windows Phone 7
    我所关注的Tech•Ed 2010(二)—Windows Phone
    我所关注的Tech•Ed 2010(三)移动云计算应用开发
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/7355907.html
Copyright © 2011-2022 走看看