Android开机流程分析 -- init进程

来源:互联网 发布:seo招聘要求 编辑:程序博客网 时间:2024/05/18 02:39
init进程(system\core\init)是Linux Kernal启动之后,在用户空间运行的第一个进程。init进程是一个守护进程,它的生命周期贯穿整个Linux内核运行的始终,在Linux系统中的所有进程都是由init进程创建并运行的。因为Android是基于Linux内核的,所以Google为启动并运行整个Android系统,实现了自己的init进程。
一、init进程职责
init作为所有进程的父进程,被赋予了至关重要的工作职责,那么它做了哪些事情呢?
1、首先系统内核启动完成后,到用户空间启动init进程,然后再启动系统运行的其他进程。在系统启动完成后,init进程作为守护进程运行。
init进程作为守护进程,用来监视其他进程,若某个被监视的进程一旦终结,进入僵死状态,就会释放掉进程所占用的系统资源。若有时候父进程先于子进程死掉,则一般会让init进程领养,init进程成为其父进程。
2、init进程负责创建系统中的几个关键进程,最重要的是zygote,zygote更是java世界的创建者。那么,init进程是如何创建zygote的呢?
3、Android系统有很多属性,于是init提供了一个property service(属性服务)来管理它们。那么这个属性服务是怎么工作的呢?
二、init分析
(一)、init中main函数分析(system\core\init\init.c)
(1)、先看一下这个变量的定义:strcut pollfd ufds[4];在后面的poll轮训中用到了该数组。
ufds是一个指向pollfd的结构体数组,pollfd的定义如下:

#include <sys/poll.h> int poll(struct pollfd *ufds, unsigned int nfds, int timeout); struct pollfd { int fd; /* 想查询的文件描述符. */ short int events; /* 等待的事件*/ short int revents; /* 实际发生了的事件 */ };

ufds 指向 struct pollfd 数组,pollfd是用于存放需要监控事件的文件描述符;nfds 指定 pollfd 数组元素的个数,也就是要监测几个 pollfd;timeout用于标记poll函数调用的阻塞时间,如果timeout为0,表示不阻塞,直接返回;poll函数返回ufds中revents不为0的fd个数;如果超时没有任何事件发生,返回0;失败时返回-1。

(2)、启动ueventd、watchdog进程
 if (!strcmp(basename(argv[0]), "ueventd")) 
return ueventd_main(argc, argv);if (!strcmp(basename(argv[0]), "watchdogd"))return watchdogd_main(argc, argv);可以看出传入到mian函数的命令行参数有ueventd和watchdogd,通过比较它们的值,进入到相应的main函数中,启动uevent和watchdog进程,分别是冷插拔进程和看门狗进程。ueventd伺服程序将解析/ueventd.rc文件,并创建相应的设备结点。watchdogd伺服程序是一个看门狗程序,它的任务就是定期向看门狗设备文件执行写操作,以判断系统是否正常运行。
(3)、将当前umask值设置为0
/* clear the umask */umask(0);
调用umask(0)函数(函数原型:mode_t umask(mode_t mask)),将当前umask值设置为0(实际上是参数值mask & 0777 之后的值),先前默认值为022;引用该函数的主要作用是在创建文件时设置或屏蔽掉文件的一些权限。这里将mask设置成0,说明后面的文件创建时的权限是多少实际权限就是多少。
(4)、创建文件系统目录并挂载文件系统
 /* Get the basic filesystem setup we need put         * together in the initramdisk on / and then we'll         * let the rc file figure out the rest.         */mkdir("/dev", 0755);mkdir("/proc", 0755);mkdir("/sys", 0755);mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");mkdir("/dev/pts", 0755);mkdir("/dev/socket", 0755);mount("devpts", "/dev/pts", "devpts", 0, NULL);mount("proc", "/proc", "proc", 0, NULL);mount("sysfs", "/sys", "sysfs", 0, NULL);
首先构建dev、proc、sys三个主要目录(其他目录从init.rc中读取建立),再调用mount函数对文件系统挂载。在这里分别挂载了tmpfs、devpts、proc、sysfs四类文件系统。
mount函数定义:int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data);source是要挂上的文件系统,通常是设备名;target是文件系统所要挂载的目标目录;filesystemtype是文件系统类型,只要有“ext2”、“ext3”、“proc”、“sysfs”、“ntfs”、“tmpfs”等;mountflags是文件系统的读写访问标志;data是文件系统特有的参数。
tmpfs:是一种虚拟内存文件系统,典型的tmpfs文件系统是完全驻留在ARM中(虚拟内存)的,因此读写速度远远快于闪存和硬盘文件系统。并且tmpfs下的内容均为临时性内容,因此如果将tmpfs卸载后,其里面的内容将不再存在。
devpts:是一种远程虚拟终端文件设备,标准挂载点是/dev/pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。通过对相应的设备文件进行操作,可以达到操作硬件的目的(读写)。
proc:是一种虚拟的文件系统,只存在内存中,而不占用内存空间。它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。通过echo可以修改内核参数,举例如下:
root@CoolpadY90:/ # cat /proc/sys/net/ipv4/ip_forwardcat /proc/sys/net/ipv4/ip_forward1root@CoolpadY90:/ # echo "0" > /proc/sys/net/ipv4/ip_forwardecho "0" > /proc/sys/net/ipv4/ip_forwardroot@CoolpadY90:/ # cat /proc/sys/net/ipv4/ip_forwardcat /proc/sys/net/ipv4/ip_forward0
sysfs:是Linux中设计的一种较新的虚拟文件系统,与proc类似,除了与proc一样查看和设置内核参数之外,还为Linux统一设备管理模型之用。参考:http://blog.chinaunix.net/uid-27411029-id-3522299.html
注意:在编译Android系统源码时,在生成的根文件系统中,不存在/dev、/proc、/sys这类目录,它们是系统运行时的目录,有init进程在运行时生成,当系统终止时,自动消失。
(5)、屏蔽标准的输入输出,即标准的输入输出重定向到null设备(/dev/__null__)。
/* indicate that booting is in progress to background fw loaders, etc */close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));    /* 检测/dev/.booting文件是否可读写和创建*/
/* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won't * be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually * talk to the outside world. */open_devnull_stdio(); /* 在/dev目录下生成__null__设备节点文件,并将标准输入,标准输出,标准错误,标准错误输出全部重定向__null__设备中。*/klog_init();  /* 初始化内核log系统。生成"/dev/__kmsg__"设备节点文件, 作为init的日志输出设备(/dev/kmsg),设置完成后马上调用unlink函数,其他进程就无法打开这个文件读取日志信息了。这个设备会把它收到的任何写入都作为printk的输出,printk是内核中运行的向控制台输出显示的函数。*/
property_init(); /* 初始化属性服务所需要的基本空间。首先创建一个/dev/__properties__文件,然后通过对应的文件描述映射一块共享内存,大小PA_SIZE(49152),映射的地址和相应的文件描述符保存在struct workspace中。 */
(6)、从/proc/cpuinfo中获取硬件信息,将处理传递给内核的命令行参数。
  get_hardware_name(hardware, &revision);process_kernel_cmdline(); 
process_kernel_cmdline函数中,先调用import_kernel_cmdline函数从/proc/cmdline读取内核启动参数,然后调用export_kernel_boot_props在属性系统设置启动属性。
如果启用了SELinux机制,接下来将加载selinux策略,并初始化文件安全上下文以及属性安全上下文。selinux是Linux内核提供的一种强制访问控制安全系统,在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要的文件。restorecon函数表示所定义的文件可以恢复原来的文件标签。参考:http://www.it165.net/pro/html/201402/9640.html
union selinux_callback cb;cb.func_log = log_callback;selinux_set_callback(SELINUX_CB_LOG, cb);cb.func_audit = audit_callback;selinux_set_callback(SELINUX_CB_AUDIT, cb);selinux_initialize();/* These directories were necessarily created before initial policy load* and therefore need their security context restored to the proper value.* This must happen before /dev is populated by ueventd.*/restorecon("/dev");restorecon("/dev/socket");restorecon("/dev/__properties__");restorecon_recursive("/sys");
(7)、如果当前启动模式不是充电模式,将/default.prop文件中加载默认的一些属性设置。这里的bootmode是一个静态全局变量,用来标示启动模式,注意这里的bootmode是在UBoot中设置的。
is_charger = !strcmp(bootmode, "charger");      INFO("property init\n");      property_load_boot_defaults();
8)、解析init.rc配置文件。对init.rc配置文件的解析,稍候详细介绍。
INFO("reading config file\n");      init_parse_config_file("/init.rc");
解析完配置文件init.rc后,会得到一系列的Action(动作)和服务,生成服务列表和动作列表;动作列表和服务列表会以链表的形式注册到service_list与action_list中,service_list和action_list是init进程中声明的全局结构体。
(9)、action待执行队列
首先触发init.rc中配置的early-init的action,并通过action_add_queue_tail函数将该action放入可执行队列action_queue队尾;接着触发内置action,内置action并没有在init.rc或init.<hardware>.rc中配置,queue_builtin_action函数第一个参数是函数指针,第二个参数是action的触发器,即action的名字,函数指针的作用是把该触发器指定的Action放入可执行队列队尾。
如果定义了BOOTCHART宏,触发boot chart初始化的Action。boot chart是分析系统启动过程的工具,并生成系统启动过程的图表,以提供一些有价值的信息,帮助提升系统的启动速度。
action_for_each_trigger("early-init", action_add_queue_tail);queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");queue_builtin_action(keychord_init_action, "keychord_init");queue_builtin_action(console_init_action, "console_init");/* execute all the boot actions to get us started */action_for_each_trigger("init", action_add_queue_tail);/* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random* wasn't ready immediately after wait_for_coldboot_done*/queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");queue_builtin_action(property_service_init_action, "property_service_init");queue_builtin_action(signal_init_action, "signal_init");/* Don't mount filesystems or start core system services if in charger mode. */if (is_charger) {action_for_each_trigger("charger", action_add_queue_tail);} else {action_for_each_trigger("late-init", action_add_queue_tail);}/* run all property triggers based on current state of the properties */queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");#if BOOTCHARTqueue_builtin_action(bootchart_init_action, "bootchart_init");#endif
(10)、init经过上面的初始化和触发action的过程,进入一个无限循环,执行command,处理事件。
for(;;) {int nr, i, timeout = -1;execute_one_command();  /* 执行当前Action的一个Command检测 */restart_processes();   /* 重新启动异常退出的service。通过遍历service_list列表,找到flags设置为需要启动的service,调用restart_service_if_needed函数启动它。*/if (!property_set_fd_init && get_property_set_fd() > 0) {   /* 监听来自属性服务property service的事件,属性服务是init提供的重要功能。 */  ufds[fd_count].fd = get_property_set_fd();  /* 得到property service的fd,是一个socket。 */
    /* 有PILLIN事件发生时,revents就会设置为POLLIN。*/
ufds[fd_count].events = POLLIN; /* 有数据可读的事件 */
ufds[fd_count].revents = 0;
fd_count++;
property_set_fd_init = 1;
}
/* 监控signal。主要用于接收子进程异常退出后内核抛出的SIGCHLD信号,然后决定回收子进程资源或者重启子进程,防止子进程成为僵尸进程。 */if (!signal_fd_init && get_signal_fd() > 0) {     /* get_signal_fd函数用于获得子进程退出新号, */ufds[fd_count].fd = get_signal_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;signal_fd_init = 1;}
/* 监听来自keychord设备的事件,keychord是组合按键设备 */if (!keychord_fd_init && get_keychord_fd() > 0) {ufds[fd_count].fd = get_keychord_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;keychord_fd_init = 1;}/* 死去的服务如果需要重启,设置等待时间 */if (process_needs_restart) {timeout = (process_needs_restart - gettime()) * 1000;if (timeout < 0)timeout = 0;}/* 如果有正要处理的Action,则设置timeout为0,表示poll不阻塞 */if (!action_queue_empty() || cur_action)timeout = 0;#if BOOTCHARTif (bootchart_count > 0) {if (timeout < 0 || timeout > BOOTCHART_POLLING_MS)timeout = BOOTCHART_POLLING_MS;if (bootchart_step() < 0 || --bootchart_count == 0) {bootchart_finish();bootchart_count = 0;}}#endif/* 将ufds传入poll函数,监控事件的发生 */nr = poll(ufds, fd_count, timeout);if (nr <= 0)continue;for (i = 0; i < fd_count; i++) {    /* 轮询这几个Socket */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(); /* 处理keychord事件 */else if (ufds[i].fd == get_signal_fd())handle_signal();/* 处理signal事件 */}}}
这里以get_property_set_fd为例,简要说明一下这部分的处理机制。
当start_property_service中创建的Socket上有可读事件发生时,init中的poll函数监控到可读事件发生,便开始执行handle_property_set_fd函数,该函数位于system/core/init/Property_service.c中,代码如下:
void handle_property_set_fd(){    prop_msg msg;    int s;    int r;    int res;    struct ucred cr;    struct sockaddr_un addr;    socklen_t addr_size = sizeof(addr);    socklen_t cr_size = sizeof(cr);    char * source_ctx = NULL;    struct pollfd ufds[1];    const int timeout_ms = 2 * 1000;  /* Default 2 sec timeout for caller to send property. */    int nr;    
     /* 接收property_set_fd上的连接请求,accept是标准的Socket编程函数 */ 
    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
        return;
    }

    /* Check socket options here */
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        ERROR("Unable to receive socket options\n");
        return;
    }

    ufds[0].fd = s;
    ufds[0].events = POLLIN;
    ufds[0].revents = 0;
    nr = TEMP_FAILURE_RETRY(poll(ufds, 1, timeout_ms));
    if (nr == 0) {
        ERROR("sys_prop: timeout waiting for uid=%d to send property message.\n", cr.uid);
        close(s);
        return;
    } else if (nr < 0) {
        ERROR("sys_prop: error waiting for uid=%d to send property message. err=%d %s\n", cr.uid, errno, strerror(errno));
        close(s);
        return;
    }

    r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT)); /* recv接收请求数据 */
    if(r != sizeof(prop_msg)) {
        ERROR("sys_prop: mis-match msg size received: %d expected: %zu errno: %d\n",
              r, sizeof(prop_msg), errno);
        close(s);
        return;
    }

    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
/* 属性客户端设置的消息码 */
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;

        if (!is_legal_property_name(msg.name, strlen(msg.name))) {
            ERROR("sys_prop: illegal property name. Got: \"%s\"\n", msg.name);
            close(s);
            return;
        }

        getpeercon(s, &source_ctx);
 
/* 如果消息名以ctl.开头,则认为是control message(控制消息),调用check_control_mac_perms函数检查权限,如果满足权限要求则执行handle_control_message设置消息。这部分消息其实控制的是Service的开启和关闭。 */
        if(memcmp(msg.name,"ctl.",4) == 0) {
            // Keep the old close-socket-early behavior when handling
            // ctl.* properties.
            close(s);
            if (check_control_mac_perms(msg.value, source_ctx)) {
                handle_control_message((char*) msg.name + 4, (char*) msg.value);
            } else {
                ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",
                        msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
            }
        } else { /* 其他类型的消息,需要通过check_perms检查权限,如果满足权限要求调用property_set函数设置属性 */
            if (check_perms(msg.name, source_ctx)) {
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }

            // Note: bionic's property client code assumes that the
            // property server will not close the socket until *AFTER*
            // the property is written to memory.
            close(s);
        }
        freecon(source_ctx);
        break;

    default:
        close(s);
        break;
    }

}
当属性服务器接收到客户端请求后,init中的poll函数监控到可读事件,这个时候它会返回,判断property fd这个Socket上是否发生了可读事件,之后调用init的handle_property_set_fd方法处理请求。handle_property_set_fd函数主要做了两部分工作:首先通过accept和recv这两个标准的Socket编程函数接收并取得客户端消息,然后根据消息类型分别调用权限检测函数和属性设置函数,设置属性或启动相应的服务。
通过对init.c文件中main函数的分析,可将init工作流程精简为以下四点:
1).  初始化文件系统和日志系统,为之后的执行阶段做准备。
2).  解析init.rc和init.<hardware>.rc初始化文件。
3).  触发需要执行的Action和Service。
4).  init循环监听事件。init触发所有Action后,进入一个无限循环,执行在可执行队列中的命令,重启异常退出的Service,并循环处理来自property service、signal、keychord的事件。
下一节接着分析配置文件的解析。


0 0
原创粉丝点击