• Android2.2源码init机制分析


    1 源码分析必备知识

    1.1 linux内核链表

    Linux内核链表的核心思想是:在用户自定义的结构A中声明list_head类型的成员p,这样每个结构类型为A的变量a中,都拥有同样的成员p,如下:

    struct A{

    int property;

    struct list_head p;

    }

    其中,list_head结构类型定义如下:

    struct list_head {

    struct list_head *next,*prev;

    };

    list_head拥有两个指针成员,其类型都为list_head,分别为前驱指针prev和后驱指针next。

    假设:

    (1)多个结构类型为A的变量a1...an,其list_head结构类型的成员为p1...pn

    (2)一个list_head结构类型的变量head,代表头节点

    使:

    (1)head.next= p1 ; head.prev = pn

    (2) p1.prev = head,p1.next = p2;

    (3)p2.prev= p1 , p2.next = p3;

    (n)pn.prev= pn-1 , pn.next = head

    以上,则构成了一个循环链表。

    因p是嵌入到a中的,p与a的地址偏移量可知,又因为head的地址可知,所以每个结构类型为A的链表节点a1...an的地址也是可以计算出的,从而可实现链表的遍历,在此基础上,则可以实现链表的各种操作。

    注:android源码中就是使用的

    struct listnode {

    struct listnode *next,*prev;

    };

    1.2 内核链表的遍历

    #define list_for_each(pos, head) 

        for (pos = (head)->next; prefetch(pos->next), pos != (head); pos = pos->next)

    从上可以看出list_for_each其实就是一个for循环,

    for()实现的就是一个链表的遍历。

    同时,为了取得链表中的节点值,还是用了node_to_item函数来取得节点数据。

    综合使用如下:

    list_for_each(node, &service_list) {

            svc = node_to_item(node, struct service, slist);

            /*处理函数*/

            Fun()….

        }

    1.3 linux umask机制

    当我们登录系统之后创建一个文件总是有一个默认权限的,那么这个权限是怎么来的呢?这就是umask干的事情。umask设置了用户创建文件的默认 权限,它与chmod的效果刚好相反,umask设置的是权限“补码”,而chmod设置的是文件权限码。umask是从权限中“拿走”相应的位,且文件创建时不能赋予执行权限。

    举例:

    指定umask(022)。那么就意味这我们在创建目录时,目录的默认权限为777 – 022 = 755——rwxr_xr_x。

    值得注意的是:如果我们创建一个文件那么该文件的默认权限是777 – 022 – 111(默认文件不能赋予执行权限) = 644!

    2 init.rc 资源配置文件的解析

    Init.rc 指示系统在那个阶段,按照什么方式,执行哪些行为。

    在认识init.rc之前,我们需要了解keywords.h里面的定义。在那个文件中主要工作是:定义多种keyword(每个keyword分属不同的类型,如:section,option,command),并将每个keyword与其对应的操作函数联系起来。

    这个文件分为多个section,每个section由section标识符(on, service,import)的关键字开始,到下一个section的开始的地方结束。

    2.1 解析service

    这里以zygote为例。

    首先在parse_config函数里面调用kw_is(kw, SECTION)找到init.rc的一个section,然后调用parse_new_section(&state, kw, nargs, args)针对不同的section使用不同的解析函数来解析。

    由于zygote是一个K_service,所以调用parse_service和parse_line_service来解析service。

    在查看这两个函数之前,我们需要理解什么是service。

    2.2 service结构体

    Init使用了这个结构体来保存与service section相关的信息。详见:init.h::service中。

    在这个结构体中比较重要的是:

    1、struct listnode slist;  这是一个特殊的结构体,在内核代码中使用得相当广泛,主要用来将结构体(可以是不同类型的)链接成一个双向链表。Init中有一个全局的service_list,专门用来保存解析rc文件后得到的各个service。

    2、struct  action  onrestart; 这里需要注意:虽然关键字onrestart是OPTION,但是通常此关键字后面都会跟着一些COMMAND。此结构体就是用来存储onrestart后面的COMMAND信息的!

    struct action {

            /* node in list of all actions */

        struct listnode alist;              //所有的action

            /* node in the queue of pending actions */

        struct listnode qlist;              //等待执行的action

            /* node in list of actions for a trigger */

        struct listnode tlist;              //等待某些条件满足后触发的action

        unsigned hash;

        const char *name;

       

    /*★ 前面已经说了listnode用于连接结构体。这里会根据OPTION后面的command数量来创建对应的数量的command 结构体,然后组成双向链表 */

        struct listnode commands;  

        struct command *current;   //指向当前的command结构体

    };

    Command结构体的定义如下:

    struct command

    {

            /* list of commands in an action */

        struct listnode clist;

    /*在后面分析的parse_line_service函数中的switch语句中的case K_onrestart中会给此函数指针赋值,指向具体的command执行函数*/

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

        int nargs;

        char *args[1];

    };

    3、unsigned flags,service的属性标记,共有9种:

    SVC_DISABLED:不随class自启动(后面会分析class的作用);

    SVC_ONESHOT:退出后不需要重启,也就是说这个service只启动一次;

    SVC_RUNNING:正在运行;

    SVC_RESTARTING:等待重启;

    SVC_CONSOLE:该service需要使用控制台;

    SVC_CRITICAL:如果在规定的时间内该service不断重启,则系统会重启并进入恢复模式

    SVC_RESET:当系统主动停止一个service的时候使用,这不会让该service变成disable状态,所以,该service可以随着其所属的class的启动而启动;

    SVC_RC_DISABLED:记住service的disable标记是否由init.rc脚本显示指定的;

    SVC_RESTART:用于安全地重启一个service;

    Zygote没有使用任何属性,这表明他它会随着class的处理而自动启动,退出后由init重启;不使用控制台;即使不断重启也不会进入恢复模式。

    2.3 分析parse_service函数

    ①声明一个指向service结构体的指针:svc;

    ②然后进行参数校验;

    ③去全局链表中查看是否有同名的services存在;

    它是通过调用函数service_find_by_name来实现的。在这个函数中使用list_for_each函数来遍历整个链表,进行service名字匹配。

    ④如果存在了,就直接返回0;否则就为svc分配内存,并给各个字段赋值;

    ⑤初始化svc->onrestart.commands链表;

    list_init(&svc->onrestart.commands);

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

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

    总结:parse_service函数只是搭建了一个service的框架,并没有什么实质的解析操作,具体的内容是有parse_line_service函数来填充的。

    2.4 分析parse_line_service函数

    此函数主要结构为:

    kw = lookup_keyword(args[0]);  //将rc中的字符型kw转换成keywords.h中定义的枚举值。

    /*根据kw的值,进行相应的操作*/

    switch(kw){

        case:

        ……….

    };

    需要注意的是case K_onrestart //根据onrestart的内容来填充action结构体的内容。

    3 init控制service

    在解析完init.rc文件后,系统就已经将相关信息写入了相应的队列之中,下一步就是执行这些队列里面的COMMAND了。

    同样的以zygote为例。

    3.1 启动zygote

    在解析init.rc的时候,发现zygote的class名字为main。那么就相当于把zygote服务加入到了全局service_list链表中,并且将它所对应的classname 赋值为main。

    那么这个classname的作用是什么呢?其实就是一个标识符,用于区分不同服务的类别。纵观整个init.rc文件,class name 共有两种“main”,“core”。

    继续往下分析。到目前为止我们还没发现系统是如何启动服务的,直到init.c的main函数执行的下面的语句:

    action_for_each_trigger("boot", action_add_queue_tail);

    //将init.rc中boot section 的command加入到执行队列中。

    再转而看init.rc中的boot section:

    On boot

    ……

    class_start core 

    class_start main

    Class_start 在keywords中表示为一个COMMAND,其对应的处理函数是do_class_start。

    所以当init进程执行到:

    /* run all property triggers based on current state of the properties */

        queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

    就会执行do_class_start函数(这里为了表示方便才这样说,其实这里仅仅是将此函数加入到待执行action队列尾,再由后面的execute_one_command函数执行此函数)。

    开始分析do_class_start的函数流程。

    int do_class_start(int nargs, char **args)

    {

            /* Starting a class does not start services

             * which are explicitly disabled.  They must

             * be started individually.

             */

           /*

           Args为init.rc文件中class后面的那个参数值(core或main)。下面这个函数将遍历service_list,找到对应名字的service,然后调用service_start_if_not_disable函数——此函数实质上就是调用service_start函数。

                 */

        service_for_each_class(args[1], service_start_if_not_disabled);

        return 0;

    }

    Service_start函数的代码较多,就不列出来了,可以在init.c中去找。

    下面分析该函数的逻辑:

    ①设置服务的状态标识符;

    ②如果这个service已经在运行了,那么就不用处理;

    ③由于service一般运行在另外的进程中(这个进程也是init的子进程),所以在启动service之前,需要判断对应的可执行文件是否存在,zygote的可执行文件为/system/bin/app_process

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

            ERROR("cannot find '%s', disabling '%s' ", svc->args[0], svc->name);

            svc->flags |= SVC_DISABLED;

            return;

        }

    ④判断是否在selinux环境中,并进行相应的操作(这个不是很懂,也不是核心代码,就略过了);

    ⑤★然后就是真正的核心部分了——使用fork函数创建子进程!

    pid = fork();

        if (pid == 0) { //表示现在运行在子进程中

            struct socketinfo *si;

            struct svcenvinfo *ei;

            char tmp[32];

            int fd, sz;

            umask(077); //默认目录权限为700,文件权限为600

            if (properties_inited()) { //判断属性是否已经完成初始化了

    //得到属性存储空间的信息并加入到环境变量中

                get_property_workspace(&fd, &sz);

                sprintf(tmp, "%d,%d", dup(fd), sz);

                add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);

            }

    //添加环境变量信息

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

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

    //根据socketinfo创建socket,SOCK_STREAM 用于面向流的套接字, SOCK_DGRAM 用于面向数据报的套接字,其可以保存消息界限. Unix 套接字总是可靠的,而且不会重组数据报.        

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

                int socket_type = (

                        !strcmp(si->type, "stream") ? SOCK_STREAM :

                            (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));

    //创建socket,这里创建的socket的域名是PF_UNIX:用于本地进程间的通信

                int s = create_socket(si->name, socket_type,

                                      si->perm, si->uid, si->gid, si->socketcon ?: scon);

                if (s >= 0) {

    //在环境变量中添加socket信息

                    publish_socket(si->name, s);

                }

            }

            freecon(scon); //不懂,什么意思?网上说是:free memory associated with SELinux security contexts. 暂且当作free看待吧~

            scon = NULL;

            …

    //判断service是否需要控制终端

            if (needs_console) {

    //调用setsid(),使得当前进程成为会话组长。详细信息涉及到pid,gid,sid等,可自行百度。

                setsid();

                open_console();

            } else {

                zap_stdio();

            }

    //然后就是设置gid,uid等

    ……

    if (!dynamic_args) {

    // 执行/system/bin/app_process,这样就进入到app_process的MAIN函数中了。

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

                    ERROR("cannot execve('%s'): %s ", svc->args[0], strerror(errno));

                }

          } else {

          ………

    }

    ……

    //父进程init中,设置service的信息:启动时间,进程号,以及状态等。

    svc->time_started = gettime();

        svc->pid = pid;

        svc->flags |= SVC_RUNNING;

        if (properties_inited()){

        //每一个service都有一个属性,zygote的属性为init.svc.zygote,

    现在设置它的值为”running”。

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

    }

    }?end service start?

    至此service_start函数分析完毕。总结一句话就是:每一个service都是由init进程通过fork和execv函数共同创建的

    3.2 重启zygote

    分析完了service的启动过程,我们发现,service中的onrestart并没有使用,why?

    从名字可以看出,这应该是用于service重新启动的时候使用的。下面开始分析当zygote死后,其父进程init会进行哪些操作。常识告诉我们,子进程死后,通常会向父进程发送信号,父进程接收到此信号后进行相应的处理。那么这就需要我们找到子进程如何向父进程发送信号,以及父进程是如何接收并处理来自子进程的信号的。

    首先,我们回到init.c的main函数中。下面的语句就是init的信号量处理机制:

    //执行signal_init_action函数。此函数初始化父子进程信号量处理机制。

    queue_builtin_action(signal_init_action, "signal_init");

    //signal_init_action函数调用signal_init().

    static int signal_init_action(int nargs, char **args)

    {

        signal_init();

        return 0;

    }

    //重点就是这个函数

    void signal_init(void)

    {

        int s[2];

    //声明一个信号量处理结构体

        struct sigaction act;

        memset(&act, 0, sizeof(act));

    act.sa_handler = sigchld_handler;

    /*

      定义信号量处理函数,当子进程退出时,调用此函数。

    static void sigchld_handler(int s)

    {

        write(signal_fd, &s, 1); //向父进程(init)发送信号

    }

    */

        act.sa_flags = SA_NOCLDSTOP;

        sigaction(SIGCHLD, &act, 0);

    /* create a signalling mechanism for the sigchld handler

      使用socketpair创建一对socket,只要一个socket发送数据,另一个socket就一定能收到此数据。

    */

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

            signal_fd = s[0];   //发送方socket,一般是子进程使用

            signal_recv_fd = s[1]; //接收方socket,一般是父进程(init)使用

            fcntl(s[0], F_SETFD, FD_CLOEXEC);

            fcntl(s[0], F_SETFL, O_NONBLOCK);

            fcntl(s[1], F_SETFD, FD_CLOEXEC);

            fcntl(s[1], F_SETFL, O_NONBLOCK);

        }

        handle_signal(); //处理信号

    }

    从上面的信息我们可以得出:当子进程退出的时候,它会调用sigchld_handler函数向父进程(init)发送信号量。那么init进程又是怎样接收并处理这个信号量的呢?回到init.c中main的for循环中:

    nr = poll(ufds, fd_count, timeout);

    if (nr <= 0)

        continue;

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

                if (ufds[i].revents & POLLIN) {

                    if (ufds[i].fd == get_property_set_fd())

                        handle_property_set_fd();

                    else if (ufds[i].fd == get_keychord_fd())

                        handle_keychord();

    /*★get_signal_fd函数返回signal_recv_fd,这里判断是否有来自signal_recv_fd的信息,如果有,那么就调用信号量处理函数handle_signal()

    */

                    else if (ufds[i].fd == get_signal_fd())

                        handle_signal();

                }

            }

    void handle_signal(void)

    {

        char tmp[32];

        /* we got a SIGCHLD - reap and restart as needed */

        read(signal_recv_fd, tmp, sizeof(tmp)); //读取信号量

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

            ;

    }

    wait_for_one_process函数的代码较多,这里就不列出了,可以自己去Signal_handler.c中查看。

    下面简要介绍下该函数的逻辑:

    ①使用waitpid函数获取死掉进程的pid,status;这里是zygote的PID。

    ②使用service_find_by_pid函数,获取死掉的那个进程的service;这里是zygote service。

    ③kill该service创建的所有子进程——这就是zygote死后,整个JAVA世界崩溃的原因。

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

            kill(-pid, SIGKILL);

            NOTICE("process '%s' killing any children in process group ", svc->name);

        }

    ④清理该service创建的所有socket;

    ⑤如果设置了SVC_CRITIVAL标志,则四分钟内该service重启次数操作4次的话,系统将会进入recovery模式。根据init.rc来看。只有:ueventd、healthd、healthd-charger、servicemanager这四个服务享有此待遇。

    if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {

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

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

                    ERROR("critical process '%s' exited %d times in %d minutes; "

                          "rebooting into recovery mode ", svc->name,

                          CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);

                    android_reboot(ANDROID_RB_RESTART2, 0, "recovery");

                    return 0;

                }

            } else {

                svc->time_crashed = now;

                svc->nr_crashed = 1;

            }

        }

    ⑥设置标识为SVC_RESTARTING,然后执行该service onrestart中的COMMAND。

    svc->flags |= SVC_RESTARTING;

    /* Execute all onrestart commands for this service. */

    /*★这里onrestart终于派上用场了!*/

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

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

            cmd->func(cmd->nargs, cmd->args); //调用相应函数处理command

        }

    ⑦设置service的状态为restarting,并退出。

    通过上面的分析,就可以知道service结构体中的onrestart变量的作用了。但是service(zygote)本身又在哪重启呢?

    在init.c的main函数的for循环中有如下语句:

    execute_one_command();//此函数的逻辑见下面分析。

    restart_processes();  //★在这里重启所有标识为restarting的services!

    上面有一个很重要的函数execute_one_command();此函数详细代码如下:

    void execute_one_command(void)

    {

        int ret;

    /*如果当前action为空,或者当前command为空,或者当前command是当前action的最后一个命令,那么就在队列头取出一个action。如果取出的action为空(表示队列中已经没有需要执行的action了),那么就直接返回,否则取得此action的第一条command*/

        if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {

            cur_action = action_remove_queue_head();

            cur_command = NULL;

            if (!cur_action)

                return;

            INFO("processing action %p (%s) ", cur_action, cur_action->name);

            cur_command = get_first_command(cur_action);

        } else { //如果当前action不为空,且command不为空,且不是最后一条command,那么就执行取得当前action的后一条command.

            cur_command = get_next_command(cur_action, cur_command);

        }

    //如果command为空,就直接返回,否者执行此command。

        if (!cur_command)

            return;

        ret = cur_command->func(cur_command->nargs, cur_command->args);

    INFO("command '%s' r=%d ", cur_command->args[0], ret);

    }

    到这里我们就分析完了整个service的重启过程。

    4 完整的init进程分析

    前面我们是站在service的角度来看init进程如何运行的。现在我们来站在init自己的角度来分析它的整个逻辑。在init.c的main函数中:

    ①挂载必要的文件系统——如创建一些根目录下的目录等;

    ②重定向标注输入/输出/错误输出到/dev/_null_;

    open_devnull_stdio();

    ③设置init的日志输出设备(klog_fd)为/dev/__kmsg__,设置完后马上unlink,其他进程就无法打开这个文件读取日志信息了;

    klog_init();

    ④一些初始化任务;

    //属性服务的初始化操作,主要是分配内存什么的

        property_init();

           //得到硬件名字和版本号

        get_hardware_name(hardware, &revision);

           //处理内核命令行参数

        process_kernel_cmdline();

    ⑤分析init.rc和init.hardware.rc;

    ⑥将init.rc中early-init section中的action加入到全局队列action_queue中;

    action_for_each_trigger("early-init", action_add_queue_tail);

    //此函数的作用就是将init.rc中early-init section中的action加入到全局队列action-queue中,后面类似。

    ⑦通过调用queue_builtin_action()把wait_for_coldboot_done_action, mix_hwrng_into_linux_rng, keychord_init_action, console_init_action,加到action_queue 里;

    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");

    //此函数的作用是将第二个参数name = “wait_for_coldboot_done”的action加入到action_queue中,并指定该action的command的执行函数为第一个参数wait_for_coldboot_done_action。后面类似。

    ⑧将init.rc中init section中的action加入到全局队列action_queue中;

    ⑨如果不处于充电模式(注:这里的充电模式,是关闭手机后,进行充电时的系统状态,而不是开机充电的状态,因为在开机完成后,init进程也早已完成了初始化任务了),则依次将init.rc中的early-fs, fs, post-fs, post-fs-data section中的action加入到action_queue中;

    ⑩通过调用queue_builtin_action()把mix_hwrng_into_linux_rng(第二次加入),property_service_init,signal_init,check_startup加到action_queue 里;这里简要说明一下:

    mix_hwrng_into_linux_rng:主要用于随机数发生器,android的随机数发生器有两种/dev/hw_random or /dev/random,这里为了加强随机性,将hw_random生成的随机数中的512bytes写入到Linux RNG's via /dev/urandom中。

    property_service_init:属性服务的初始化

    signal_init:信号量机制的初始化,创建socket对用于init进程同其子进程通信

    ⑪如果不处于充电模式,那么就将eearly-boot, boot section中的action加入到action_queue中;否者将charge section中的action加入到队列中;

    ⑫通过调用queue_builtin_action()把queue_property_triggers加到action_queue 里;就是执行基于当前所有属性状态的所有属性触发器(trigger)

    ⑬如果已经定义了bootchart,那么就将init.rc中的bootchart_init section加入到action_queue中;

    ⑭开始for循环;

    execute_one_command();//执行action_queue队列中当前action的一条command;

    restart_processes();//执行list_service中所有flags为restarting的services;

    //然后根据需要来设置ufds[], 分别监听来自属性服务器,由soketpair创建的另一个socket,keychord设备这三个事件

    //然后调用poll等待监听事情的发生,如果有来自上面监听的事件,则处理事件,否则,返回for循环,做下一个action

    nr = poll(ufds, fd_count, timeout);

    if (nr <= 0)

        continue;

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

         if (ufds[i].revents & POLLIN) {

         if (ufds[i].fd == get_property_set_fd())

              handle_property_set_fd();

         else if (ufds[i].fd == get_keychord_fd())

              handle_keychord();

         else if (ufds[i].fd == get_signal_fd())

              handle_signal();

         }

    }

  • 相关阅读:
    ****阿里云使用+快速运维总结(不断更新)
    Linux 标准目录结构
    linux awk命令
    反射型 DDoS 攻击的原理和防范措施
    容器平台选型的十大模式:Docker、DC/OS、K8S 谁与当先?
    谈谈数据库的跨机房容灾-网易云
    前端 支持 超大上G,多附件上传
    java 支持 超大上G,多附件上传讨论
    java 支持 超大上G,多附件上传分享
    java 支持 超大上G,多附件上传功能
  • 原文地址:https://www.cnblogs.com/wanyuanchun/p/3709909.html
Copyright © 2020-2023  润新知