Android Init进程源码分析

来源:互联网 发布:视频直播app源码 编辑:程序博客网 时间:2024/04/29 18:57

Init 进程源码分析

基于Linux内核的android系统,在内核启动完成后将创建一个Init用户进程,实现了内核空间到用户空间的转变。在Android 启动过程介绍一文中介绍了Android系统的各个启动阶段,init进程启动后会读取init.rc配置文件,通过fork系统调用启动init.rc文件中配置的各个Service进程。init进程首先启动启动android的服务大管家ServiceManager服务,然后启动Zygote进程。Zygote进程的启动开创了Java世界,无论是SystemServer进程还是android的应用进程都是Zygote的子进程,Zygote进程启动过程的源代码分析一文中详细介绍了Zygote进程的启动过程,System Server进程启动过程源码分析则详细介绍了在Zygote进程启动完成后创建的第一个进程SystemServer进程的启动过程,SystemServer进程的启动包括两个阶段,在第一阶段主要是启动C++相关的本地服务,如SurfaceFlinger等,在第二阶段通过在ServerThread线程中启动android的各大关键Java服务。Zygote孵化应用进程过程的源码分析一文中详细介绍了Zygote进程创建android应用进程的过程,当用户点击Luncher上的应用图标时,Luncher进程通过socket向Zygote进程发送进程创建请求,Zygote进程接受客户端的请求后,通过fork系统调用为应用程序创建相应的进程。本文则介绍android用户进程的始祖Init进程,Init进程是Linux系统中用户空间的第一个进程,负责创建系统中的关键进程,同时提供属性服务来管理系统属性。

Android进程模型

Linux通过调用start_kernel函数来启动内核,当内核启动模块启动完成后,将启动用户空间的第一个进程——Init进程,下图为Android系统的进程模型图:

从上图可以看出,Linux内核在启动过程中,创建一个名为Kthreadd的内核进程,PID=2,用于创建内核空间的其他进程;同时创建第一个用户空间Init进程,该进程PID = 1,用于启动一些本地进程,比如Zygote进程,而Zygote进程也是一个专门用于孵化Java进程的本地进程,上图清晰地描述了整个Android系统的进程模型,为了证明以上进程模型的正确性,可以通过ps命令来查看进程的PID级PPID,下图显示了Init进程的PID为1,其他的本地进程的PPID都是1,说明它们的父进程都是Init进程,都是由Init进程启动的。

下图显示kthreadd进程的PID=2,有一部分内核进程如binder、dhd_watchdog等进程的PPID=2,说明这些进程都是由kthreadd进程创建:

上图中显示zygote进程PID=107,下图显示了zygote进程创建的子进程,从图中可以看到,zygote进程创建的都是Java进程,证明了zygote进程开创了Android系统的Java世界。

上面介绍了Android系统的进程模型设计,接下来将详细分析Init进程。

Init进程源码分析

上节介绍了Init进程在Linux内核启动时被创建的,那它是如何启动的呢?

Init进程启动分析

在Linux内核启动过程中,将调用Start_kernel来初始化配置:

asmlinkage void __init start_kernel(void){    .............. //执行初始化工作rest_init(); }
start_kernel函数调用一些初始化函数完成初始化工作后,调用rest_init()函数来创建新的进程:

static noinline void __init_refok rest_init(void)__releases(kernel_lock){int pid;rcu_scheduler_starting();    //创建一个kernel_init进程,该进程实质上是Init进程,用于启动用户空间进程kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); numa_default_policy();//创建一个kthreadd内核线程,用于创建新的内核进程pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); rcu_read_lock();kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);rcu_read_unlock();complete(&kthreadd_done);unlock_kernel();/* * The boot idle thread must execute schedule() * at least once to get things moving: */init_idle_bootup_task(current);preempt_enable_no_resched();schedule(); preempt_disable();/* Call into cpu_idle with preempt disabled */cpu_idle();}
在rest_init函数里完成两个新进程的创建:Init进程和kthreadd进程,因为Init进程创建在先,所以其PID=1而kthreadd的PID=2,本文只对Init进程进行详细分析,如果读者对kthreadd进行感兴趣,可自行分析。

kernel_thread函数仅仅调用了fork系统调用来创建新的进程,创建的子进程和父进程都执行在fork函数调用之后的代码,子进程是父进程的一个拷贝。

static int __init kernel_init(void * unused){/* * Wait until kthreadd is all set-up. */wait_for_completion(&kthreadd_done);/* * init can allocate pages on any node */set_mems_allowed(node_states[N_HIGH_MEMORY]);/* * init can run on any cpu. */set_cpus_allowed_ptr(current, cpu_all_mask);cad_pid = task_pid(current);smp_prepare_cpus(setup_max_cpus);    //执行保存在__initcall_start与__early_initcall_end之间的函数do_pre_smp_initcalls();lockup_detector_init();    //smp 多核初始化处理smp_init();sched_init_smp();    //内核驱动模块初始化do_basic_setup();/* Open the /dev/console on the rootfs, this should never fail */if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)printk(KERN_WARNING "Warning: unable to open an initial console.\n");(void) sys_dup(0);(void) sys_dup(0);/* * check if there is an early userspace init.  If yes, let it do all * the work */if (!ramdisk_execute_command)ramdisk_execute_command = "/init";if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {ramdisk_execute_command = NULL;prepare_namespace();}/* * Ok, we have completed the initial bootup, and * we're essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. * 进入用户空间,执行用户空间代码 */init_post();return 0;}
在kernel_init函数中调用__initcall_start到__initcall_end之间保存的函数进行驱动模块初始化,然后直接调用init_post()函数进入用户空间,执行Init 进程代码。

static noinline int init_post(void){/* need to finish all async __init code before freeing the memory */async_synchronize_full();free_initmem();mark_rodata_ro();system_state = SYSTEM_RUNNING;numa_default_policy();current->signal->flags |= SIGNAL_UNKILLABLE;    //如果ramdisk_execute_command不为空,ramdisk_execute_command下的Init程序if (ramdisk_execute_command) {run_init_process(ramdisk_execute_command);printk(KERN_WARNING "Failed to execute %s\n",ramdisk_execute_command);}    //如果execute_command不为空,execute_command下的Init程序if (execute_command) {run_init_process(execute_command);printk(KERN_WARNING "Failed to execute %s.  Attempting ""defaults...\n", execute_command);}//如果以上路径下都没有init程序,就从/sbin、/etc、/bin三个路径下寻找init程序,同时启动一个sh进程run_init_process("/sbin/init");run_init_process("/etc/init");run_init_process("/bin/init");run_init_process("/bin/sh");    //如果以上路径都没有找到init程序,调用内核panicpanic("No init found.  Try passing init= option to kernel. "      "See Linux Documentation/init.txt for guidance.");}
当根文件系统顶层目录中不存在init进程,或未指定启动选项"init="时,内核会到/sbin、/etc、/bin目录下查找init文件。如果在这些目录中仍未找到init文件,内核就会中止执行init进程,并引发Kernel Panic。run_init_process函数通过系统调用do_execve从内核空间跳转到用户空间,并且执行用户空间的Init程序的入口函数。

static void run_init_process(const char *init_filename){argv_init[0] = init_filename;kernel_execve(init_filename, argv_init, envp_init);}
这里就介绍完了内核启动流程,run_init_process函数的将执行Init程序的入口函数,Init的入口函数位于/system/core/init/init.c

Init进程源码分析

Android的init进程主要功能:
1)、分析init.rc启动脚本文件,根据文件内容执行相应的功能;
2)、当一些关键进程死亡时,重启该进程;
3)、提供Android系统的属性服务;

int main(int argc, char **argv){    int fd_count = 0;    struct pollfd ufds[4];    char *tmpdev;    char* debuggable;    char tmp[32];    int property_set_fd_init = 0;    int signal_fd_init = 0;    int keychord_fd_init = 0;    bool is_charger = false;    if (!strcmp(basename(argv[0]), "ueventd"))        return ueventd_main(argc, argv);    /* clear the umask */    umask(0);    //挂载tmpfs,devpts,proc,sysfs 4类文件系统    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);    /* indicate that booting is in progress to background fw loaders, etc */    close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000));    //屏蔽标准的输入输出,即标准的输入输出定向到NULL设备。    open_devnull_stdio();    // log 初始化    klog_init();    // 属性存储空间初始化    property_init();    //读取机器硬件名称    get_hardware_name(hardware, &revision);    //设置基本属性    process_kernel_cmdline();#ifdef HAVE_SELINUX    INFO("loading selinux policy\n");    selinux_load_policy();#endif    //判断当前启动模式    is_charger = !strcmp(bootmode, "charger");        INFO("property init\n");    if (!is_charger)//读取默认的属性文件        property_load_boot_defaults();    //解析init.rc文件    INFO("reading config file\n");    init_parse_config_file("/init.rc");    //将early-init动作添加到链表action_queue中     action_for_each_trigger("early-init", action_add_queue_tail);    //创建wait_for_coldboot_done 动作并添加到链表action_queue和action_list中    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");//创建keychord_init动作并添加到链表action_queue和action_list中    queue_builtin_action(keychord_init_action, "keychord_init");//创建console_init动作并添加到链表action_queue和action_list中    queue_builtin_action(console_init_action, "console_init");    //将init动作添加到链表action_queue中    action_for_each_trigger("init", action_add_queue_tail);//将early-fs动作添加到链表action_queue中    action_for_each_trigger("early-fs", action_add_queue_tail);//将fs动作添加到链表action_queue中    action_for_each_trigger("fs", action_add_queue_tail);//将post-fs动作添加到链表action_queue中action_for_each_trigger("post-fs", action_add_queue_tail);    //非充电模式下,将post-fs-data动作添加到链表action_queue中    if (!is_charger) {        action_for_each_trigger("post-fs-data", action_add_queue_tail);    }    //创建property_service_init动作并添加到链表action_queue和action_list中    queue_builtin_action(property_service_init_action, "property_service_init");//创建signal_init动作并添加到链表action_queue和action_list中    queue_builtin_action(signal_init_action, "signal_init");//创建check_startup动作并添加到链表action_queue和action_list中    queue_builtin_action(check_startup_action, "check_startup");    if (!strcmp(bootmode, "alarm")) {        action_for_each_trigger("alarm", action_add_queue_tail);    }    if (is_charger) {//充电模式下,将charger动作添加到链表action_queue中        action_for_each_trigger("charger", action_add_queue_tail);    } else {//非充电模式下,将early-boot、boot动作添加到链表action_queue中        action_for_each_trigger("early-boot", action_add_queue_tail);        action_for_each_trigger("boot", action_add_queue_tail);    }    //创建queue_property_triggers动作并添加到链表action_queue和action_list中    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");#if BOOTCHART    //如果BOOTCHART宏定义了,创建bootchart_init动作并添加到链表action_queue和action_list中    queue_builtin_action(bootchart_init_action, "bootchart_init");#endif    for(;;) {        int nr, i, timeout = -1;        //按序执行action_queue里的action        execute_one_command();//重启一些关键进程        restart_processes();        //添加事件句柄到句柄次        if (!property_set_fd_init && get_property_set_fd() > 0) {            ufds[fd_count].fd = get_property_set_fd();            ufds[fd_count].events = POLLIN;            ufds[fd_count].revents = 0;            fd_count++;            property_set_fd_init = 1;        }        if (!signal_fd_init && get_signal_fd() > 0) {            ufds[fd_count].fd = get_signal_fd();            ufds[fd_count].events = POLLIN;            ufds[fd_count].revents = 0;            fd_count++;            signal_fd_init = 1;        }        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;        }        if (!action_queue_empty() || cur_action)            timeout = 0;#if BOOTCHART        if (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        //监控句柄池中的事件        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();            }        }    }    return 0;}

文件系统简介

tmpfs文件系统

    tmpfs是一种虚拟内存文件系统,因此它会将所有的文件存储在虚拟内存中,并且tmpfs下的所有内容均为临时性的内容,如果你将tmpfs文件系统卸载后,那么其下的所有的内容将不复存在。tmpfs是一个独立的文件系统,不是块设备,只要挂接,立即就可以使用。

devpts文件系统   

    devpts文件系统为伪终端提供了一个标准接口,它的标准挂接点是/dev/pts。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态的创建一个新的pty设备文件。

proc文件系统

    proc文件系统是一个非常重要的虚拟文件系统,它可以看作是内核内部数据结构的接口,通过它我们可以获得系统的信息,同时也能够在运行时修改特定的内核参数。

sysfs文件系统

    与proc文件系统类似,sysfs文件系统也是一个不占有任何磁盘空间的虚拟文件系统。它通常被挂接在/sys目录下。sysfs文件系统是Linux2.6内核引入的,它把连接在系统上的设备和总线组织成为一个分级的文件,使得它们可以在用户空间存取。

屏蔽标准的输入输出

void open_devnull_stdio(void){    int fd;//创建一个字符专用文件/dev/__null__     static const char *name = "/dev/__null__";    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {        //获取/dev/__null__的文件描述符,并输出该文件        fd = open(name, O_RDWR);        unlink(name);        if (fd >= 0) {    //将与进程相关的标准输入(0),标准输出(1),标准错误输出(2),均定向到NULL设备            dup2(fd, 0);            dup2(fd, 1);            dup2(fd, 2);            if (fd > 2) {                close(fd);            }            return;        }    }    exit(1);}
将标准输入输出,错误输出重定向到/dev/_null_设备中

初始化内核log系统

void klog_init(void){    static const char *name = "/dev/__kmsg__";//创建/dev/__kmsg__设备节点    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {        klog_fd = open(name, O_WRONLY);//当进程在进行exec系统调用时,要确保log_fd是关闭的        fcntl(klog_fd, F_SETFD, FD_CLOEXEC);        unlink(name);    }}

属性存储空间初始化

void property_init(void){    init_property_area();}
关于Android的属性系统,请查看Android 系统属性SystemProperty分析一文,在这篇文章中详细分析了Android的属性系统。

读取机器硬件名称

从/proc/cpuinfo中获取“Hardware”字段信息写入<hw>;“Reversion” 字段信息写入<reversion>

void get_hardware_name(char *hardware, unsigned int *revision){    char data[1024];    int fd, n;    char *x, *hw, *rev;    /* Hardware string was provided on kernel command line */    if (hardware[0])        return;    //打开/proc/cpuinfo文件    fd = open("/proc/cpuinfo", O_RDONLY);    if (fd < 0) return;    //读取/proc/cpuinfo文件内容    n = read(fd, data, 1023);    close(fd);    if (n < 0) return;    data[n] = 0;    hw = strstr(data, "\nHardware");    rev = strstr(data, "\nRevision");    if (hw) {        x = strstr(hw, ": ");        if (x) {            x += 2;            n = 0;            while (*x && *x != '\n') {                if (!isspace(*x))                    hardware[n++] = tolower(*x);                x++;                if (n == 31) break;            }            hardware[n] = 0;        }    }    if (rev) {        x = strstr(rev, ": ");        if (x) {            *revision = strtoul(x + 2, 0, 16);        }    }}
get_hardware_name函数从/proc/cpuinfo文件中读取硬件名称等信息,/proc/cpuinfo文件内容如下:

Processor: ARMv7 Processor rev 1 (v7l)BogoMIPS: 1024.00Features: swp half thumb fastmult vfp edsp thumbee neon vfpv3 CPU implementer: 0x41CPU architecture: 7CPU variant: 0x0CPU part: 0xc05CPU revision: 1Hardware: sc7710gRevision: 0000Serial: 0000000000000000

设置命令行参数属性

static void process_kernel_cmdline(void){    /* don't expose the raw commandline to nonpriv processes */    chmod("/proc/cmdline", 0440);    /* first pass does the common stuff, and finds if we are in qemu.     * second pass is only necessary for qemu to export all kernel params     * as props.     */    import_kernel_cmdline(0, import_kernel_nv);    if (qemu[0])        import_kernel_cmdline(1, import_kernel_nv);    /* now propogate the info given on command line to internal variables     * used by init as well as the current required properties     */    export_kernel_boot_props();}
process_kernel_cmdline函数首先修改/proc/cmdline文件权限,然后调用import_kernel_cmdline函数来读取/proc/cmdline文件的内容,并查找格式为:<key> = <value> 的字串,调用import_kernel_nv函数来设置属性。函数export_kernel_boot_props()用于设置内核启动时需要的属性。

void import_kernel_cmdline(int in_qemu,void (*import_kernel_nv)(char *name, int in_qemu)){    char cmdline[1024];    char *ptr;    int fd;    //打开并读取/proc/cmdline文件    fd = open("/proc/cmdline", O_RDONLY);    if (fd >= 0) {        int n = read(fd, cmdline, 1023);        if (n < 0) n = 0;        /* get rid of trailing newline, it happens */        if (n > 0 && cmdline[n-1] == '\n') n--;        cmdline[n] = 0;        close(fd);    } else {        cmdline[0] = 0;    }        ptr = cmdline;    while (ptr && *ptr) {        char *x = strchr(ptr, ' ');        if (x != 0) *x++ = 0;//回调import_kernel_nv函数,in_qemu =0        import_kernel_nv(ptr, in_qemu);        ptr = x;    }}
/proc/cmdline文件内容如下:

initrd=0x4c00000,0x1118e8 lpj=3350528 apv="sp7710ga-userdebug 4.1.2 JZO54K W13.23.2-010544 test-keys" mem=256M init=/init mtdparts=sprd-nand:256k(spl),512k(2ndbl),256k(params),512k(vmjaluna),10m(modem),3840k(fixnv),3840k(backupfixnv),5120k(dsp),3840k(runtimenv),10m(boot),10m(recovery),260m(system),160m(userdata),20m(cache),256k(misc),1m(boot_logo),1m(fastboot_logo),3840k(productinfo),512k(kpanic),15m(firmware) console=null  lcd_id=ID18 ram=256M
static void import_kernel_nv(char *name, int for_emulator){    char *value = strchr(name, '=');    int name_len = strlen(name);    if (value == 0) return;    *value++ = 0;    if (name_len == 0) return;#ifdef HAVE_SELINUX    if (!strcmp(name,"enforcing")) {        selinux_enforcing = atoi(value);    } else if (!strcmp(name,"selinux")) {        selinux_enabled = atoi(value);    }#endif    //判断是否为模拟器    if (for_emulator) {        /* in the emulator, export any kernel option with the         * ro.kernel. prefix */        char buff[PROP_NAME_MAX];        int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );        if (len < (int)sizeof(buff))            property_set( buff, value );        return;    }    //如果/proc/cmdline文件中有qemu关键字    if (!strcmp(name,"qemu")) {        strlcpy(qemu, value, sizeof(qemu));//如果/proc/cmdline文件中有以androidboot.开头的关键字    } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {        const char *boot_prop_name = name + 12;        char prop[PROP_NAME_MAX];        int cnt;        //格式化为ro.boot.xx 属性        cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);        if (cnt < PROP_NAME_MAX)            property_set(prop, value);    }}
最后调用函数export_kernel_boot_props设置内核启动属性

static void export_kernel_boot_props(void){    char tmp[PROP_VALUE_MAX];    const char *pval;    unsigned i;//属性表    struct {        const char *src_prop;        const char *dest_prop;        const char *def_val;    } prop_map[] = {        { "ro.boot.serialno", "ro.serialno", "", },        { "ro.boot.mode", "ro.bootmode", "unknown", },        { "ro.boot.baseband", "ro.baseband", "unknown", },        { "ro.boot.bootloader", "ro.bootloader", "unknown", },    };    //循环读取ro.boot.xxx属性值,并设置ro.xxx属性    for (i = 0; i < ARRAY_SIZE(prop_map); i++) {        pval = property_get(prop_map[i].src_prop);        property_set(prop_map[i].dest_prop, pval ?: prop_map[i].def_val);    }    //读取ro.boot.console属性值    pval = property_get("ro.boot.console");    if (pval)        strlcpy(console, pval, sizeof(console));    //读取ro.bootmode属性值    strlcpy(bootmode, property_get("ro.bootmode"), sizeof(bootmode));    //读取ro.boot.hardware属性值    pval = property_get("ro.boot.hardware");    if (pval)        strlcpy(hardware, pval, sizeof(hardware));    //设置ro.hardware属性    property_set("ro.hardware", hardware);    //设置ro.revision属性    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);    property_set("ro.revision", tmp);    //设置ro.factorytest属性    if (!strcmp(bootmode,"factory"))        property_set("ro.factorytest", "1");    else if (!strcmp(bootmode,"factory2"))        property_set("ro.factorytest", "2");    else        property_set("ro.factorytest", "0");}

init.rc 文件解析

init_parse_config_file(const char *fn){    char *data;    //读取/init.rc文件内容    data = read_file(fn, 0);    if (!data) return -1;//解析读取到的文件内容    parse_config(fn, data);    DUMP();    return 0;}
函数首先调用read_file函数将init.rc文件的内容读取保存到data中,在调用parse_config对其进行解析
void *read_file(const char *fn, unsigned *_sz){    char *data;    int sz;    int fd;    struct stat sb;    data = 0;//打开/init.rc文件    fd = open(fn, O_RDONLY);    if(fd < 0) return 0;    // for security reasons, disallow world-writable    // or group-writable files    if (fstat(fd, &sb) < 0) {        ERROR("fstat failed for '%s'\n", fn);        goto oops;    }    if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) {        ERROR("skipping insecure file '%s'\n", fn);        goto oops;    }    //将文件指针移到文件尾部,得到文件内容长度    sz = lseek(fd, 0, SEEK_END);    if(sz < 0) goto oops;    if(lseek(fd, 0, SEEK_SET) != 0) goto oops;    //分配buffer    data = (char*) malloc(sz + 2);    if(data == 0) goto oops;    //读取文件    if(read(fd, data, sz) != sz) goto oops;    close(fd);    data[sz] = '\n';    data[sz+1] = 0;    if(_sz) *_sz = sz;    return data;oops:    close(fd);    if(data != 0) free(data);    return 0;}

init.rc文件语法介绍

在Android根文件系统下存在多个.rc文件,该文件为Android启动配置脚本文件,文件内容如下:

# Copyright (C) 2012 The Android Open Source Project## IMPORTANT: Do not create world writable files or directories.# This is a common source of Android security bugs.#import /init.${ro.hardware}.rcimport /init.usb.rcimport /init.trace.rcon early-init    # Set init and its forked children's oom_adj.    write /proc/1/oom_adj -16    start ueventd    mkdir /mnt 0775 root systemon init    sysclktz 0    loglevel 3# setup the global environment    export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin    export LD_LIBRARY_PATH /vendor/lib:/system/lib    export ANDROID_BOOTLOGO 1    export ANDROID_ROOT /system    export ANDROID_ASSETS /system/app    export ANDROID_DATA /data    export ASEC_MOUNTPOINT /mnt/asec    export LOOP_MOUNTPOINT /mnt/obb    export BOOTCLASSPATH /system/framework/core.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/framework2.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/apache-xml.jar# Backward compatibility    symlink /system/etc /etc    symlink /sys/kernel/debug /d# Right now vendor lives on the same filesystem as system,# but someday that may change.    symlink /system/vendor /vendor# Create cgroup mount point for cpu accounting    mkdir /acct    mount cgroup none /acct cpuacct    mkdir /acct/uid    mkdir /system    mkdir /data 0771 system system    mkdir /cache 0770 system cache    mkdir /runtimenv 0774 system system    mkdir /backupfixnv 0774 system system    mkdir /productinfo 0774 system system    mkdir /fixnv 0774 system system    mkdir /config 0500 root root# Create cgroup mount points for process groups    mkdir /dev/cpuctl    mount cgroup none /dev/cpuctl cpu    chown system system /dev/cpuctl    chown system system /dev/cpuctl/tasks    chmod 0660 /dev/cpuctl/tasks    write /dev/cpuctl/cpu.shares 1024    write /dev/cpuctl/cpu.rt_runtime_us 950000    write /dev/cpuctl/cpu.rt_period_us 1000000    mkdir /dev/cpuctl/apps    chown system system /dev/cpuctl/apps/tasks    chmod 0666 /dev/cpuctl/apps/tasks    write /dev/cpuctl/apps/cpu.shares 1024    write /dev/cpuctl/apps/cpu.rt_runtime_us 800000    write /dev/cpuctl/apps/cpu.rt_period_us 1000000on fs# mount mtd partitions    # Mount /system rw first to give the filesystem a chance to save a checkpoint    chmod 0744 /modem_control    start modem_control    mount yaffs2 mtd@system /system    mount yaffs2 mtd@system /system ro remount    mount yaffs2 mtd@userdata /data nosuid nodev    mount yaffs2 mtd@cache /cache nosuid nodevon post-fs    # once everything is setup, no need to modify /    mount rootfs rootfs / ro remount    mount yaffs2 mtd@fixnv /fixnv nosuid nodev no-checkpoint    chown system system /fixnv    chmod 0774 /fixnv    mount yaffs2 mtd@runtimenv /runtimenv nosuid nodev no-checkpoint    chown system system /runtimenv    chmod 0774 /runtimenv    # We chown/chmod /cache again so because mount is run as root + defaults    chown system cache /cache    chmod 0770 /cache    mount yaffs2 mtd@backupfixnv /backupfixnv nosuid nodev no-checkpoint    chown system system /backupfixnv    chmod 0774 /backupfixnv    mount yaffs2 mtd@productinfo /productinfo nosuid nodev no-checkpoint    chown system system /productinfo    chmod 0774 /productinfo    chmod 0660 /fixnv/fixnv.bin    chmod 0660 /backupfixnv/fixnv.bin    chmod 0660 /productinfo/productinfo.bin    chmod 0660 /productinfo/productinfobkup.bin    chown system system /fixnv/fixnv.bin    chown system system /backupfixnv/fixnv.bin    chown system system /productinfo/productinfo.bin    chown system system /productinfo/productinfobkup.bin    # This may have been created by the recovery system with odd permissions    chown system cache /cache/recovery    chmod 0770 /cache/recovery    #change permissions on vmallocinfo so we can grab it from bugreports    chown root log /proc/vmallocinfo    chmod 0440 /proc/vmallocinfo    #change permissions on kmsg & sysrq-trigger so bugreports can grab kthread stacks    chown root system /proc/kmsg    chmod 0440 /proc/kmsg    chown root system /proc/sysrq-trigger    chmod 0220 /proc/sysrq-trigger    # create the lost+found directories, so as to enforce our permissions    mkdir /cache/lost+found 0770 root rooton post-fs-data    # create basic filesystem structure    mkdir /data/misc 01771 system misc    mkdir /data/misc/bluetoothd 0770 bluetooth bluetooth    mkdir /data/misc/bluetooth 0770 system system    mkdir /data/misc/keystore 0700 keystore keystore    mkdir /data/misc/keychain 0771 system system    mkdir /data/misc/vpn 0770 system vpn    mkdir /data/misc/systemkeys 0700 system systemon boot# basic network init    ifup lo    hostname localhost    domainname localdomain# set RLIMIT_NICE to allow priorities from 19 to -20    setrlimit 13 40 40# Memory management.  Basic kernel parameters, and allow the high# level system server to be able to adjust the kernel OOM driver# parameters to match how it is managing things.    write /proc/sys/vm/overcommit_memory 1    write /proc/sys/vm/min_free_order_shift 4    chown root system /sys/module/lowmemorykiller/parameters/adj    # Tweak background writeout    write /proc/sys/vm/dirty_expire_centisecs 200    write /proc/sys/vm/dirty_background_ratio  5    class_start core    class_start mainon nonencrypted    class_start late_starton charger    class_start core    class_start chargeron alarm    insmod /system/lib/modules/ft5306_ts.ko    class_start core    start media    exec /bin/poweroff_alarmon property:vold.decrypt=trigger_reset_main    class_reset mainon property:vold.decrypt=trigger_load_persist_props    load_persist_propson property:vold.decrypt=trigger_post_fs_data    trigger post-fs-dataon property:vold.decrypt=trigger_restart_min_framework    class_start mainon property:vold.decrypt=trigger_restart_framework    class_start main    class_start late_starton property:vold.decrypt=trigger_shutdown_framework    class_reset late_start    class_reset main## Daemon processes to be run by init.##service ueventd /sbin/ueventd    class core    criticalservice console /system/bin/sh    class core    console    disabled    user shell    group logon property:ro.debuggable=1    start console# adbd is controlled via property triggers in init.<platform>.usb.rcservice adbd /sbin/adbd    class core    disabled# adbd on at boot in emulatoron property:ro.kernel.qemu=1    start adbd# This property trigger has added to imitiate the previous behavior of "adb root".# The adb gadget driver used to reset the USB bus when the adbd daemon exited,# and the host side adb relied on this behavior to force it to reconnect with the# new adbd instance after init relaunches it. So now we force the USB bus to reset# here when adbd sets the service.adb.root property to 1.  We also restart adbd here# rather than waiting for init to notice its death and restarting it so the timing# of USB resetting and adb restarting more closely matches the previous behavior.on property:service.adb.root=1    write /sys/class/android_usb/android0/enable 0    restart adbd    write /sys/class/android_usb/android0/enable 1service servicemanager /system/bin/servicemanager    class core    user system    group system    critical    onrestart restart zygote    onrestart restart media    onrestart restart surfaceflinger    onrestart restart drmservice vold /system/bin/vold    class core    socket vold stream 0660 root mount    ioprio be 2service netd /system/bin/netd    class main    socket netd stream 0660 root system    socket dnsproxyd stream 0660 root inet    socket mdns stream 0660 root systemservice debuggerd /system/bin/debuggerd    class main#service ril-daemon /system/bin/rild#    class main#    socket rild stream 660 root radio#    socket rild-debug stream 660 radio system#    user root#    group radio cache inet misc audio sdcard_r sdcard_rw logservice surfaceflinger /system/bin/surfaceflinger    class main    user system    group graphics    onrestart restart zygoteservice zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server    class main    socket zygote stream 660 root system    onrestart write /sys/android_power/request_state wake    onrestart write /sys/power/state on    onrestart restart media    onrestart restart netdservice bootanim /system/bin/bootanimation    class main    user graphics    group graphics    disabled    oneshotservice dbus /system/bin/dbus-daemon --system --nofork    class main    socket dbus stream 660 bluetooth bluetooth    user bluetooth    group bluetooth net_bt_adminservice bluetoothd /system/bin/bluetoothd -n    class main    socket bluetooth stream 660 bluetooth bluetooth    socket dbus_bluetooth stream 660 bluetooth bluetooth    # init.rc does not yet support applying capabilities, so run as root and    # let bluetoothd drop uid to bluetooth with the right linux capabilities    group bluetooth net_bt_admin misc    disabledservice installd /system/bin/installd    class main    socket installd stream 600 system systemservice flash_recovery /system/etc/install-recovery.sh    class main    oneshotservice racoon /system/bin/racoon    class main    socket racoon stream 600 system system    # IKE uses UDP port 500. Racoon will setuid to vpn after binding the port.    group vpn net_admin inet    disabled    oneshotservice mtpd /system/bin/mtpd    class main    socket mtpd stream 600 system system    user vpn    group vpn net_admin inet net_raw    disabled    oneshotservice keystore /system/bin/keystore /data/misc/keystore    class main    user keystore    group keystore drmrpc    socket keystore stream 666
init.rc是一个可配置的初始化文件,通常定制厂商可以配置额外的初始化配置,如果关键字中有空格,处理方法类似于C语言,使用/表示转义,使用“”防止关键字被断开,另外注意/在末尾表示换行,由 # (前面允许有空格)开始的行都是注释行。init.rc包含4种状态类别:Actions/Commands/Services/Options。当声明一个service或者action的时候,它将隐式声明一个section,它之后跟随的command或者option都将属于这个section,action和service不能重名,否则忽略为error。

Action

actions就是在某种条件下触发一系列的命令,通常有一个trigger,形式如:  

on <trigger>
      <command>
      <command>

trigger主要包括:

boot 当/init.conf加载完毕时
<name>=<value> 当<name>被设置为<value>时
device-added-<path> 设备<path>被添加时
device-removed-<path> 设备<path>被移除时
service-exited-<name> 服务<name>退出时

Service

service就是要启动的本地服务进程

service <name> <pathname> [ <argument> ]*
      <option>
     <option>

Option

option是service的修饰词,由它来指定何时并且如何启动Services程序,主要包括:
     critical  表示如果服务在4分钟内存在多于4次,则系统重启到recovery mode
     disabled   表示服务不会自动启动,需要手动调用名字启动
     setEnv <name> <value>  设置启动环境变量
     socket <name> <type> <permission> [<user> [<group>]] 开启一个unix域的socket,名字为/dev/socket/<name> , <type>只能是dgram或者stream,<user>和<group>默认为0
     user <username> 表示将用户切换为<username>,用户名已经定义好了,只能是system/root
     group <groupname>  表示将组切换为<groupname>
     oneshot 表示这个service只启动一次
     class <name> 指定一个要启动的类,这个类中如果有多个service,将会被同时启动。默认的class将会是“default”
     onrestart  在重启时执行一条命令

Command

comand主要包括:

 exec <path> [ <argument> ]*执行一个<path>指定的程序
 export <name> <value> 设置一个全局变量
 ifup <interface> 使网络接口<interface>连接
 import <filename> 引入其他的配置文件
 hostname <name> 设置主机名
 chdir <directory> 切换工作目录
 chmod <octal-mode> <path> 设置访问权限
 chown <owner> <group> <path> 设置用户和组
 chroot <directory> 设置根目录
 class_start <serviceclass> 启动类中的service
 class_stop <serviceclass> 停止类中的service
 domainname <name> 设置域名
 insmod <path> 安装模块
 mkdir <path> [mode] [owner] [group] 创建一个目录,并可以指定权限,用户和组
 mount <type> <device> <dir> [ <mountoption> ]* 加载指定设备到目录下<mountoption> 包括"ro", "rw", "remount", "noatime"
 setprop <name> <value> 设置系统属性
 setrlimit <resource> <cur> <max> 设置资源访问权限
 start <service> 开启服务
 stop <service> 停止服务
 symlink <target> <path> 创建一个动态链接
 sysclktz <mins_west_of_gmt> 设置系统时钟
 trigger <event> 触发事件
 write <path> <string> [ <string> ]* 向<path>路径的文件写入多个<string>

Properties(属性)

Init更新一些系统属性以提供对正在发生的事件的监控能力:
       init.action 此属性值为正在被执行的action的名字,如果没有则为""。
       init.command  此属性值为正在被执行的command的名字,如果没有则为""。
       init.svc.<name> 名为<name>的service的状态("stopped"(停止), "running"(运行), "restarting"(重启))


在默认情况下,程序在被init执行时会将标准输出和标准错误都重定向到/dev/null(丢弃)。若你想要获得调试信息,你可以通过Andoird系统中的logwrapper程序执行你的程序。它会将标准输出/标准错误都重定向到Android日志系统(通过logcat访问)。
例如:
    service akmd /system/bin/logwrapper /sbin/akmd

init.rc解析过程

1. 扫描init.rc中的token
    找到其中的 文件结束EOF/文本TEXT/新行NEWLINE,其中的空格‘ ’、‘\t’、‘\r’会被忽略,#开头的行也被忽略掉;而对于TEXT,空格‘ ’、‘\t’、‘\r’、‘\n’都是TEXT的结束标志。
2. 对每一个TEXT token,都加入到args[]数组中
3. 当遇到新一行(‘\n’)的时候,用args[0]通过lookup_keyword()检索匹配关键字;

   1) 对Section(on和service),调用parse_new_section() 解析:
     - 对on section,调用parse_action(),并设置解析函数parse_line为parse_line_action()
     - 对service section,调用parse_service(),并设置解析函数parse_line为parse_line_service()
   2) 对其他关键字的行(非on或service开头的地方,也就是没有切换section)调用parse_line()
     - 对于on section内的命令行,调用parse_line_action()解析;
     - 对于service section内的命令行,调用parse_line_service()解析。

Token的定义

#define T_EOF 0#define T_TEXT 1#define T_NEWLINE 2
 解析过程中的双向循环链表的使用,android用到了一个非常巧妙的链表实现方法,一般情况下如果链表的节点是一个单独的数据结构的话,那么针对不同的数据结构,都需要定义不同链表操作。而在初始化过程中使用到的链表则解决了这个问题,它将链表的节点定义为了一个非常精简的结构,只包含前向和后向指针,那么在定义不同的数据结构时,只需要将链表节点嵌入到数据结构中即可。链表节点定义如下:

struct listnode{struct listnode *next;struct listnode *prev;};
对于Action数据结构为例:
struct action {/* node in list of all actions */struct listnode alist;/* node in the queue of pending actions */struct listnode qlist;/* node in list of actions for a trigger */struct listnode tlist;unsigned hash;const char *name;   struct listnode commands;struct command *current;};
这样的话,所有的链表的基本操作,例如插入,删除等只会针对listnode进行操作,而不是针对特定的数据结构,链表的实现得到了统一,即精简了代码,又提高了效率。 但是这样的链表实现,存在一个问题,链表节点listnode中只有前向和后向指针,并且前向和后向指针均指向listnode,那么我们通过什么方式来访问数据结构action的内容呢?我们使用offsetof宏来计算链表节点在数据结构中的偏移量,从而计算数据结构实例的地址。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#define node_to_item(node, container, member) \    (container *) (((char*) (node)) - offsetof(container, member))
这种链表的优点:(1)所有链表基本操作都是基于listnode指针的,因此添加类型时,不需要重复写链表基本操作函数(2)一个container数据结构可以含有多个listnode成员,这样就可以同时挂到多个不同的链表中。

Service数据结构定义:

struct service {        /* list of all services */    struct listnode slist;    const char *name;    const char *classname;    unsigned flags;    pid_t pid;    time_t time_started;    /* time of last start */    time_t time_crashed;    /* first crash within inspection window */    int nr_crashed;         /* number of times crashed within window */        uid_t uid;    gid_t gid;    gid_t supp_gids[NR_SVC_SUPP_GIDS];    size_t nr_supp_gids;#ifdef HAVE_SELINUX    char *seclabel;#endif    struct socketinfo *sockets;    struct svcenvinfo *envvars;    struct action onrestart;  /* Actions to execute on restart. */      /* keycodes for triggering this service via /dev/keychord */    int *keycodes;    int nkeycodes;    int keychord_id;    int ioprio_class;    int ioprio_pri;    int nargs;    /* "MUST BE AT THE END OF THE STRUCT" */    char *args[1];};
对于某些Service可能采用Socket来实现进程间通信,因此该Service需要创建多个socket,比如:

service wril-daemon /system/bin/rild_sp -l /system/lib/libreference-ril_sp.so -m w -n 0    class core    socket rild stream 660 root radio    socket rild-debug stream 660 radio system    disabled    user root    group radio cache inet misc audio sdcard_rw log
该service需要创建rild 和rild-debug socket,这些socket的信息在解析init.rc文件时保存在Service的成员变量sockets链表中。socketinfo 数据结构定义如下:

struct socketinfo {    struct socketinfo *next;    const char *name;    const char *type;    uid_t uid;    gid_t gid;    int perm;};

某些Service在运行时需要设置环境变量,这些环境变量被保存在Service的成员变量envvars链表中,svcenvinfo 数据结构定义如下:

struct svcenvinfo {    struct svcenvinfo *next;    const char *name;    const char *value;};
在每个Action或Service下可能需要执行多个Command,关于command数据结构定义如下:

struct command{        /* list of commands in an action */    struct listnode clist;    int (*func)(int nargs, char **args);    int nargs;    char *args[1];};

在Init进程中分别使用了3个链表来存储init.rc文件中的Action和Service:

static list_declare(service_list);static list_declare(action_list);static list_declare(action_queue);
service_list链表用于保存init.rc文件中的Service配置信息,service_list链表的存储如下图所示:

service_list 链表保存init.rc文件中的所有service,每个service下的所有socket信息保存在该service的成员变量sockets链表中,当该service重启时,需要重启某些服务,对于重启某些服务的命令以Action的形式保存在Service的成员变量onrestart链表中,而真正执行的命令却存放在该Action下的commands链表里。

action_list用于保存init.rc文件中的所有以on开头的section,action_list链表的存储如下图所示:

从上图可以看出action_queue和action_list都是用来保存所有的Action,它们之间的区别是action_list用于保存从init.rc中解析出来的所有Action,而action_queue却是用于保存待执行的Action,action_queue是一个待执行队列。

在system\core\init\keywords.h文件中定义了解析关键字,其内容如下:

#ifndef KEYWORDint do_chroot(int nargs, char **args);int do_chdir(int nargs, char **args);int do_class_start(int nargs, char **args);int do_class_stop(int nargs, char **args);int do_class_reset(int nargs, char **args);int do_domainname(int nargs, char **args);int do_exec(int nargs, char **args);int do_export(int nargs, char **args);int do_hostname(int nargs, char **args);int do_ifup(int nargs, char **args);int do_insmod(int nargs, char **args);int do_mkdir(int nargs, char **args);int do_mount_all(int nargs, char **args);int do_mount(int nargs, char **args);int do_restart(int nargs, char **args);int do_restorecon(int nargs, char **args);int do_rm(int nargs, char **args);int do_rmdir(int nargs, char **args);int do_setcon(int nargs, char **args);int do_setenforce(int nargs, char **args);int do_setkey(int nargs, char **args);int do_setprop(int nargs, char **args);int do_setrlimit(int nargs, char **args);int do_setsebool(int nargs, char **args);int do_start(int nargs, char **args);int do_stop(int nargs, char **args);int do_trigger(int nargs, char **args);int do_symlink(int nargs, char **args);int do_sysclktz(int nargs, char **args);int do_write(int nargs, char **args);int do_copy(int nargs, char **args);int do_chown(int nargs, char **args);int do_chmod(int nargs, char **args);int do_loglevel(int nargs, char **args);int do_load_persist_props(int nargs, char **args);int do_pipe(int nargs, char **args);int do_wait(int nargs, char **args);#define __MAKE_KEYWORD_ENUM__#define KEYWORD(symbol, flags, nargs, func) K_##symbol,enum {    K_UNKNOWN,#endif    KEYWORD(capability,  OPTION,  0, 0)    KEYWORD(chdir,       COMMAND, 1, do_chdir)    KEYWORD(chroot,      COMMAND, 1, do_chroot)    KEYWORD(class,       OPTION,  0, 0)    KEYWORD(class_start, COMMAND, 1, do_class_start)    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)    KEYWORD(class_reset, COMMAND, 1, do_class_reset)    KEYWORD(console,     OPTION,  0, 0)    KEYWORD(critical,    OPTION,  0, 0)    KEYWORD(disabled,    OPTION,  0, 0)    KEYWORD(domainname,  COMMAND, 1, do_domainname)    KEYWORD(exec,        COMMAND, 1, do_exec)    KEYWORD(export,      COMMAND, 2, do_export)    KEYWORD(group,       OPTION,  0, 0)    KEYWORD(hostname,    COMMAND, 1, do_hostname)    KEYWORD(ifup,        COMMAND, 1, do_ifup)    KEYWORD(insmod,      COMMAND, 1, do_insmod)    KEYWORD(import,      SECTION, 1, 0)    KEYWORD(keycodes,    OPTION,  0, 0)    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)    KEYWORD(mount,       COMMAND, 3, do_mount)    KEYWORD(on,          SECTION, 0, 0)    KEYWORD(oneshot,     OPTION,  0, 0)    KEYWORD(onrestart,   OPTION,  0, 0)    KEYWORD(restart,     COMMAND, 1, do_restart)    KEYWORD(restorecon,  COMMAND, 1, do_restorecon)    KEYWORD(rm,          COMMAND, 1, do_rm)    KEYWORD(rmdir,       COMMAND, 1, do_rmdir)    KEYWORD(seclabel,    OPTION,  0, 0)    KEYWORD(service,     SECTION, 0, 0)    KEYWORD(setcon,      COMMAND, 1, do_setcon)    KEYWORD(setenforce,  COMMAND, 1, do_setenforce)    KEYWORD(setenv,      OPTION,  2, 0)    KEYWORD(setkey,      COMMAND, 0, do_setkey)    KEYWORD(setprop,     COMMAND, 2, do_setprop)    KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)    KEYWORD(setsebool,   COMMAND, 1, do_setsebool)    KEYWORD(socket,      OPTION,  0, 0)    KEYWORD(start,       COMMAND, 1, do_start)    KEYWORD(stop,        COMMAND, 1, do_stop)    KEYWORD(trigger,     COMMAND, 1, do_trigger)    KEYWORD(symlink,     COMMAND, 1, do_symlink)    KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)    KEYWORD(user,        OPTION,  0, 0)    KEYWORD(wait,        COMMAND, 1, do_wait)    KEYWORD(write,       COMMAND, 2, do_write)    KEYWORD(copy,        COMMAND, 2, do_copy)    KEYWORD(chown,       COMMAND, 2, do_chown)    KEYWORD(chmod,       COMMAND, 2, do_chmod)    KEYWORD(loglevel,    COMMAND, 1, do_loglevel)    KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)    KEYWORD(pipe,        COMMAND, 2, do_pipe)    KEYWORD(ioprio,      OPTION,  0, 0)#ifdef __MAKE_KEYWORD_ENUM__    KEYWORD_COUNT,};#undef __MAKE_KEYWORD_ENUM__#undef KEYWORD#endif
宏KEYWORD并未定义,因此将定义宏__MAKE_KEYWORD_ENUM__ 及KEYWORD,KEYWORD宏定义如下:

#define KEYWORD(symbol, flags, nargs, func) K_##symbol,
同时定义了枚举:

enum {    K_UNKNOWN,    KEYWORD(capability,  OPTION,  0, 0)    KEYWORD(chdir,       COMMAND, 1, do_chdir)    KEYWORD(chroot,      COMMAND, 1, do_chroot)    KEYWORD(class,       OPTION,  0, 0)    KEYWORD(class_start, COMMAND, 1, do_class_start)    KEYWORD(class_stop,  COMMAND, 1, do_class_stop)    KEYWORD(class_reset, COMMAND, 1, do_class_reset)    KEYWORD(console,     OPTION,  0, 0)    KEYWORD(critical,    OPTION,  0, 0)    KEYWORD(disabled,    OPTION,  0, 0)    KEYWORD(domainname,  COMMAND, 1, do_domainname)    KEYWORD(exec,        COMMAND, 1, do_exec)    KEYWORD(export,      COMMAND, 2, do_export)    KEYWORD(group,       OPTION,  0, 0)    KEYWORD(hostname,    COMMAND, 1, do_hostname)    KEYWORD(ifup,        COMMAND, 1, do_ifup)    KEYWORD(insmod,      COMMAND, 1, do_insmod)    KEYWORD(import,      SECTION, 1, 0)    KEYWORD(keycodes,    OPTION,  0, 0)    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)    KEYWORD(mount,       COMMAND, 3, do_mount)    KEYWORD(on,          SECTION, 0, 0)    KEYWORD(oneshot,     OPTION,  0, 0)    KEYWORD(onrestart,   OPTION,  0, 0)    KEYWORD(restart,     COMMAND, 1, do_restart)    KEYWORD(restorecon,  COMMAND, 1, do_restorecon)    KEYWORD(rm,          COMMAND, 1, do_rm)    KEYWORD(rmdir,       COMMAND, 1, do_rmdir)    KEYWORD(seclabel,    OPTION,  0, 0)    KEYWORD(service,     SECTION, 0, 0)    KEYWORD(setcon,      COMMAND, 1, do_setcon)    KEYWORD(setenforce,  COMMAND, 1, do_setenforce)    KEYWORD(setenv,      OPTION,  2, 0)    KEYWORD(setkey,      COMMAND, 0, do_setkey)    KEYWORD(setprop,     COMMAND, 2, do_setprop)    KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)    KEYWORD(setsebool,   COMMAND, 1, do_setsebool)    KEYWORD(socket,      OPTION,  0, 0)    KEYWORD(start,       COMMAND, 1, do_start)    KEYWORD(stop,        COMMAND, 1, do_stop)    KEYWORD(trigger,     COMMAND, 1, do_trigger)    KEYWORD(symlink,     COMMAND, 1, do_symlink)    KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)    KEYWORD(user,        OPTION,  0, 0)    KEYWORD(wait,        COMMAND, 1, do_wait)    KEYWORD(write,       COMMAND, 2, do_write)    KEYWORD(copy,        COMMAND, 2, do_copy)    KEYWORD(chown,       COMMAND, 2, do_chown)    KEYWORD(chmod,       COMMAND, 2, do_chmod)    KEYWORD(loglevel,    COMMAND, 1, do_loglevel)    KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)    KEYWORD(pipe,        COMMAND, 2, do_pipe)    KEYWORD(ioprio,      OPTION,  0, 0)    KEYWORD_COUNT,};
该枚举的通过宏展开后定义为:

enum {    K_UNKNOWN,K_capability,K_chdir,K_chroot,K_class,K_class_start,K_class_stop,K_class_reset,K_console,K_critical,K_disabled,K_domainname,K_exec,K_export,K_group,K_hostname,K_ifup,K_insmod,K_import,K_keycodes,K_mkdir,K_mount_all,K_mount,K_on,K_oneshot,K_onrestart,K_restart,K_restorecon,K_rm,K_rmdirK_seclabelK_serviceK_setconK_setenforceK_setenvK_setkeyK_setpropK_setrlimitK_setseboolK_socketK_startK_stopK_triggerK_symlinkK_sysclktzK_userK_waitK_writeK_copyK_chownK_chmodK_loglevelK_load_persist_propsK_pipeK_ioprio    KEYWORD_COUNT,};
该枚举的定义主要是为每个命令指定对应的序号。在keywords.h文件最后取消了宏__MAKE_KEYWORD_ENUM__ 及KEYWORD的定义,在system\core\init\init_parser.c文件中又重定义了KEYWORD宏:

#define KEYWORD(symbol, flags, nargs, func) \    [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
该宏的定义是为了给接下来定义的keyword_info这个关键字信息数组的赋值,keyword_info定义如下:

struct {    const char *name;    int (*func)(int nargs, char **args);    unsigned char nargs;    unsigned char flags;} keyword_info[KEYWORD_COUNT] = {    [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },#include "keywords.h"};
keyword_info数组元素是keywords.h文件中的内容,因为此时KEYWORD宏已经被定义了同时__MAKE_KEYWORD_ENUM__被取消定义,因此keywords.h文件内容此时变为:

KEYWORD(capability,  OPTION,  0, 0)KEYWORD(chdir,       COMMAND, 1, do_chdir)KEYWORD(chroot,      COMMAND, 1, do_chroot)KEYWORD(class,       OPTION,  0, 0)KEYWORD(class_start, COMMAND, 1, do_class_start)KEYWORD(class_stop,  COMMAND, 1, do_class_stop)KEYWORD(class_reset, COMMAND, 1, do_class_reset)KEYWORD(console,     OPTION,  0, 0)KEYWORD(critical,    OPTION,  0, 0)KEYWORD(disabled,    OPTION,  0, 0)KEYWORD(domainname,  COMMAND, 1, do_domainname)KEYWORD(exec,        COMMAND, 1, do_exec)KEYWORD(export,      COMMAND, 2, do_export)KEYWORD(group,       OPTION,  0, 0)KEYWORD(hostname,    COMMAND, 1, do_hostname)KEYWORD(ifup,        COMMAND, 1, do_ifup)KEYWORD(insmod,      COMMAND, 1, do_insmod)KEYWORD(import,      SECTION, 1, 0)KEYWORD(keycodes,    OPTION,  0, 0)KEYWORD(mkdir,       COMMAND, 1, do_mkdir)KEYWORD(mount_all,   COMMAND, 1, do_mount_all)KEYWORD(mount,       COMMAND, 3, do_mount)KEYWORD(on,          SECTION, 0, 0)KEYWORD(oneshot,     OPTION,  0, 0)KEYWORD(onrestart,   OPTION,  0, 0)KEYWORD(restart,     COMMAND, 1, do_restart)KEYWORD(restorecon,  COMMAND, 1, do_restorecon)KEYWORD(rm,          COMMAND, 1, do_rm)KEYWORD(rmdir,       COMMAND, 1, do_rmdir)KEYWORD(seclabel,    OPTION,  0, 0)KEYWORD(service,     SECTION, 0, 0)KEYWORD(setcon,      COMMAND, 1, do_setcon)KEYWORD(setenforce,  COMMAND, 1, do_setenforce)KEYWORD(setenv,      OPTION,  2, 0)KEYWORD(setkey,      COMMAND, 0, do_setkey)KEYWORD(setprop,     COMMAND, 2, do_setprop)KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)KEYWORD(setsebool,   COMMAND, 1, do_setsebool)KEYWORD(socket,      OPTION,  0, 0)KEYWORD(start,       COMMAND, 1, do_start)KEYWORD(stop,        COMMAND, 1, do_stop)KEYWORD(trigger,     COMMAND, 1, do_trigger)KEYWORD(symlink,     COMMAND, 1, do_symlink)KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)KEYWORD(user,        OPTION,  0, 0)KEYWORD(wait,        COMMAND, 1, do_wait)KEYWORD(write,       COMMAND, 2, do_write)KEYWORD(copy,        COMMAND, 2, do_copy)KEYWORD(chown,       COMMAND, 2, do_chown)KEYWORD(chmod,       COMMAND, 2, do_chmod)KEYWORD(loglevel,    COMMAND, 1, do_loglevel)KEYWORD(load_persist_props,    COMMAND, 0, do_load_persist_props)KEYWORD(pipe,        COMMAND, 2, do_pipe)KEYWORD(ioprio,      OPTION,  0, 0)

使用上述KEYWORD宏展开得到keyword_info数组内容如下:

[ K_capability] = { capability,   0,              1,  OPTION, },[ K_class] = { class,        0,              1,  OPTION, },[ K_console] = { console,      0,              1,  OPTION, },[ K_critical] = { critical,     0,              1,  OPTION, },[ K_group] = { group,        0,              1,  OPTION, },[ K_disabled] = { disabled,     0,              1,  OPTION, },[ K_keycodes] = { keycodes,     0,              1,  OPTION, },[ K_oneshot] = { oneshot,      0,              1,  OPTION, },[ K_onrestart] = { onrestart,    0,              1,  OPTION, },[ K_socket] = { socket,       0,              1,  OPTION, },[ K_setenv] = { setenv,       0,              3,  OPTION, },[ K_ioprio] = { ioprio,       0,              1,  OPTION, },[ K_user] = { user,         0,              1,  OPTION, },[ K_seclabel] = { seclabel,     0,              1,  OPTION, },[ K_service] = { service,      0,              1, SECTION, },[ K_on] = { on,           0,              1, SECTION, },[ K_import] = { import,       0,              2, SECTION, },[ K_chdir] = { chdir,        do_chdir,       2, COMMAND, },[ K_chroot] = { chroot,       do_chroot,      2, COMMAND, },[ K_class_start] = { class_start,  do_class_start, 2, COMMAND, },[ K_class_stop] = { class_stop,   do_class_stop,  2, COMMAND, },[ K_class_reset] = { class_reset,  do_class_reset, 2, COMMAND, },[ K_domainname] = { domainname,   do_domainname,  2, COMMAND, },[ K_exec] = { exec,         do_exec,        2, COMMAND, },[ K_export] = { export,       do_export,      3, COMMAND, },[ K_hostname] = { hostname,     do_hostname,    2, COMMAND, },[ K_ifup] = { ifup,         do_ifup,        2, COMMAND, },[ K_insmod] = { insmod,       do_insmod,      3, COMMAND, },[ K_mkdir] = { mkdir,        do_mkdir,       2, COMMAND, },[ K_mount_all] = { mount_all,    do_mount_all,   2, COMMAND, },[ K_mount] = { mount,        do_mount,       4, COMMAND, },[ K_restart] = { restart,      do_restart,     2, COMMAND, },[ K_restorecon] = { restorecon,   do_restorecon,  2, COMMAND, },[ K_rm] = { rm,           do_rm,          2, COMMAND, }[ K_rmdir] = { rmdir,        do_rmdir,       2, COMMAND, },[ K_setcon] = { setcon,       do_setcon,      2, COMMAND, },[ K_setenforce] = { setenforce,   do_setenforce,  2, COMMAND, },[ K_setkey] = { setkey,       do_setkey,      1, COMMAND, },[ K_setprop] = { setprop,      do_setprop,     3, COMMAND, },[ K_setrlimit] = { setrlimit,    do_setrlimit,   4, COMMAND, },[ K_setsebool] = { setsebool,    do_setsebool,   2, COMMAND, },[ K_start] = { start,        do_start,       2, COMMAND, },[ K_stop] = { stop,         do_stop,        2, COMMAND, },[ K_trigger] = { trigger,      do_trigger,     2, COMMAND, },[ K_symlink] = { symlink,      do_symlink,     2, COMMAND, },[ K_sysclktz] = { sysclktz,     do_sysclktz,    2, COMMAND, },[ K_wait] = { wait,         do_wait,        2, COMMAND, },[ K_write] = { write,        do_write,       3, COMMAND, },[ K_copy] = { copy,         do_copy,        3, COMMAND, },[ K_chown] = { chown,        do_chown,       3, COMMAND, },[ K_chmod] = { chmod,        do_chmod,       3, COMMAND, },[ K_loglevel] = { loglevel,     do_loglevel,    2, COMMAND, },[ K_load_persist_props] = { load_persist_props, do_load_persist_props,1, COMMAND, },[ K_pipe] = { pipe,         do_pipe,        3, COMMAND, },

了解了这些内容之后,我们开始分析init.rc文件的真正解析过程:

static void parse_config(const char *fn, char *s){    struct parse_state state;    struct listnode import_list;    struct listnode *node;    char *args[INIT_PARSER_MAXARGS];    int nargs;    nargs = 0;    state.filename = fn; //文件名称    state.line = 0; //统计文件行数    state.ptr = s; //文件内容    state.nexttoken = 0;    state.parse_line = parse_line_no_op; //解析函数指针    //初始化import_list链表,该链表用于保存通过import关键字引入的其他.rc文件    list_init(&import_list);    state.priv = &import_list;        for (;;) {//next_token函数用于扫描init.rc中的token        switch (next_token(&state)) {//文件结束EOF        case T_EOF:            state.parse_line(&state, 0, 0);            goto parser_done;//新行NEWLINE        case T_NEWLINE:            state.line++;             if (nargs) {//根据行头查找关键字类型                int kw = lookup_keyword(args[0]);//如果是SECTION类型,SECTION包括以关键字service,on,import开头的语句                if (kw_is(kw, SECTION)) {    //解析该行,此时parse_line指向的回调函数为parse_line_no_op,该函数什么也不做                    state.parse_line(&state, 0, 0);    //解析该SECTION                    parse_new_section(&state, kw, nargs, args);//如果不是SECTION类型,则调用parse_line指向的回调函数                } else {                    state.parse_line(&state, nargs, args);                }                nargs = 0;            }            break;    //文本TEXT        case T_TEXT:            if (nargs < INIT_PARSER_MAXARGS) {                args[nargs++] = state.text;            }            break;        }    }parser_done:     //init.rc 文件解析结束后,解析通过import关键字导入的.rc文件    list_for_each(node, &import_list) {  //从import_list链表中循环取出导入的.rc文件路径         struct import *import = node_to_item(node, struct import, list);         int ret;         INFO("importing '%s'", import->filename);         //读取并解析导入的.rc文件         ret = init_parse_config_file(import->filename);         if (ret)             ERROR("could not import file '%s' from '%s'\n",import->filename, fn);    }}
函数parse_config通过调用next_token函数来查找3个定义的token,当查找到T_NEWLINE  token时,使用lookup_keyword函数来判断关键字类型,如果属于SECTION类型,则调用parse_new_section函数进行解析,如果是其他类型,则调用parse_line指向的回调函数来解析。

在前面介绍了通过定义枚举来为每个命令分配类型,lookup_keyword函数通过比较命令名称来返回对应命令的类型,如下所示:

int lookup_keyword(const char *s){    switch (*s++) {    case 'c':    if (!strcmp(s, "opy")) return K_copy;        if (!strcmp(s, "apability")) return K_capability;        if (!strcmp(s, "hdir")) return K_chdir;        if (!strcmp(s, "hroot")) return K_chroot;        if (!strcmp(s, "lass")) return K_class;        if (!strcmp(s, "lass_start")) return K_class_start;        if (!strcmp(s, "lass_stop")) return K_class_stop;        if (!strcmp(s, "lass_reset")) return K_class_reset;        if (!strcmp(s, "onsole")) return K_console;        if (!strcmp(s, "hown")) return K_chown;        if (!strcmp(s, "hmod")) return K_chmod;        if (!strcmp(s, "ritical")) return K_critical;        break;    case 'd':        if (!strcmp(s, "isabled")) return K_disabled;        if (!strcmp(s, "omainname")) return K_domainname;        break;    case 'e':        if (!strcmp(s, "xec")) return K_exec;        if (!strcmp(s, "xport")) return K_export;        break;    case 'g':        if (!strcmp(s, "roup")) return K_group;        break;    case 'h':        if (!strcmp(s, "ostname")) return K_hostname;        break;    case 'i':        if (!strcmp(s, "oprio")) return K_ioprio;        if (!strcmp(s, "fup")) return K_ifup;        if (!strcmp(s, "nsmod")) return K_insmod;        if (!strcmp(s, "mport")) return K_import;        break;    case 'k':        if (!strcmp(s, "eycodes")) return K_keycodes;        break;    case 'l':        if (!strcmp(s, "oglevel")) return K_loglevel;        if (!strcmp(s, "oad_persist_props")) return K_load_persist_props;        break;    case 'm':        if (!strcmp(s, "kdir")) return K_mkdir;        if (!strcmp(s, "ount_all")) return K_mount_all;        if (!strcmp(s, "ount")) return K_mount;        break;    case 'o':        if (!strcmp(s, "n")) return K_on;        if (!strcmp(s, "neshot")) return K_oneshot;        if (!strcmp(s, "nrestart")) return K_onrestart;        break;    case 'r':        if (!strcmp(s, "estart")) return K_restart;        if (!strcmp(s, "estorecon")) return K_restorecon;        if (!strcmp(s, "mdir")) return K_rmdir;        if (!strcmp(s, "m")) return K_rm;        break;    case 's':        if (!strcmp(s, "eclabel")) return K_seclabel;        if (!strcmp(s, "ervice")) return K_service;        if (!strcmp(s, "etcon")) return K_setcon;        if (!strcmp(s, "etenforce")) return K_setenforce;        if (!strcmp(s, "etenv")) return K_setenv;        if (!strcmp(s, "etkey")) return K_setkey;        if (!strcmp(s, "etprop")) return K_setprop;        if (!strcmp(s, "etrlimit")) return K_setrlimit;        if (!strcmp(s, "etsebool")) return K_setsebool;        if (!strcmp(s, "ocket")) return K_socket;        if (!strcmp(s, "tart")) return K_start;        if (!strcmp(s, "top")) return K_stop;        if (!strcmp(s, "ymlink")) return K_symlink;        if (!strcmp(s, "ysclktz")) return K_sysclktz;        break;    case 't':        if (!strcmp(s, "rigger")) return K_trigger;        break;    case 'u':        if (!strcmp(s, "ser")) return K_user;        break;    case 'w':        if (!strcmp(s, "rite")) return K_write;        if (!strcmp(s, "ait")) return K_wait;        break;    case 'p':        if (!strcmp(s, "ipe")) return K_pipe;    }    return K_UNKNOWN;}
对于SECTION类型,又包括import、on、service三种,因此需要分别处理:

void parse_new_section(struct parse_state *state, int kw,                       int nargs, char **args){    printf("[ %s %s ]\n", args[0],nargs > 1 ? args[1] : "");    switch(kw) {//如果关键字是service,表示这是一条描述服务的语句,调用parse_service函数来解析该行,并将解析得到的service保存在state->context中,同时设置解析函数parse_line为parse_line_service()    case K_service:        state->context = parse_service(state, nargs, args);        if (state->context) {            state->parse_line = parse_line_service;            return;        }        break;//如果关键字是on,表示这是一条Action语句,调用parse_action函数来解析该行,并将解析得到的Action保存在state->context中,同时设置解析函数parse_line为parse_line_action()    case K_on:        state->context = parse_action(state, nargs, args);        if (state->context) {            state->parse_line = parse_line_action;            return;        }        break;//如果关键字是import,表示这是一条import语句,调用parse_import函数来解析该行,同时设置解析函数parse_line为parse_line_no_op()    case K_import:        parse_import(state, nargs, args);        break;    }    state->parse_line = parse_line_no_op;}
1.Service解析

static void *parse_service(struct parse_state *state, int nargs, char **args){    struct service *svc;//检查参数个数    if (nargs < 3) {        parse_error(state, "services must have a name and a program\n");        return 0;    }//检查参数名称的有效性    if (!valid_name(args[1])) {        parse_error(state, "invalid service name '%s'\n", args[1]);        return 0;    }    //从服务链表中查找该名称的服务以防止出现重复的服务    svc = service_find_by_name(args[1]);    if (svc) {        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);        return 0;    }    nargs -= 2;    //创建一个service    svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);    if (!svc) {        parse_error(state, "out of memory\n");        return 0;    }    svc->name = args[1];    svc->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);//将该服务添加到service_list链表中    list_add_tail(&service_list, &svc->slist);    return svc;}
2. Service 配置项解析

static void parse_line_service(struct parse_state *state, int nargs, char **args){//从state->context中取出已经解析生成的service    struct service *svc = state->context;    struct command *cmd;    int i, kw, kw_nargs;    if (nargs == 0) {        return;    }    svc->ioprio_class = IoSchedClass_NONE;//查找命令类型    kw = lookup_keyword(args[0]);    switch (kw) {//capability命令处理    case K_capability:        break;//class命令处理    case K_class:        if (nargs != 2) {            parse_error(state, "class option requires a classname\n");        } else {            svc->classname = args[1];        }        break;//console命令处理    case K_console:        svc->flags |= SVC_CONSOLE;        break;//disabled命令处理    case K_disabled:        svc->flags |= SVC_DISABLED;        svc->flags |= SVC_RC_DISABLED;        break;//ioprio命令处理    case K_ioprio:        if (nargs != 3) {            parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");        } else {            svc->ioprio_pri = strtoul(args[2], 0, 8);            if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {                parse_error(state, "priority value must be range 0 - 7\n");                break;            }            if (!strcmp(args[1], "rt")) {                svc->ioprio_class = IoSchedClass_RT;            } else if (!strcmp(args[1], "be")) {                svc->ioprio_class = IoSchedClass_BE;            } else if (!strcmp(args[1], "idle")) {                svc->ioprio_class = IoSchedClass_IDLE;            } else {                parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");            }        }        break;//group命令处理    case K_group:        if (nargs < 2) {            parse_error(state, "group option requires a group id\n");        } else if (nargs > NR_SVC_SUPP_GIDS + 2) {            parse_error(state, "group option accepts at most %d supp. groups\n",                        NR_SVC_SUPP_GIDS);        } else {            int n;            svc->gid = decode_uid(args[1]);            for (n = 2; n < nargs; n++) {                svc->supp_gids[n-2] = decode_uid(args[n]);            }            svc->nr_supp_gids = n - 2;        }        break;//keycodes命令处理,service命令组合键启动    case K_keycodes:        if (nargs < 2) {            parse_error(state, "keycodes option requires atleast one keycode\n");        } else {            svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));            if (!svc->keycodes) {                parse_error(state, "could not allocate keycodes\n");            } else {                svc->nkeycodes = nargs - 1;                for (i = 1; i < nargs; i++) {                    svc->keycodes[i - 1] = atoi(args[i]);                }            }        }        break;//oneshot命令处理    case K_oneshot:        svc->flags |= SVC_ONESHOT;        break;//onrestart命令处理    case K_onrestart:        nargs--;        args++;//onrestart restart zygote//查找onrestart后的参数类型        kw = lookup_keyword(args[0]);//如果不属于COMMAND类型,跳出不处理        if (!kw_is(kw, COMMAND)) {            parse_error(state, "invalid command '%s'\n", args[0]);            break;        }//读取该命令的参数个数        kw_nargs = kw_nargs(kw);//验证参数个数        if (nargs < kw_nargs) {            parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,                kw_nargs > 2 ? "arguments" : "argument");            break;        }        //创建一个command        cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);        cmd->func = kw_func(kw);        cmd->nargs = nargs;        memcpy(cmd->args, args, sizeof(char*) * nargs);//添加到svc->onrestart.commands链表中        list_add_tail(&svc->onrestart.commands, &cmd->clist);        break;//critical命令处理    case K_critical:        svc->flags |= SVC_CRITICAL;        break;//setenv命令处理    case K_setenv: { /* name value */        struct svcenvinfo *ei;        if (nargs < 2) {            parse_error(state, "setenv option requires name and value arguments\n");            break;        }//创建一个环境变量svcenvinfo        ei = calloc(1, sizeof(*ei));        if (!ei) {            parse_error(state, "out of memory\n");            break;        }        ei->name = args[1];        ei->value = args[2];        //添加到svc->envvars链表中        ei->next = svc->envvars;        svc->envvars = ei;        break;    }//socket命令处理    case K_socket: {/* name type perm [ uid gid ] */        struct socketinfo *si;        if (nargs < 4) {            parse_error(state, "socket option requires name, type, perm arguments\n");            break;        }        if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")                && strcmp(args[2],"seqpacket")) {            parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n");            break;        }        //创建一个socket信息结构        si = calloc(1, sizeof(*si));        if (!si) {            parse_error(state, "out of memory\n");            break;        }        si->name = args[1];        si->type = args[2];        si->perm = strtoul(args[3], 0, 8);        if (nargs > 4)            si->uid = decode_uid(args[4]);        if (nargs > 5)            si->gid = decode_uid(args[5]);        //添加到svc->sockets链表中        si->next = svc->sockets;        svc->sockets = si;        break;    }//user命令处理    case K_user:        if (nargs != 2) {            parse_error(state, "user option requires a user id\n");        } else {            svc->uid = decode_uid(args[1]);        }        break;//seclabel命令处理    case K_seclabel:#ifdef HAVE_SELINUX        if (nargs != 2) {            parse_error(state, "seclabel option requires a label string\n");        } else {            svc->seclabel = args[1];        }#endif        break;    default:        parse_error(state, "invalid option '%s'\n", args[0]);    }}
3. Action解析
static void *parse_action(struct parse_state *state, int nargs, char **args){    struct action *act;    if (nargs < 2) {        parse_error(state, "actions must have a trigger\n");        return 0;    }    if (nargs > 2) {        parse_error(state, "actions may not have extra parameters\n");        return 0;    }//创建一个action    act = calloc(1, sizeof(*act));    act->name = args[1];    list_init(&act->commands);//添加到action_list链表中    list_add_tail(&action_list, &act->alist);    return act;}
解析到新的on section调用parse_action()时,申请了struct action *act,设置:

     1) act->name为on section的名字(比如boot/fs/);

     2) 初始化list act->commands

     3) 把act->alist加入到action_list的列尾

这样,action创建并加入到了action_list中。

4.Action 命令解析

static void parse_line_action(struct parse_state* state, int nargs, char **args){    struct command *cmd;//获取解析得到的action    struct action *act = state->context;    int (*func)(int nargs, char **args);    int kw, n;    if (nargs == 0) {        return;    }    //查找关键字类型    kw = lookup_keyword(args[0]);//如果不是COMMAND类型,跳出不处理    if (!kw_is(kw, COMMAND)) {        parse_error(state, "invalid command '%s'\n", args[0]);        return;    }    //得到命令参数个数,验证参数个数的合法性    n = kw_nargs(kw);    if (nargs < n) {        parse_error(state, "%s requires %d %s\n", args[0], n - 1,            n > 2 ? "arguments" : "argument");        return;    }//创建命令command    cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);    cmd->func = kw_func(kw);    cmd->nargs = nargs;    memcpy(cmd->args, args, sizeof(char*) * nargs);//将command添加到act->commands链表中    list_add_tail(&act->commands, &cmd->clist);}
action里的command的解析
    对on section内action里的command,调用parse_line_action()
     1) 查找关键字,核对是否是COMMAND,参数数目是否正确
     2) 申请struct command *cmd
       - cmd->func从keyword表中获取;
       - 设置参数个数给cmd->nargs,拷贝参数给cmd->args;
       - 把cmd->clist加入到act->commands的列尾

这样,command加入到了action中。

5. import 命令解析

void parse_import(struct parse_state *state, int nargs, char **args){    struct listnode *import_list = state->priv;    struct import *import;    char conf_file[PATH_MAX];    int ret;    //参数个数判断    if (nargs != 2) {        ERROR("single argument needed for import\n");        return;    }    ret = expand_props(conf_file, args[1], sizeof(conf_file));    if (ret) {        ERROR("error while handling import on line '%d' in '%s'\n",              state->line, state->filename);        return;    }    //创建一个import    import = calloc(1, sizeof(struct import));//设置import文件名称    import->filename = strdup(conf_file);//添加到import->list链表中    list_add_tail(import_list, &import->list);    INFO("found import '%s', adding to import list", import->filename);}

当init.rc文件解析完成后,将从import_list链表中取出通过关键字import导入的其他rc文件,并调用init_parse_config_file函数进行解析:

list_for_each(node, &import_list) { struct import *import = node_to_item(node, struct import, list); int ret; INFO("importing '%s'", import->filename); ret = init_parse_config_file(import->filename); if (ret) ERROR("could not import file '%s' from '%s'\n",   import->filename, fn);}
到此init.rc文件就解析完成,文件内容全部存储在service_list和action_list链表中。

添加Action到待执行队列

 当解析完所有的init.rc内容之后,在执行这些action之前,需要按顺序将其置于一个待执行队列中

void action_for_each_trigger(const char *trigger,void (*func)(struct action *act)){    struct listnode *node;    struct action *act;//遍历action_list链表,根据名字查找相关的action    list_for_each(node, &action_list) {        act = node_to_item(node, struct action, alist);        if (!strcmp(act->name, trigger)) {//回调action_add_queue_tail函数            func(act);        }    }}
从action_list链表中查询指定名称的action,并调用函数action_add_queue_tail将其添加到待执行队列action_queue中。

void action_add_queue_tail(struct action *act){    list_add_tail(&action_queue, &act->qlist);}

action_for_each_trigger()把队列action_list里所匹配的action,追加到action_queue的队尾

构建新的Action

还有一些没有在init.rc中定义的action,相比init.rc,这些action的共同点是没有参数,对于这类action,通过queue_builtin_action()函数来构建

queue_builtin_action()把执行的函数组成command,创建action,挂在action_list上,并追加到action_queue的队尾。

void queue_builtin_action(int (*func)(int nargs, char **args), char *name){    struct action *act;    struct command *cmd;    //创建一个Action    act = calloc(1, sizeof(*act));    act->name = name;    list_init(&act->commands);    //为该Action创建一个command    cmd = calloc(1, sizeof(*cmd));    cmd->func = func;    cmd->args[0] = name;//将该command添加到Action的commands链表中    list_add_tail(&act->commands, &cmd->clist);    //将该Action添加到action_list链表中    list_add_tail(&action_list, &act->alist);    //将该Action添加到待执行队列action_queue中    action_add_queue_tail(act);}

添加Action到待执行队列

init 进程通过action_for_each_trigger 和queue_builtin_action 函数向待执行队列action_queue依次添加了以下Action:

    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(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);    /* skip mounting filesystems in charger mode */    action_for_each_trigger("early-fs", action_add_queue_tail);    action_for_each_trigger("fs", action_add_queue_tail);action_for_each_trigger("post-fs", action_add_queue_tail);    if (!is_charger) {        //action_for_each_trigger("post-fs", action_add_queue_tail);        action_for_each_trigger("post-fs-data", action_add_queue_tail);    }    queue_builtin_action(property_service_init_action, "property_service_init");    queue_builtin_action(signal_init_action, "signal_init");    queue_builtin_action(check_startup_action, "check_startup");    if (!strcmp(bootmode, "alarm")) {        action_for_each_trigger("alarm", action_add_queue_tail);    }    if (is_charger) {        action_for_each_trigger("charger", action_add_queue_tail);    } else {        action_for_each_trigger("early-boot", action_add_queue_tail);        action_for_each_trigger("boot", 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 BOOTCHART    queue_builtin_action(bootchart_init_action, "bootchart_init");#endif

early-init
    查看init.rc中的相应字符段为start ueventd
    这个action主要目的是通过early-init启动ueventd服务,这个服务负责uevent(user space event)的处理,uevent是内核向用户空间发出的一个时间通知,使应用程序能够有机会对该event做出反应。

wait_for_coldboot_done
    android 冷过程结束后会生成dev/.coldboot_done文件,wait_for_coldboot_done这个action会等待dev/.coldboot_done文件的生成,等待时长为5s。当然这个action不会阻塞android的冷启动过程,它会每查询一次就会休眠0.1s,直到冷启动结束。

static int wait_for_coldboot_done_action(int nargs, char **args){    int ret;    INFO("wait for %s\n", coldboot_done);//  /dev/.coldboot_done//#define COMMAND_RETRY_TIMEOUT 5    ret = wait_for_file(coldboot_done, COMMAND_RETRY_TIMEOUT);    if (ret)        ERROR("Timed out waiting for %s\n", coldboot_done);    return ret;}

keychord_init

keychord是组合按键,Android暂时还不支持keychord机制,keychord机制就是在init.rc文件中为每个服务配置组合键,在服务解析时为指定服务设置相应的键码值。

static int keychord_init_action(int nargs, char **args){    keychord_init();    return 0;}
调用keychord_init函数来初始化组合键机制。

void keychord_init(){    int fd, ret;    //遍历service_list链表,为每个service分配keychord_id    service_for_each(add_service_keycodes);    /* nothing to do if no services require keychords */    if (!keychords)        return;    //打开/dev/keychord设备文件    fd = open("/dev/keychord", O_RDWR);    if (fd < 0) {        ERROR("could not open /dev/keychord\n");        return;    }//设置设备属性    fcntl(fd, F_SETFD, FD_CLOEXEC);    //将keychords数组内容写入设备文件中    ret = write(fd, keychords, keychords_length);    if (ret != keychords_length) {        ERROR("could not configure /dev/keychord %d (%d)\n", ret, errno);        close(fd);        fd = -1;    }    free(keychords);    keychords = 0;    keychord_fd = fd;}
console_init
    1.如果/proc/cmdline指定了控制台终端,那么优先使用这个控制台,如果没有指定,那么将使用默认控制台终端/dev/console。
    2.加载开机图片,参考load_565rle_image函数
    a,通过ioctl函数修改dev/tty0(即终端控制台)为图像显示模式;
    b,尝试打开/initlogo.rle,如果失败,那么将dev/tty0恢复为文本显示模式,则开机时显示"ANDROID"文字;
    c,如果打开/initlogo.rle成功,那么init将会打开Framebuffer;

    d,将initlogo.rle数据写到Framebuffer中。

static int console_init_action(int nargs, char **args){    int fd;    char tmp[PROP_VALUE_MAX];    if (console[0]) {        snprintf(tmp, sizeof(tmp), "/dev/%s", console);        console_name = strdup(tmp);    }    fd = open(console_name, O_RDWR);    if (fd >= 0)        have_console = 1;    close(fd);    //加载开机图片    if( load_565rle_image(INIT_IMAGE_FILE) ) {        fd = open("/dev/tty0", O_WRONLY);        if (fd >= 0) {            const char *msg;                msg = "\n"            "\n"            "\n"            "\n"            "\n"            "\n"            "\n"  // console is 40 cols x 30 lines            "\n"            "\n"            "\n"            "\n"            "\n"            "\n"            "\n"            "             A N D R O I D ";            write(fd, msg, strlen(msg));            close(fd);        }    }    return 0;}
load_565rle_image()函数将加载由参数传递过来的图像文件,而后将该文件显示在LCD屏幕上。

property_service_init

       读取属性文件,并设置相关属性。关于Android属性系统,请查看Android 系统属性SystemProperty分析

static int property_service_init_action(int nargs, char **args){    /* read any property files on system or data and     * fire up the property service.  This must happen     * after the ro.foo properties are set above so     * that /data/local.prop cannot interfere with them.     */    start_property_service();    return 0;}

signal_init
      
    创建套接字对,以便init进程在收到子进程终止的SIGCHLD信号时调用相应的handler

static int signal_init_action(int nargs, char **args){    signal_init();    return 0;}

void signal_init(void){    int s[2];    struct sigaction act;    act.sa_handler = sigchld_handler; //设置handler回调函数    act.sa_flags = SA_NOCLDSTOP;    act.sa_mask = 0;    act.sa_restorer = NULL;    sigaction(SIGCHLD, &act, 0); //安装信号处理器    /* create a signalling mechanism for the sigchld handler */    if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {         signal_fd = s[0];        signal_recv_fd = s[1];        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();}
init进程定义了handler,用于处理子进程的终止,当子进程死亡时将向父进程发送SIGCHLD信号,为了调用相关handler,init进程会通过socket连接SIGCHLD信号的handler,socketpair()函数会创建一对已经连接的套接字,事件处理handler会监视signal_recv_fd的值,当其值为1时,init进程就会调用子进程停止处理函数handler。

check_startup

    检查属性socket句柄及信号句柄是否安装成功

static int check_startup_action(int nargs, char **args){    /* */    if ((get_property_set_fd() < 0) ||(get_signal_fd() < 0)) {        ERROR("init startup failure\n");        exit(1);    }        /* signal that we hit this point */    unlink("/dev/.booting");    return 0;}
queue_property_triggers
   根据当前属性值来触发该属性对应的动作

static int queue_property_triggers_action(int nargs, char **args){    queue_all_property_triggers();    /* enable property triggers */    property_triggers_enabled = 1;    return 0;}
调用queue_all_property_triggers()函数来检查init.rc文件中配置的属性触发条件是否满足,如果满足,则将该Action添加到待执行队列中:

void queue_all_property_triggers(){    struct listnode *node;    struct action *act;//遍历action_list链表    list_for_each(node, &action_list) {//取得每个节点下对应的action        act = node_to_item(node, struct action, alist);//如果该action的名字以property开头         if (!strncmp(act->name, "property:", strlen("property:"))) {            //读取该属性的名称            const char* name = act->name + strlen("property:");//读取该属性的值            const char* equals = strchr(name, '=');            if (equals) {                char prop_name[PROP_NAME_MAX + 1];                const char* value;                int length = equals - name;                if (length > PROP_NAME_MAX) {                    ERROR("property name too long in trigger %s", act->name);                } else {                    memcpy(prop_name, name, length);                    prop_name[length] = 0;                    /* 从属性系统中读取该属性的值*/                    value = property_get(prop_name);//如果属性系统中的值等于init.rc文件中设置的触发值                    if (value && (!strcmp(equals + 1, value) ||!strcmp(equals + 1, "*"))) {                        //将该Action添加到待执行队列action_queue中action_add_queue_tail(act);                    }                }            }        }    }}

Action - boot

  在boot动作中启动所有的service服务,启动命令如下:

class_start core                                                                                      class_start main
我们可以在service配置中通过关键字class 将service分为不同的类别,从而可以通过class_start 或class_stop 来启动或停止某一类型的service,如下将adbd服务设置为core类型的服务:

service adbd /sbin/adbdclass coredisabled
class_start core 表示启动所有类型为core的服务:

int do_class_start(int nargs, char **args){        /* Starting a class does not start services         * which are explicitly disabled.  They must         * be started individually.         */    service_for_each_class(args[1], service_start_if_not_disabled);    return 0;}
函数中args[1]指定该服务所属类型,service_start_if_not_disabled是启动服务的回调函数;

void service_for_each_class(const char *classname,                            void (*func)(struct service *svc)){    struct listnode *node;    struct service *svc;    list_for_each(node, &service_list) {        svc = node_to_item(node, struct service, slist);        if (!strcmp(svc->classname, classname)) {            func(svc);        }    }}
service_for_each_class函数通过遍历service_list服务链表来查找指定类型名称的服务,并调用函数service_start_if_not_disabled来启动服务。

static void service_start_if_not_disabled(struct service *svc){    if (!(svc->flags & SVC_DISABLED)) {        service_start(svc, NULL);    }}
函数service_start_if_not_disabled()首先判断该服务的标志位是否设置成了SVC_DISABLED,SVC_DISABLED标志着服务不能在开始时启动,如果服务没有设置此标志位,则启动该服务,

service_start()函数比较复杂,这里就不详细分析,service_start()函数主要完成以下工作:

1)设置服务标志位

2)调用fork()系统调用创建新的进程;

3)获取属性匿名存储空间句柄,并添加为服务配置的环境变量;

4)创建服务配置的socket,调用publish_socket函数将创建的socket句柄添加到环境变量中;该环境变量为:ANDROID_SOCKET_XXX = fd

5)为新进程打开控制台,并设置新进程的PID,GID等;

6)调用execve()系统调用执行新进程运行的程序;

7)设置服务运行状态属性;该属性为:init.svc.XXX = running

Init进程循环执行

当将以上Action添加到待执行队列中后,init进程将进入无限循环中执行,循环过程中主要完成以下工作:

A. 调用函数execute_one_command来检查action_queue列表是否为空。如果不为空的话,那么init进程就会将保存在列表头中的action移除,并且执行这个被移除的action。由于前面我们将一个名称为“console_init”的action添加到了action_queue列表中,因此,在这个无限循环中,这个action就会被执行,即函数console_init_action会被调用。
B. 调用函数restart_processes来检查系统中是否有进程需要重启。在启动脚本/init.rc中,我们可以指定一个进程在退出之后会自动重新启动。在这种情况下,函数restart_processes就会检查是否存在需要重新启动的进程,如果存在的话,那么就会将它重新启动起来。
C. 处理系统属性变化事件。当我们调用函数property_set来改变一个系统属性值时,系统就会通过一个socket(通过调用函数get_property_set_fd可以获得它的文件描述符)来向init进程发送一个属性值改变事件通知。init进程接收到这个属性值改变事件之后,就会调用函数handle_property_set_fd来进行相应的处理。后面在分析第三个开机画面的显示过程时,我们就会看到,SurfaceFlinger服务就是通过修改“ctl.start”和“ctl.stop”属性值来启动和停止第三个开机画面的。
D. 处理一种称为“chorded keyboard”的键盘输入事件。这种类型为chorded keyboard”的键盘设备通过不同的铵键组合来描述不同的命令或者操作,它对应的设备文件为/dev/keychord。我们可以通过调用函数get_keychord_fd来获得这个设备的文件描述符,以便可以监控它的输入事件,并且调用函数handle_keychord来对这些输入事件进行处理。
E. 回收僵尸进程。我们知道,在Linux内核中,如果父进程不等待子进程结束就退出,那么当子进程结束的时候,就会变成一个僵尸进程,从而占用系统的资源。为了回收这些僵尸进程,init进程会安装一个SIGCHLD信号接收器。当那些父进程已经退出了的子进程退出的时候,内核就会发出一个SIGCHLD信号给init进程。init进程可以通过一个socket(通过调用函数get_signal_fd可以获得它的文件描述符)来将接收到的SIGCHLD信号读取回来,并且调用函数handle_signal来对接收到的SIGCHLD信号进行处理,即回收那些已经变成了僵尸的子进程。

for(;;) {int nr, i, timeout = -1;execute_one_command();①restart_processes();②if (!property_set_fd_init && get_property_set_fd() > 0) {③ufds[fd_count].fd = get_property_set_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;property_set_fd_init = 1;}if (!signal_fd_init && get_signal_fd() > 0) {ufds[fd_count].fd = get_signal_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;signal_fd_init = 1;}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;}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;}}#endifnr = 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();⑧}}}
1).execute_one_command(void)

从待执行队列action_queue中取出一个Action来执行,并且将已经执行完的Action从action_queue队列中移除。

void execute_one_command(void){    int ret;    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 {        cur_command = get_next_command(cur_action, cur_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);}
      1) 从action_queue取下struct action *act赋给cur_action;

      2) 从cur_action获得struct command *赋给cur_command;

      3) 执行cur_command->func(cur_command->nargs, cur_command->args)

2).服务重启

当内存不足时,Android系统会自动杀死一下进程来释放空间,所以当某些重要的服务被杀,同时该服务进程并未设置为oneshot,则必须重新启动该服务进程。

static void restart_processes(){    process_needs_restart = 0;    service_for_each_flags(SVC_RESTARTING,restart_service_if_needed);}
调用函数service_for_each_flags来循环遍历服务链表,查找标志位为SVC_RESTARTING的服务,当该服务进程死亡时,init进程监控到进程死亡事件,在处理该事件的时候会为该服务进程设置SVC_RESTARTING标志位,并调用restart_service_if_needed函数重启服务

void service_for_each_flags(unsigned matchflags,                            void (*func)(struct service *svc)){    struct listnode *node;    struct service *svc;    list_for_each(node, &service_list) {        svc = node_to_item(node, struct service, slist);        if (svc->flags & matchflags) {            func(svc);        }    }}
从服务链表中查找具有相同标志位的服务,并调用回调函数进行处理,对于具有SVC_RESTARTING标志的服务,说明该服务需要重启,

static void restart_service_if_needed(struct service *svc){    time_t next_start_time = svc->time_started + 5;    if (next_start_time <= gettime()) {        svc->flags &= (~SVC_RESTARTING);        service_start(svc, NULL);        return;    }    if ((next_start_time < process_needs_restart) ||        (process_needs_restart == 0)) {        process_needs_restart = next_start_time;    }}
当当前时间大于服务启动时间时,清楚服务重启标志并启动该服务,service_start()函数已经在前面简单介绍过了。那服务重启标志位是在哪里设置的呢?在接下来介绍的init进程处理子进程死亡信号SIGCHLD时会进行详细介绍。

3.设置句柄池

if (!property_set_fd_init && get_property_set_fd() > 0) {ufds[fd_count].fd = get_property_set_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;property_set_fd_init = 1;}if (!signal_fd_init && get_signal_fd() > 0) {ufds[fd_count].fd = get_signal_fd();ufds[fd_count].events = POLLIN;ufds[fd_count].revents = 0;fd_count++;signal_fd_init = 1;}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;}
get_property_set_fd()函数用于得到属性socket设备/dev/socket/property_service的句柄property_set_fd,并添加到句柄次ufds中;property_set_fd_init标志位的设置是为了在下一次循环中不在执行这部分代码,从而避免了重复添加句柄的工作。

get_signal_fd()函数用于得到安装信号处理时创建的socket对的接收端句柄signal_recv_fd;signal_fd_init和property_set_fd_init的作用相同;

get_keychord_fd()函数用于得到设备/dev/keychord的句柄keychord_fd,keychord_fd_init和property_set_fd_init的作用相同;

4.计算超时时间

系统调用poll在监控句柄池时,如果超时时间到了或者有事件发生时,才会返回,如果超时时间被设置为-1时,只有事件发生才会返回。

if (process_needs_restart) {timeout = (process_needs_restart - gettime()) * 1000;if (timeout < 0)timeout = 0;}if (!action_queue_empty() || cur_action)timeout = 0;
如果待执行队列不为空,并且当前Action也不为空,这设置timeout为0,这样poll就不会阻塞,init进程就可以循环执行队列action_queue中的Action

5.事件监控

nr = poll(ufds, fd_count, timeout);if (nr <= 0)continue;
如果被监控的句柄池中的句柄没有事件发生,但超时时间已到,则返回-1,此时代码不往下执行,而是继续循环执行队列action_queue中的Action,及重启必要的服务。

6.事件处理

当监控的句柄池中的句柄发生了某些事件时,返回事件发生对应的句柄,从而进入该句柄对应的事件处理函数中。

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();}}
这里有三类事件:

1.属性设置事件;

2.键盘组合事件;

3.子进程死亡信号事件;

对于属性设置事件处理handle_property_set_fd(),请查看Android 系统属性SystemProperty分析。由于Android系统暂时未使用keychord机制,因此这里不详细介绍。

void handle_keychord(){    struct service *svc;    const char* debuggable;    const char* adb_enabled;    int ret;    __u16 id;    // only handle keychords if ro.debuggable is set or adb is enabled.    // the logic here is that bugreports should be enabled in userdebug or eng builds    // and on user builds for users that are developers.    debuggable = property_get("ro.debuggable");    adb_enabled = property_get("init.svc.adbd");    ret = read(keychord_fd, &id, sizeof(id));    if (ret != sizeof(id)) {        ERROR("could not read keychord id\n");        return;    }    //只有在调试模式下才使用    if ((debuggable && !strcmp(debuggable, "1")) ||        (adb_enabled && !strcmp(adb_enabled, "running"))) {        svc = service_find_by_keychord(id); //根据keychord_id查找指定的服务        if (svc) {            INFO("starting service %s from keychord\n", svc->name); //通过发送组合键消息来启动某些服务            service_start(svc, NULL);        } else {            ERROR("service for keychord %d not found\n", id);        }    }}
keychord机制就是为服务配置指定的组合键,可以通过该组合键来启动对应的服务。


当init进程的某个子进程终止时,会对系统的运行产生影响,因此init进程需要重新启动他们。当init的子进程意外终止时,会向父进程init进程传递SIGCHLD信号,init进程接收到该信号时,预先安装的handler将被调用,将SIGCHLD信号的编号写入socket对的一端,在socket另一端通过poll系统调用监控到事件的发生,将调用子进程死亡事件处理函数。



当init子进程终止时,init进程会接收到SIGCHLD信号,前面已经介绍了init进程首先安装了信号处理器,因此当接收到SIGCHLD信号时,init进程会调用与该信号相对应的处理函数sigchld_handler:

static void sigchld_handler(int s){    write(signal_fd, &s, 1);}
参数s用来接收SIGCHLD信号的编号,该函数仅仅将信号编号写入socket对的一端signal_fd中,由于signal_fd与signal_recv_fd是一对已连接的socket,因此当向signal_fd写入信号编号时,信号编号被传递到接收端signal_recv_fd中,由于signal_recv_fd被添加到了监控句柄池中并被注册到了poll系统调用中,因此信号编号的写入将触发poll函数返回并调用handle_signal()函数来处理信号事件。

void handle_signal(void){    char tmp[32];    //读取socket接收端的数据    /* we got a SIGCHLD - reap and restart as needed */    read(signal_recv_fd, tmp, sizeof(tmp));    while (!wait_for_one_process(0))        ;}
该函数首先从signal_recv_fd读取发送过来的信号编号,表示该事件得到处理,避免重复处理该信号事件,然后循环调用wait_for_one_process函数,直到wait_for_one_process函数返回非0,wait_for_one_process函数在产生SIGCHLD信号的进程服务列表中,检查进程的设置选项,若选项没有配置oneshot(SVC_ONE_SHOT)则设置重启选项(SVC_RESTARTING),oneshot选项定义在init.rc文件的service部分中,若进程带有oneshot选项,进程终止时不会被重启。

static int wait_for_one_process(int block){   //block = 0 -->false    pid_t pid;    int status;    struct service *svc;    struct socketinfo *si;    time_t now;    struct listnode *node;    struct command *cmd;    /*当进程被终止时,将发送SIGCHLD信号,waitpid()函数用来回收进程所占用的资源,第一个参数pid是指欲等待的子进程的识别码,设置为-1表示查看所有子进程是否发出SIGCHIL信号,第二个参数status用于返回子进程的结束状态;第三个参数决定waitpid()函数是否应用阻塞处理方式。waitpid()函数返回产生SIGCHID信号的进程pid */    while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR );    //正常情况下返回的死亡进程pid大于0,因此wait_for_one_process的返回值正常情况下为0if (pid <= 0) return -1;    INFO("waitpid returned pid %d, status = %08x\n", pid, status);    //用于根据pid值在服务链表中查找对应的服务    svc = service_find_by_pid(pid);    if (!svc) {        ERROR("untracked pid %d exited\n", pid);        return 0;    }        NOTICE("process '%s', pid %d exited\n", svc->name, pid);    /* 检查服务是否设置了oneshot标志,SVC_ONESHOT表示进程仅运行一次,如果没有设置SVC_ONESHOT标志,    表示需要重启该服务进程,首先将该服务进程组下的所有子进程杀死 */    if (!(svc->flags & SVC_ONESHOT)) {        kill(-pid, SIGKILL);        NOTICE("process '%s' killing any children in process group\n", svc->name);    }    /* 删除该服务进程下的创建的所有socket  */    for (si = svc->sockets; si; si = si->next) {        char tmp[128];        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);        unlink(tmp);    }    //设置服务的pid为0 ,并清除SVC_RUNNING标志    svc->pid = 0;    svc->flags &= (~SVC_RUNNING);    /* 如果设置了SVC_ONESHOT标志,表示服务只能运行一次,因此设置表示位SVC_DISABLED */    if (svc->flags & SVC_ONESHOT) {        svc->flags |= SVC_DISABLED;    }    /* 判断服务标志是否设置了SVC_DISABLED 或 SVC_RESET 对于设置了这两种标志的进程是不能重启的 */    if (svc->flags & (SVC_DISABLED | SVC_RESET) )  {//设置进程运行状态属性值为stopped        notify_service_state(svc->name, "stopped");        return 0;    }    now = gettime();//如果死亡的服务进程是系统关键进程,则直接重启手机    if (svc->flags & SVC_CRITICAL) {        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,在restart_processes()函数中会重启持有SVC_RESTARTING    svc->flags |= SVC_RESTARTING;    /* 运行该service下所有Execute all onrestart commands for this service. */    list_for_each(node, &svc->onrestart.commands) {        cmd = node_to_item(node, struct command, clist);        cmd->func(cmd->nargs, cmd->args);     }//设置进程运行状态属性值为stopped    notify_service_state(svc->name, "restarting");    return 0;}

原创粉丝点击