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'\n", 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\n", 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\n", 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\n", 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)\n", 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\n", 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();

     }

}

posted @ 2014-05-05 19:47  WanChouchou  阅读(562)  评论(0编辑  收藏  举报