Android init进程(长文)

from : https://my.oschina.net/wizardmerlin/blog/511926

本文转载自:(我给它整合了,并适当修改了一些笔误或者说的不准确的内容)

http://blog.csdn.net/yangwen123/article/details/9029959

http://blog.csdn.net/zhgxhuaa/article/details/22994095

http://blog.csdn.net/zhgxhuaa/article/details/23059849

http://blog.csdn.net/zhgxhuaa/article/details/23104119

http://blog.csdn.net/zhgxhuaa/article/details/24153441


本文需要java, c++, c, linux等相关知识  (没有怎么办,没有就看流程,千万别等我会了xxx再说。。。就要现在)

tip:   记住它的流程图忙住蛮大的。


init启动过程

众所周知,Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。Android是基于Linux的操作系统,所以init也是Android系统中用户空间的第一个进程,它的进程号是1。下面先简单的看一下init进程的启动过程。


@/kernel/goodfish/init/main.c

kernel_init()

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);  
  
    do_pre_smp_initcalls();  
    lockup_detector_init();  
  
    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;  
}

init_post()

/* This is a non __init function. Force it to be noinline otherwise gcc 
 * makes it inline to init() and it becomes part of init.text section 
 */  
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;  
  
    if (ramdisk_execute_command) {  
        run_init_process(ramdisk_execute_command);  
        printk(KERN_WARNING "Failed to execute %s\n",  
                ramdisk_execute_command);  
    }  
  
    /* 
     * We try each of these until one succeeds. 
     * 
     * The Bourne shell can be used instead of init if we are 
     * trying to recover a really broken machine. 
     */  
    if (execute_command) {  
        run_init_process(execute_command);  
        printk(KERN_WARNING "Failed to execute %s.  Attempting "  
                    "defaults...\n", execute_command);  
    }  
    run_init_process("/sbin/init");  
    run_init_process("/etc/init");  
    run_init_process("/bin/init");  
    run_init_process("/bin/sh");  
  
    panic("No init found.  Try passing init= option to kernel. "  
          "See Linux Documentation/init.txt for guidance.");  
}

run_init_process()

static void run_init_process(const char *init_filename)  
{  
    argv_init[0] = init_filename;  
    kernel_execve(init_filename, argv_init, envp_init);  
}

在init_post()中会判断execute_command是否为空,如果不为空则执行run_init_process调用。execute_command的赋值在init_setup()中,所以这里应该注意在设置内核启动选项时,应设置为“ init=/init”,以便正常启动init进程,因为编译完Android后生成的文件系统中,init位于最顶层目录。 (可以在手机的根目录看到init文件)

static const char * argv_init[MAX_INIT_ARGS+2] = { "init"NULL, };
static int __init init_setup(char *str)  
{  
    unsigned int i;  
  
    execute_command = str;  
    /* 
     * In case LILO is going to boot us with default command line, 
     * it prepends "auto" before the whole cmdline which makes 
     * the shell think it should execute a script with such name. 
     * So we ignore all arguments entered _before_ init=... [MJ] 
     */  
    for (i = 1; i < MAX_INIT_ARGS; i++)  
        argv_init[i] = NULL;  
    return 1;  
}  
__setup("init=", init_setup);


当根目录中不存在init时,或者未指定启动项“init=”时,内核会到/sbin、/etc、/bin目录下查找init。

了解了init进程的启动过程后,接下来看一下init进程都干了些什么?Android中的init进程与Linux不同,其职责可以归结如下:

  • 作为守护进程

  • 解析和执行init.rc文件

  • 生成设备驱动节点

  • 属性服务

init源码分析

init进程的入口函数是main,它的代码如下:

@/system/core/init/init.c

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;  
  
  
    //启动ueventd  
    if (!strcmp(basename(argv[0]), "ueventd"))  
        return ueventd_main(argc, argv);  
  
  
    //启动watchdogd  
    if (!strcmp(basename(argv[0]), "watchdogd"))  
        return watchdogd_main(argc, argv);  
  
  
    /* clear the umask */  
    umask(0);  
  
  
        /* 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"0NULL);  
    mount("proc""/proc""proc"0NULL);  
    mount("sysfs""/sys""sysfs"0NULL);  
  
  
        /* 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_  
    klog_init();//log初始化  
    property_init();//属性服务初始化  
  
  
    //从/proc/cpuinfo中读取Hardware名,在后面的mix_hwrng_into_linux_rng_action函数中会将hardware的值设置给属性ro.hardware  
    get_hardware_name(hardware, &revision);  
  
  
    //导入并设置内核变量  
    process_kernel_cmdline();  
  
  
    //selinux相关,暂不分析  
    union selinux_callback cb;  
    cb.func_log = klog_write;  
    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");  
  
  
    is_charger = !strcmp(bootmode, "charger");//关机充电相关,暂不做分析  
  
  
    INFO("property init\n");  
    if (!is_charger)  
        property_load_boot_defaults();  
  
  
    INFO("reading config file\n");  
    init_parse_config_file("/init.rc");//解析init.rc配置文件  
  
  
    /* 
     * 解析完init.rc后会得到一系列的action等,下面的代码将执行处于early-init阶段的action。 
     * init将action按照执行时间段的不同分为early-init、init、early-boot、boot。 
     * 进行这样的划分是由于有些动作之间具有依赖关系,某些动作只有在其他动作完成后才能执行,所以就有了先后的区别。 
     * 具体哪些动作属于哪个阶段是在init.rc中的配置决定的 
     */  
    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);  
  
  
    /* skip mounting filesystems in charger mode */  
    if (!is_charger) {  
        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);  
        action_for_each_trigger("post-fs-data", 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");  
    queue_builtin_action(check_startup_action, "check_startup");  
  
  
    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  
  
  
    for(;;) {//init进入无限循环  
        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())//处理keychord事件  
                    handle_keychord();  
                else if (ufds[i].fd == get_signal_fd())//处理  
                    handle_signal();//处理SIGCHLD信号  
            }  
        }  
    }  
  
  
    return 0;  
}

main函数分析:

 if (!strcmp(basename(argv[0]), "ueventd"))  
        return ueventd_main(argc, argv);

main函数一开始就会判断参数argv[0]的值是否等于“ueventd”,如果是就调用ueventd进程的入口函数ueventd_main()启动ueventd进程。这是怎么回事呢?当前正在启动的进程不是init吗?它的名称怎么可能会等于“ueventd”?所以这里有必要看一下ueventd的启动过程,ueventd是在init.rc中被启动的。

on boot  
service ueventd /sbin/ueventd  
    class core  
    critical  
    seclabel u:r:ueventd:s0

可以看出ueventd可执行文件位于/sbin/ueventd,在观察了/sbin/ueventd后我们发现,它只不过是是可执行文件/init的一个符号链接文件,即应用程序ueventd和init运行的是同一个可执行文件。

所以,整个过程是这样的:内核启动完成之后,可执行文件/init首先会被执行,即init进程会首先被启动。init进程在启动的过程中,会对启动脚本/init.rc进行解析。在启动脚本/init.rc中,配置了一个ueventd进程,它对应的可执行文件为/sbin/ueventd,即ueventd进程加载的可执行文件也为/init(此时init中main函数的参数argv[0] = “/sbin/ueventd”)。因此,通过判断参数argv[0]的值,就可以知道当前正在启动的是init进程还是ueventd进程。      

PS:ueventd是一个守护进程,主要作用是接收uevent来创建或删除/dev/xxx(设备节点),其实现位于@system/core/init/ueventd.c中。ueventd进程会通过一个socket接口来和内核通信,以便可以监控系统设备事件。


在开始所有的工作之前,main进程首先做的是创建并挂载启动所需的(其他的会在解析init.rc时创建)文件目录,如下所示:

 /* 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"0NULL);  
mount("proc""/proc""proc"0NULL);  
mount("sysfs""/sys""sysfs"0NULL);

说明:

tmpfs是一种虚拟内存的文件系统,典型的tmpfs文件系统完全驻留在RAM中,读写速度远快于内存或硬盘文件系统。

/dev目录保存着硬件设备访问所需要的设备驱动程序。在Android中,将相关目录作用于tmpfs,可以大幅度提高设备访问的速度。

devpts是一种虚拟终端文件系统。

proc是一种虚拟文件系统,只存在于内存中,不占用外存空间。借助此文件系统,应用程序可以与内核内部数据结构进行交互。

sysfs是一种特殊的文件系统,在Linux 2.6中引入,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息,将proc、devpts、devfs三种文件系统统一起来。
编译Android系统源码时,在生成的根文件系统中,并不存在/dev、/proc、/sys这类目录,它们是系统运行时的目录,有init进程在运行中生成,当系统终止时,它们就会消失。上面的代码所形成的的文件层次结构为:

 /* 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_

open_devnull_stdio()函数的作用是重定向标准输入/输出/错误输出到/dev/_null_,至于为什么要重定向的原因在注释中已经写明。open_devnull_stdio()的实现如下:

@system/core/init/util.c

void open_devnull_stdio(void)  
{  
    int fd;  
    static const char *name = "/dev/__null__";  
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) {  
        fd = open(name, O_RDWR);  
        unlink(name);  
        if (fd >= 0) {  
            dup2(fd, 0);  
            dup2(fd, 1);  
            dup2(fd, 2);  
            if (fd > 2) {  
                close(fd);  
            }  
            return;  
        }  
    }  
  
    exit(1);  
}
klog_init();

klog_init()用于初始化log,通过其实现可以看出log被打印到/dev/__kmsg__文件中。主要在代码中最后通过fcntl和unlink使得/dev/__kmsg__不可被访问,这就保证了只有log程序才可以访问。

void klog_init(void)  
{  
    static const char *name = "/dev/__kmsg__";  
  
    if (klog_fd >= 0return/* Already initialized */  
  
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {  
        klog_fd = open(name, O_WRONLY);  
        if (klog_fd < 0)  
                return;  
        fcntl(klog_fd, F_SETFD, FD_CLOEXEC);  
        unlink(name);  
    }  
}


property_init

属性服务初始化,这里先不深究,接下来会单独分析。


//从/proc/cpuinfo中读取Hardware名
//在后面的mix_hwrng_into_linux_rng_action函数中会将hardware的值设置给属性ro.hardware  
get_hardware_name(hardware, &revision);

get_hardware_name()函数的作用是从/proc/cpuinfo中获取Hardware和Revision的值,并保持到全局变量hardware和revision中。

下面的截图是在我的手机上的CPU info信息:

这里获取hardware信息有什么用呢?在main()函数后面的代码中,我们可以看见这样一句:

//导入并设置内核变量  
process_kernel_cmdline();

下面看一下process_kernel_cmdline的实现:

@system/core/init/init.c

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();  
}
static void export_kernel_boot_props(void)  
{  
    char tmp[PROP_VALUE_MAX];  
      
    ......  
  
    /* if this was given on kernel command line, override what we read 
     * before (e.g. from /proc/cpuinfo), if anything */  
    ret = property_get("ro.boot.hardware", tmp);  
    if (ret)  
        strlcpy(hardware, tmp, sizeof(hardware));  
    property_set("ro.hardware", hardware);  
  
    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);  
    property_set("ro.revision", tmp);  
  
    ......  
}

process_kernel_cmdline()函数用于导入和设置一些内核变量,在export_kernel_boot_props()中我们看见将hardware的值赋值给了属性"ro.hardware"。那这个赋值又是干什么的呢?我们再看一下main()函数,在解析init.rc配置文件的时候,有没有发现少了点什么?

INFO("reading config file\n");  
init_parse_config_file("/init.rc");//解析init.rc配置文件

是的,在以前比较老的代码中(例如2.3和4.0)这里除了init.rc以外还会有一个与硬件相关的rc脚本,如下:

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

那现在这段代码跑去哪里了呢?我们在init.rc中找到了它:

所以,之前设置的ro.hardware的值是在这里用的,在init.rc中用来导入init.${ro.hardware}.rc脚本,然后一起进行解析。与之前相比,这里只是方式变了,本质上还是一样的。

INFO("reading config file\n");  
init_parse_config_file("/init.rc");//解析init.rc配置文件  
  
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);  
  
/* skip mounting filesystems in charger mode */  
if (!is_charger) {  
    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);  
    action_for_each_trigger("post-fs-data", 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");  
queue_builtin_action(check_startup_action, "check_startup");  
  
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");  
  
  
BOOTCHART  
queue_builtin_action(bootchart_init_action, "bootchart_init");  
if

这部分代码用于解析init.rc脚本,并触发执行解析生成的action。

在main()函数的最后,init进入了一个无限循环,并等待一些事情的发生。即:在执行完前面的初始化工作以后,init变为一个守护进程。init所关心的事件有三类:属性服务事件、keychord事件和SIGNAL,当有这三类事件发生时,init进程会调用相应的handle函数进行处理。


我转载,我插嘴,抱歉啦: 

Init的四个作用这里已经说了俩了: 生成设备节点,作为守护进程处理相应的事件(属性服务以及其他SIGNAL),

还有俩作用下面说:

  • 解析和执行init.rc文件

  • 属性服务

(当然init最大的作用,孕育zygote它这里没有说)

总结:

init 是android系统用户空间的第一个进程,进程号为1。

lrwxrwxrwx root     root          ueventd -> ../init

lrwxrwxrwx root     root          watchdogd -> ../init

应该注意在设置内核启动选项时,应设置为“ init=/init”,以便正常启动init进程,因为编译完Android后

生成的文件系统中,init位于最顶层目录。

init的职责可以归结如下:

1. 作为守护进程

2. 解析和执行init.rc文件

3. 生成设备驱动节点

4. 属性服务


init进程在解析init.rc的过程中配置和启动了一些进程或者服务,其中有一个就是uevent这个进程(它的位置在手机的/sbin/ueventd,但这个文件直接指向了/init文件,实际上还是执行init,不过工作内容和原始的init不一样了,另一个启动的进程watchdog也是这么回事儿),它负责创建和删除/dev/xxx设备节点(在源码/system/core/init/ueventd.c中可以看到它的工作,利用socket和内核通信)。至于挂在启动所需的运行时目录,还是在init中。


ueventd的实现位于/system/core/init/ueventd.c你懂么?


解析init.rc, 还有执行init.rc到底是干什么?


对配置文件(init.rc)的解析做一下分析。

init.rc脚本语法

init.rc文件不同于init进程,init进程仅当编译完Android后才会生成,而init.rc文件存在于Android平台源代码中。init.rc在源代码中的位置为:@system/core/rootdir/init.rc。init.rc文件的大致结构如下图所示:



关于init.rc脚本的介绍,在@system/core/init/readme.txt中有完整的介绍,这里不再赘述,不想看英文的朋友也可以看下面的部分,这个部分关于rc脚本的介绍转自http://blog.csdn.net/nokiaguy/article/details/9109491。相当于readme的翻译吧。

init.rc文件并不是普通的配置文件,而是由一种被称为“Android初始化语言”(Android Init Language,这里简称为AIL)的脚本写成的文件。在了解init如何解析init.rc文件之前,先了解AIL非常必要,否则机械地分析init.c及其相关文件的源代码毫无意义。

为了学习AIL,读者可以到自己Android手机的根目录寻找init.rc文件,最好下载到本地以便查看,如果有编译好的Android源代码,在<Android源代码根目录>out/target/product/geneic/root目录也可找到init.rc文件。

AIL由如下4部分组成。

1.  动作(Actions)

2.  命令(Commands)

3.服务(Services)

4.  选项(Options)

     这4部分都是面向行的代码,也就是说用回车换行符作为每一条语句的分隔符。而每一行的代码由多个符号(Tokens)表示。可以使用反斜杠转义符在Token中插入空格。双引号可以将多个由空格分隔的Tokens合成一个Tokens。如果一行写不下,可以在行尾加上反斜杠,来连接下一行。也就是说,可以用反斜杠将多行代码连接成一行代码。

     AIL的注释与很多Shell脚本一行,以#开头。

     AIL在编写时需要分成多个部分(Section),而每一部分的开头需要指定Actions或Services。也就是说,每一个Actions或Services确定一个Section。而所有的Commands和Options只能属于最近定义的Section。如果Commands和Options在第一个Section之前被定义,它们将被忽略。

Actions和Services的名称必须唯一。如果有两个或多个Action或Service拥有同样的名称,那么init在执行它们时将抛出错误,并忽略这些Action和Service。

下面来看看Actions、Services、Commands和Options分别应如何设置。

Actions的语法格式如下:

on <trigger>  
   <command>  
   <command>  
   <command>

也就是说Actions是以关键字on开头的,然后跟一个触发器,接下来是若干命令。例如,下面就是一个标准的Action

on boot  
    ifup lo  
    hostname localhost  
    domainname localdomain

其中boot是触发器,下面三行是command

那么init.rc到底支持哪些触发器呢?目前init.rc支持如下5类触发器。

1.  boot

   这是init执行后第一个被触发Trigger,也就是在 /init.rc被装载之后执行该Trigger 

2.  <name>=<value>

   当属性<name>被设置成<value>时被触发。例如,

on property:vold.decrypt=trigger_reset_main

    class_reset main

3.  device-added-<path>

    当设备节点被添加时触发

4.  device-removed-<path>

   当设备节点被移除时添加

5. service-exited-<name>

   会在一个特定的服务退出时触发

Actions后需要跟若干个命令,这些命令如下:

1.  exec <path> [<argument> ]*

  创建和执行一个程序(<path>)。在程序完全执行前,init将会阻塞。由于它不是内置命令,应尽量避免使用exec ,它可能会引起init执行超时。

    2.  export <name> <value>

在全局环境中将 <name>变量的值设为<value>。(这将会被所有在这命令之后运行的进程所继承)

3.  ifup <interface>

   启动网络接口

4.  import <filename>

   指定要解析的其他配置文件。常被用于当前配置文件的扩展

5.  hostname <name>

   设置主机名

6.  chdir <directory>

   改变工作目录

7.  chmod <octal-mode><path>

   改变文件的访问权限

8.  chown <owner><group> <path>

   更改文件的所有者和组

9.  chroot <directory>

  改变处理根目录

10.  class_start<serviceclass>

   启动所有指定服务类下的未运行服务。

11  class_stop<serviceclass>

  停止指定服务类下的所有已运行的服务。

12.  domainname <name>

   设置域名

13.  insmod <path>

   加载<path>指定的驱动模块

14.  mkdir <path> [mode][owner] [group]

   创建一个目录<path> ,可以选择性地指定mode、owner以及group。如果没有指定,默认的权限为755,并属于root用户和 root组。

15. mount <type> <device> <dir> [<mountoption> ]*

   试图在目录<dir>挂载指定的设备。<device> 可以是mtd@name的形式指定一个mtd块设备。<mountoption>包括 "ro"、"rw"、"re

16.  setkey

   保留,暂时未用

17.  setprop <name><value>

   将系统属性<name>的值设为<value>。

18. setrlimit <resource> <cur> <max>

   设置<resource>的rlimit (资源限制)

19.  start <service>

   启动指定服务(如果此服务还未运行)。

20.stop<service>

   停止指定服务(如果此服务在运行中)。

21. symlink <target> <path>

   创建一个指向<path>的软连接<target>。

22. sysclktz <mins_west_of_gmt>

   设置系统时钟基准(0代表时钟滴答以格林威治平均时(GMT)为准)

23.  trigger <event>

  触发一个事件。用于Action排队

24.  wait <path> [<timeout> ]

等待一个文件是否存在,当文件存在时立即返回,或到<timeout>指定的超时时间后返回,如果不指定<timeout>,默认超时时间是5秒。

25. write <path> <string> [ <string> ]*

向<path>指定的文件写入一个或多个字符串。  

Services (服务)是一个程序,他在初始化时启动,并在退出时重启(可选)。Services (服务)的形式如下:

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

例如,下面是一个标准的Service用法

service servicemanager /system/bin/servicemanager  
    class core  
    user system  
    group system  
    critical  
    onrestart restart zygote  
    onrestart restart media  
    onrestart restart surfaceflinger  
    onrestart restart drm

Services的选项是服务的修饰符,可以影响服务如何以及怎样运行。服务支持的选项如下:

1.  critical

表明这是一个非常重要的服务。如果该服务4分钟内退出大于4次,系统将会重启并进入 Recovery (恢复)模式。

2. disabled

表明这个服务不会同与他同trigger (触发器)下的服务自动启动。该服务必须被明确的按名启动。

3.  setenv <name><value>

在进程启动时将环境变量<name>设置为<value>。

4.  socket <name><type> <perm> [ <user> [ <group> ] ]

   Create a unix domain socketnamed /dev/socket/<name> and pass

   its fd to the launchedprocess.  <type> must be"dgram", "stream" or "seqpacket".

   User and group default to0.

创建一个unix域的名为/dev/socket/<name> 的套接字,并传递它的文件描述符给已启动的进程。<type> 必须是 "dgram","stream" 或"seqpacket"。用户和组默认是0。

5.  user <username>

在启动这个服务前改变该服务的用户名。此时默认为 root。

6.  group <groupname> [<groupname> ]*

在启动这个服务前改变该服务的组名。除了(必需的)第一个组名,附加的组名通常被用于设置进程的补充组(通过setgroups函数),档案默认是root。

7.  oneshot

服务退出时不重启。

8.  class <name>

指定一个服务类。所有同一类的服务可以同时启动和停止。如果不通过class选项指定一个类,则默认为"default"类服务。

9. onrestart

当服务重启,执行一个命令(下详)。


init.rc脚本分析

在上一篇文章中说过,init将动作执行的时间划分为几个阶段,按照被执行的先后顺序依次为:early-init、init、early-boot、boot。在init进程的main()方法中被调用的先后顺序决定了它们的先后(直接原因), 根本原因是:各个阶段的任务不同导致后面的对前面的有依赖,所以这里的先后顺序是不能乱调整的。

@system/core/init/init.c

early-init主要用于设置init进程(进程号为1)的oom_adj的值,以及启动ueventd进程。oom_adj是Linux和Android中用来表示进程重要性的一个值,取值范围为[-17, 15]。在Android中系统在杀死进程时会根据oom_adj和空闲内存大小作为依据,oom_adj越大越容易被杀死。

Android将程序分成以下几类,按照重要性依次降低的顺序:

on early-init  
    # Set init and its forked children's oom_adj.  
    write /proc/1/oom_adj -16  
  
    # Set the security context for the init process.  
    # This should occur before anything else (e.g. ueventd) is started.  
    setcon u:r:init:s0  
  
    start ueventd

这里设置init进程的oom_adj的值为-16.这里要说明的是,我们现在分析的是init.rc文件,在文件头部我们发现还导入了其他的rc脚本。其他rc脚本中的文件结构与init.rc是类似的。在init进程解析rc脚本时,会将所有rc脚本中的配置安装执行阶段一并解析。即:init.rc中的early-init与init,${hardware}.rc中的early-init是一并解析的。

在执行完early-init以后,接下来就是init阶段。在init阶段主要用来:设置环境变量,创建和挂在文件节点。下面是init接的的不分代码截选:

@system/core/rootdir/init.environ.rc.in

set up the global environment  
on init  
    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 ANDROID_STORAGE /storage  
    export ASEC_MOUNTPOINT /mnt/asec  
    export LOOP_MOUNTPOINT /mnt/obb  
    export BOOTCLASSPATH %BOOTCLASSPATH%

以前设置环境变量这一段时在init.rc中的,现在放到了init.environ.rc.in,这样代码也更清晰一些。

@system/core/rootdir/init.rc

on init  
  
sysclktz 0  
  
loglevel 3  
  
# 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 /config 0500 root root

接下来是fs相关的几个过程,它们主要用于文件系统的挂载,下面是截取的一小部分代码:

on post-fs  
    # once everything is setup, no need to modify /  
    mount rootfs rootfs / ro remount  
    # mount shared so changes propagate into child namespaces  
    mount rootfs rootfs / shared rec  
    mount tmpfs tmpfs /mnt/secure private rec  
  
    # We chown/chmod /cache again so because mount is run as root + defaults  
    chown system cache /cache  
    chmod 0770 /cache  
    # We restorecon /cache in case the cache partition has been reset.  
    restorecon /cache

如果你看过以前的版本的init.rc脚本,看到这里会想起,应该还有几行:

mount yaffs2 mtd@system /system  
mount yaffs2 mtd@userdata /data

这两行用于挂载/system分区和/data分区到yaffs2文件系统。手机领域有多种不同的内存设备,其中NAND闪存设备以其低功耗、重量轻、性能佳等优良特性,受到绝大多数厂商的青睐。NAND闪存采用yaffs2文件系统。

可以看出在Android 4.4中默认已经不再使用yaffs2。在完成文件系统的创建和挂载后,完整的Android根文件系统结构如下:


接下来看一下boot部分,该部分主要用于设置应用程序终止条件,应用程序驱动目录及文件权限等。下面是一部分代码片段:

on 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  
    chmod 0664 /sys/module/lowmemorykiller/parameters/adj  
    chown root system /sys/module/lowmemorykiller/parameters/minfree  
    chmod 0664 /sys/module/lowmemorykiller/parameters/minfree  
  
    class_start core  
    class_start main

在on boot部分,我们可以发现许多”on property:<name> = <value>"的代码片段,这些是根据属性的触发器,但相应的属性满足一定的条件时,就会触发相应的动作。此外,还有许多service字段,service后面第一项表示服务的名称,第二项表示服务的路径,接下来的第2行等是服务的附加内容,配合服务使用,主要包含运行权限、条件以及重启等相关选项。


解析配置文件

前面了解了init.rc脚本的相关内容,接下来我们分析一下init进程是如何解析rc脚本的。首先,在init进程的main()函数中调用init_parse_config_file()函数对属性进行解析,下面就来看一下这个函数:

init_parse_config_file("/init.rc");//解析init.rc配置文件

@system/core/init/init_parser.c

int init_parse_config_file(const char *fn)  
{  
    char *data;  
    data = read_file(fn, 0);  
    if (!data) return -1;  
  
    parse_config(fndata);  
    DUMP();  
    return 0;  
}

read_file(fn, 0)函数将fn指针指向的路径(这里即:/init.rc)所对应的文件读取到内存中,保存为字符串形式,并返回字符串在内存中的地址;然后parse_config会对文件进行解析,生成动作列表(Action List)和服务列表(Service List)。关于read_file()函数的实现在@system/core/init/util.c中。下面是parse_config()的实现:

static void parse_config(const char *fn, char *s)  
{  
    struct parse_state state;  
    struct listnode import_list;//导入链表,用于保持在init.rc中通过import导入的其他rc文件  
    struct listnode *node;  
    char *args[INIT_PARSER_MAXARGS];  
    int nargs;  
  
  
    nargs = 0;  
    state.filename = fn;//初始化filename的值为init.rc文件  
    state.line = 0;//初始化行号为0  
    state.ptr = s;//初始化ptr指向s,即read_file读入到内存中的init.rc文件的首地址  
    state.nexttoken = 0;//初始化nexttoken的值为0  
    state.parse_line = parse_line_no_op;//初始化行解析函数  
  
  
    list_init(&import_list);  
    state.priv = &import_list;  
  
  
    for (;;) {  
        switch (next_token(&state)) {  
        case T_EOF://如果返回为T_EOF,表示init.rc已经解析完成,则跳到parser_done解析import进来的其他rc脚本  
            state.parse_line(&state, 00);  
            goto parser_done;  
        case T_NEWLINE:  
            state.line++;//一行读取完成后,行号加1  
            if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对这一行进行语法解析  
                int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword开头的,因此args[0]即表示这一行的keyword  
                if (kw_is(kw, SECTION)) {  
                    state.parse_line(&state, 00);  
                    parse_new_section(&state, kw, nargs, args);  
                } else {  
                    state.parse_line(&state, nargs, args);  
                }  
                nargs = 0;//复位  
            }  
            break;  
        case T_TEXT://将nexttoken解析的一个text保存到args字符串数组中,nargs的最大值为INIT_PARSER_MAXARGS(64),即init.rc中一行最多不能超过INIT_PARSER_MAXARGS个text(单词)  
            if (nargs < INIT_PARSER_MAXARGS) {  
                args[nargs++] = state.text;  
            }  
            break;  
        }  
    }  
  
  
parser_done:  
    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);  
    }  
}

parse_config()函数,代码虽然很短,实际上却比较复杂。接下来将对其进行详细分析。首先看一下struct parse_state的定义:

struct parse_state  
{  
    char *ptr;//指针,指向剩余的尚未被解析的数据(即:ptr指向当前解析到的位置)  
    char *text;//一行文本  
    int line; //行号   
    int nexttoken;//下一行的标示,T_EOF标示文件结束,T_TEXT表示需要进行解释的文本,T_NEWLINE标示一个空行或者是注释行  
    void *context;//一个action或者service  
    void (*parse_line)(struct parse_state *state, int nargs, char **args);//函数指针,指向当前行的解析函数  
    const char *filename;//解析的rc文件  
    void *priv;//执行import链表的指针  
};

next_token()以行为单位分割参数传递过来的字符串。

@system/core/init/parser.c

int next_token(struct parse_state *state)  
{  
    char *x = state->ptr;  
    char *s;  
  
  
    if (state->nexttoken) {//nexttoken的值为0  
        int t = state->nexttoken;  
        state->nexttoken = 0;  
        return t;  
    }  
  
  
    for (;;) {  
        switch (*x) {  
        case 0://到底末尾,解析完成  
            state->ptr = x;  
            return T_EOF;  
        case '\n'://换行符,返回T_NEWLINE,表示下一个token是新的一行  
            x++;  
            state->ptr = x;  
            return T_NEWLINE;  
        case ' '://忽略空格、制表符等  
        case '\t':  
        case '\r':  
            x++;  
            continue;  
        case '#'://在当前解析到的字符为#号时,将指针一直移动到#行的末尾,然后判断下一个字符是T_NEWLINE还是T_EOF  
            while (*x && (*x != '\n')) x++;//注意x++,当指针移动到#行末尾时,x执行末尾的下一个字符  
            if (*x == '\n') {  
                state->ptr = x+1;  
                return T_NEWLINE;  
            } else {  
                state->ptr = x;  
                return T_EOF;  
            }  
        default:  
            goto text;//解析的为普通文本  
        }  
    }  
  
  
textdone://x指向一个单词的开头位置,s指向末尾位置,将s设置为0(C字符串末尾为0),即表示单词结束  
    state->ptr = x;  
    *s = 0;  
    return T_TEXT;  
text:  
    state->text = s = x;  
textresume:  
    for (;;) {  
        switch (*x) {  
        case 0:  
            goto textdone;  
        case ' ':  
        case '\t':  
        case '\r':  
            x++;  
            goto textdone;  
        case '\n':  
            state->nexttoken = T_NEWLINE;  
            x++;  
            goto textdone;  
        case '"':  
            x++;  
            for (;;) {  
                switch (*x) {  
                case 0:  
                        /* unterminated quoted thing */  
                    state->ptr = x;  
                    return T_EOF;  
                case '"':  
                    x++;  
                    goto textresume;  
                default:  
                    *s++ = *x++;  
                }  
            }  
            break;  
        case '\\':  
            x++;  
            switch (*x) {  
            case 0:  
                goto textdone;  
            case 'n':  
                *s++ = '\n';  
                break;  
            case 'r':  
                *s++ = '\r';  
                break;  
            case 't':  
                *s++ = '\t';  
                break;  
            case '\\':  
                *s++ = '\\';  
                break;  
            case '\r':  
                    /* \ <cr> <lf> -> line continuation */  
                if (x[1] != '\n') {  
                    x++;  
                    continue;  
                }  
            case '\n':  
                    /* \ <lf> -> line continuation */  
                state->line++;  
                x++;  
                    /* eat any extra whitespace */  
                while((*x == ' ') || (*x == '\t')) x++;  
                continue;  
            default:  
                    /* unknown escape -- just copy */  
                *s++ = *x++;  
            }  
            continue;  
        default:  
            *s++ = *x++;  
        }  
    }  
    return T_EOF;  
}

在parse_config()中通过next_token从rc脚本中解析出一行行的rc语句,下面看一下另一个重要的函数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 'p':  
        if (!strcmp(s, "owerctl")) return K_powerctl;  
    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, "wapon_all")) return K_swapon_all;  
        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;  
    }  
    return K_UNKNOWN;  
}

lookup_keyword()主要用解析出args中的关键字,这个函数本身没有什么特别,也非常简单,但是其实现方法在我们自己实现类似通过switch等的查找判断时是值得借鉴的,即:先通过单词的首字母将内容分组,在定位到哪一个组后再依次比较。这样就减少了程序中比较的次数,提高了效率。

case T_NEWLINE:  
    state.line++;//一行读取完成后,行号加1  
    if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对这一行进行语法解析  
        int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword开头的,因此args[0]即表示这一行的keyword  
        if (kw_is(kw, SECTION)) {  
            state.parse_line(&state, 00);  
            parse_new_section(&state, kw, nargs, args);  
        } else {  
            state.parse_line(&state, nargs, args);  
        }  
        nargs = 0;//复位  
    }  
    break;

在parse_config()中,在找的keyword以后,接下来会判断这个keyword是否是section,是则走解析section的逻辑,否则走其他逻辑。下面我们看一下kw_is的实现:

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

可以看出kw_is只不过是一个宏定义,这里又引出了keyword_info,下面让我们一起来看一下keyword的相关定义:

关键字定义

@system/core/init/keywords.h

#ifndef KEYWORD//如果没有定义KEYWORD则执行下面的分支  
//声明一些函数,这些函数即Action的执行函数  
int 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_powerctl(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_swapon_all(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_wait(int nargs, char **args);  
#define __MAKE_KEYWORD_ENUM__//定义一个宏  
/* 
 * 定义KEYWORD宏,这里KEYWORD宏中有四个参数,其各自的含义如下: 
 * symbol表示keyword的名称(即init.rc中的关键字); 
 * flags表示keyword的类型,包括SECTION、COMMAND和OPTION三种类型,其定义在init_parser.c中; 
 * nargs表示参数的个数,即:该keyword需要几个参数 
 * func表示该keyword所对应的处理函数。 
 * 
 * KEYWORD宏虽然有四个参数,但是这里只用到了symbol,其中K_##symbol中的##表示连接的意思, 
 * 即最后的得到的值为K_symbol。 
 */  
#define KEYWORD(symbol, flags, nargs, func) K_##symbol,  
enum {  
    K_UNKNOWN,  
#endif  
    KEYWORD(capability,  OPTION,  00)//根据上面KEYWORD的宏定义,这一行就变成了K_capability,  
    KEYWORD(chdir,       COMMAND, 1, do_chdir)//key_chdir,后面的依次类推  
    KEYWORD(chroot,      COMMAND, 1, do_chroot)  
    KEYWORD(class,       OPTION,  00)  
    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,  00)  
    KEYWORD(critical,    OPTION,  00)  
    KEYWORD(disabled,    OPTION,  00)  
    KEYWORD(domainname,  COMMAND, 1, do_domainname)  
    KEYWORD(exec,        COMMAND, 1, do_exec)  
    KEYWORD(export,      COMMAND, 2, do_export)  
    KEYWORD(group,       OPTION,  00)  
    KEYWORD(hostname,    COMMAND, 1, do_hostname)  
    KEYWORD(ifup,        COMMAND, 1, do_ifup)  
    KEYWORD(insmod,      COMMAND, 1, do_insmod)  
    KEYWORD(import,      SECTION, 10)  
    KEYWORD(keycodes,    OPTION,  00)  
    KEYWORD(mkdir,       COMMAND, 1, do_mkdir)  
    KEYWORD(mount_all,   COMMAND, 1, do_mount_all)  
    KEYWORD(mount,       COMMAND, 3, do_mount)  
    KEYWORD(on,          SECTION, 00)  
    KEYWORD(oneshot,     OPTION,  00)  
    KEYWORD(onrestart,   OPTION,  00)  
    KEYWORD(powerctl,    COMMAND, 1, do_powerctl)  
    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,  00)  
    KEYWORD(service,     SECTION, 00)  
    KEYWORD(setcon,      COMMAND, 1, do_setcon)  
    KEYWORD(setenforce,  COMMAND, 1, do_setenforce)  
    KEYWORD(setenv,      OPTION,  20)  
    KEYWORD(setkey,      COMMAND, 0, do_setkey)  
    KEYWORD(setprop,     COMMAND, 2, do_setprop)  
    KEYWORD(setrlimit,   COMMAND, 3, do_setrlimit)  
    KEYWORD(setsebool,   COMMAND, 2, do_setsebool)  
    KEYWORD(socket,      OPTION,  00)  
    KEYWORD(start,       COMMAND, 1, do_start)  
    KEYWORD(stop,        COMMAND, 1, do_stop)  
    KEYWORD(swapon_all,  COMMAND, 1, do_swapon_all)  
    KEYWORD(trigger,     COMMAND, 1, do_trigger)  
    KEYWORD(symlink,     COMMAND, 1, do_symlink)  
    KEYWORD(sysclktz,    COMMAND, 1, do_sysclktz)  
    KEYWORD(user,        OPTION,  00)  
    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(ioprio,      OPTION,  00)  
#ifdef __MAKE_KEYWORD_ENUM__  
    KEYWORD_COUNT,  
};  
#undef __MAKE_KEYWORD_ENUM__  
#undef KEYWORD//取消KEYWORD宏的定义  
#endif

看一下keyword在init_parse.c中是如何被使用的:

#include "keywords.h"  
  
#define KEYWORD(symbol, flags, nargs, func) \  
    [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },  
  
struct {  
    const char *name;//关键字的名称  
    int (*func)(int nargs, char **args);//对应关键字的处理函数  
    unsigned char nargs;//参数个数,每个关键字的参数个数是固定的  
    unsigned char flags;//关键字属性,包括:SECTION、OPTION和COMMAND,其中COMMAND有对应的处理函数,见keyword的定义。  
} keyword_info[KEYWORD_COUNT] = {  
    [ K_UNKNOWN ] = { "unknown"000 },  
#include "keywords.h"  
};  
#undef KEYWORD  
  
#define kw_is(kw, type) (keyword_info[kw].flags & (type))  
#define kw_name(kw) (keyword_info[kw].name)  
#define kw_func(kw) (keyword_info[kw].func)  
#define kw_nargs(kw) (keyword_info[kw].nargs)

从上面的代码我们看到一个很有意思的地方,keyword.h头文件被包含引用了两次。

  • 第一次包含keywords.h时,它声明了一些诸如do_class_start的函数,另外还定义了一个枚举,枚举值为K_class、K_mkdir等关键字。

  • 第二次包含keywords.h后,得到了keyword_info结构体数组,这个keyword_info结构体数组以前定义的枚举值为索引,存储对应的关键字信息。

flags的取值也在init_parse.c中定义:

#define SECTION 0x01  
#define COMMAND 0x02  
#define OPTION  0x04

在了解了keyword后,下面我们继续来分析rc脚本的解析,让我们回到之前的代码,继续分析。

case T_NEWLINE:  
    state.line++;//一行读取完成后,行号加1  
    if (nargs) {//如果刚才解析的一行为语法行(非注释等),则nargs的值不为0,需要对这一行进行语法解析  
        int kw = lookup_keyword(args[0]);//init.rc中每一个语法行均是以一个keyword开头的,因此args[0]即表示这一行的keyword  
        if (kw_is(kw, SECTION)) {  
            state.parse_line(&state, 00);  
            parse_new_section(&state, kw, nargs, args);  
        } else {  
            state.parse_line(&state, nargs, args);  
        }  
        nargs = 0;//复位  
    }  
    break;

解析section的函数为parse_new_section,其实现为:

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) {  
    case K_service://解析Service  
        state->context = parse_service(state, nargs, args);//当service_list中不存在同名service时,执行新加入service_list中的service  
        if (state->context) {//service为新增加的service时,即:<span style="font-family: Arial, Helvetica, sans-serif;">service_list中不存在同名service</span>  
            state->parse_line = parse_line_service;//制定解析service行的函数为<span style="font-family: Arial, Helvetica, sans-serif;">parse_line_service</span>  
            return;  
        }  
        break;  
    case K_on://解析section  
        state->context = parse_action(state, nargs, args);  
        if (state->context) {  
            state->parse_line = parse_line_action;  
            return;  
        }  
        break;  
    case K_import://解析import  
        parse_import(state, nargs, args);  
        break;  
    }  
    state->parse_line = parse_line_no_op;  
}

先看一下service的解析:

static void *parse_service(struct parse_state *state, int nargs, char **args)  
{  
    struct service *svc;//保持Service相关信息  
    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;  
    }  
   //service_list中是否已存在同名service<span style="white-space:pre">    </span>  
    svc = service_find_by_name(args[1]);  
    if (svc) {//<span style="font-family: Arial, Helvetica, sans-serif;">如果已存在同名service则直接返回,不再做其他操作</span>  
        parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);  
        return 0;  
    }  
  
    nargs -= 2;  
    svc = calloc(1sizeof(*svc) + sizeof(char*) * nargs);  
    if (!svc) {  
        parse_error(state, "out of memory\n");  
        return 0;  
    }  
    svc->name = args[1];  
    svc->classname = "default";//设置classname为“default”  
    memcpy(svc->args, args + 2sizeof(char*) * nargs);  
    svc->args[nargs] = 0;  
    svc->nargs = nargs;  
    svc->onrestart.name = "onrestart";  
    list_init(&svc->onrestart.commands);  
    list_add_tail(&service_list, &svc->slist);//将service添加到全局链表service_list中  
    return svc;  
}

init中使用了一个叫做service的结构体来保存与service相关的信息。
@system/core/init/init.h

struct service {  
        /* list of all services */  
    struct listnode slist;//双向链表  
  
    const char *name;//service的名字  
    const char *classname;//service所属class的名字,默认是“default”  
  
    unsigned flags;//service的属性  
    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;  
  
    char *seclabel;  
  
    struct socketinfo *sockets;//有些service需要使用socket,socketinfo用来描述socket相关信息  
    struct svcenvinfo *envvars;//service一般运行在一个单独的进程中,envvars用来描述创建这个进程时所需的环境变量信息  
    //关键字onrestart标示一个OPTION,可是onrestart后面一般跟着COMMAND,下面这个action结构体可用来存储command信息  
    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];//用于存储参数  
}; /*     ^-------'args' MUST be at the end of this struct! */

从parse_service函数可以看出,它的作用就是讲service添加到service_list列表中,并制定解析函数为parse_line_service,也就是说具体的service的解析靠的是parse_line_service方法。

static void parse_line_service(struct parse_state *state, int nargs, char **args)  
{  
    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) {  
    case K_capability:  
        break;  
    case K_class:  
        if (nargs != 2) {  
            parse_error(state, "class option requires a classname\n");  
        } else {  
            svc->classname = args[1];  
        }  
        break;  
    case K_console:  
        svc->flags |= SVC_CONSOLE;  
        break;  
    case K_disabled:  
        svc->flags |= SVC_DISABLED;  
        svc->flags |= SVC_RC_DISABLED;  
        break;  
    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], 08);  
  
            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;  
    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;  
    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;  
    case K_oneshot:  
        svc->flags |= SVC_ONESHOT;  
        break;  
    case K_onrestart:  
        nargs--;  
        args++;  
        kw = lookup_keyword(args[0]);  
        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;  
        }  
  
        cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);  
        cmd->func = kw_func(kw);  
        cmd->nargs = nargs;  
        memcpy(cmd->args, args, sizeof(char*) * nargs);  
        list_add_tail(&svc->onrestart.commands, &cmd->clist);  
        break;  
    case K_critical:  
        svc->flags |= SVC_CRITICAL;  
        break;  
    case K_setenv: { /* name value */  
        struct svcenvinfo *ei;  
        if (nargs < 2) {  
            parse_error(state, "setenv option requires name and value arguments\n");  
            break;  
        }  
        ei = calloc(1, sizeof(*ei));  
        if (!ei) {  
            parse_error(state, "out of memory\n");  
            break;  
        }  
        ei->name = args[1];  
        ei->value = args[2];  
        ei->next = svc->envvars;  
        svc->envvars = ei;  
        break;  
    }  
    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;  
        }  
        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], 08);  
        if (nargs > 4)  
            si->uid = decode_uid(args[4]);  
        if (nargs > 5)  
            si->gid = decode_uid(args[5]);  
        si->next = svc->sockets;  
        svc->sockets = si;  
        break;  
    }  
    case K_user:  
        if (nargs != 2) {  
            parse_error(state, "user option requires a user id\n");  
        } else {  
            svc->uid = decode_uid(args[1]);  
        }  
        break;  
    case K_seclabel:  
        if (nargs != 2) {  
            parse_error(state, "seclabel option requires a label string\n");  
        } else {  
            svc->seclabel = args[1];  
        }  
        break;  
  
    default:  
        parse_error(state, "invalid option '%s'\n", args[0]);  
    }  
}

可以看出parse_line_service中会根据keyword找的对应的keyword的处理函数,具体进程处理。
section的处理与service类似,通过分析init.rc的解析过程,我们知道,所谓的解析就是将rc脚本中的内容通过解析,填充到service_list和action_list中去。那他们是在哪里进行调用的呢,让我们回忆一下init进程中main函数的实现。

INFO("reading config file\n");  
init_parse_config_file("/init.rc");//解析init.rc配置文件  
  
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);  
  
/* skip mounting filesystems in charger mode */  
if (!is_charger) {  
    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);  
    action_for_each_trigger("post-fs-data", action_add_queue_tail);  
}

init.rc脚本的解析就完了。

Android Init Language怎么回事儿? 其实也不难,主要关注是 action和service就好了,官方文件:

system/core/init/readme.txt文件,这是关于rc脚本的完整的语法介绍,具体就不摘录了。


我转载,我又来插嘴:

编译后在手机中的init可执行文件不同于,源码中的实现文件/system/core/init/init.c,

源码目录中/system/core/rootdir/init.rc 在手机的根目录也存在。


init进程讲init.rc中的动作划分为了early-init,init,early-boot,boot等几个阶段。early-init主要用于设置init进程(进程号为1)的oom_adj的值,以及启动ueventd进程。oom_adj是Linux和Android中用来表示进程重要性的一个值,取值范围为[-17, 15]。在Android中系统在杀死进程时会根据oom_adj和空闲内存大小作为依据,oom_adj越大越容易被杀死。

 # Set init and its forked children's oom_adj.  

    write /proc/1/oom_adj -16 


在on boot阶段之前,手机的文件系统已经建立完毕

在on boot阶段设置了一系列的属性触发器,以及一些列的本地服务

所谓的解析就是将rc脚本中的内容通过解析,填充到service_list和action_list中去。




Android中的属性主要用来保存一些全局性的信息,这里可以理解为Android中的“注册表”。Android中的属性服务只针对系统开发者使用,并不对应用开发者开发,这通过SystemProperties是hide的可以看出。下面让我们一起来剖析属性服务。


初始化属性空间

在init进程启动一文中我们讲到,init的其中一个作用就是启动系统属性服务。在init进程的main()函数中有这样一句:

@system/core/init/init.c

property_init();//属性服务初始化

property_init()的实现如下:

@system/core/init/property_service.c

void property_init(void)  
{  
    init_property_area();  
}

从字面意思来看init_property_area是用来初始化属性存储区域,让我们来看一下:

@system/core/init/property_service.c

static int init_property_area(void)  
{  
    if (property_area_inited)//属性区域已初始化则直接返回,不再初始化  
        return -1;  
  
    if(__system_property_area_init())//通过mmap创建共享内存  
        return -1;  
  
    if(init_workspace(&pa_workspace, 0))//初始化pa_workspace  
        return -1;  
  
    fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);  
  
    property_area_inited = 1;  
    return 0;  
}

通过上面的代码我们可以知道,属性服务是创建在共享内存上的,通过共享内存实现跨进程的访问。那共享内存是如何创建的呢?来看一下__system_property_area_init的实现:

@/bionic/libc/bionic/system_properties.c

int __system_property_area_init()  
{  
    return map_prop_area_rw();  
}
static int map_prop_area_rw()  
{  
    prop_area *pa;  
    int fd;  
    int ret;  
  
    /* dev is a tmpfs that we can use to carve a shared workspace 
     * out of, so let's do that... 
     */  
    fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |  
            O_EXCL, 0444);//打开property_filename文件,即:/dev/__properties__  
    if (fd < 0) {  
        if (errno == EACCES) {  
            /* for consistency with the case where the process has already 
             * mapped the page in and segfaults when trying to write to it 
             */  
            abort();  
        }  
        return -1;  
    }  
  
    ret = fcntl(fd, F_SETFD, FD_CLOEXEC);//这里设置为FD_CLOEXEC表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递给exec创建的新进程  
    if (ret < 0)  
        goto out;  
  
    if (ftruncate(fd, PA_SIZE) < 0)//更改fd指向文件的大小为PA_SIZE(128 * 1024)大小  
        goto out;  
  
    pa_size = PA_SIZE;//设置属性空间的大小为PA_SIZE(128 * 1024)  
    pa_data_size = pa_size - sizeof(prop_area);//属性空间中可以用来保存属性的区域的大小,prop_area用来保存属性空间自身的一些信息  
    compat_mode = false;  
  
    pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//映射属性文件到进程  
    if(pa == MAP_FAILED)  
        goto out;  
  
    memset(pa, 0, pa_size);//初始化属性区域  
    pa->magic = PROP_AREA_MAGIC;  
    pa->version = PROP_AREA_VERSION;  
    /* reserve root node */  
    pa->bytes_used = sizeof(prop_bt);  
  
    /* plug into the lib property services */  
    __system_property_area__ = pa;  
  
    close(fd);  
    return 0;  
  
out:  
    close(fd);  
    return -1;  
}

上面的内容比较简单,不过最后的赋值语句可是大有来头。_system_property_area_是bionic libc库中输出的一个变量,为什么这里要给她赋值呢?

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

  • 把属性区域创建在共享内存上,而共享内存是可以跨进程的。

  • 如何让其他进程知道这个共享内存呢?Android利用gcc的constructor属性,这个属性指明了一个_libc_prenit函数,当bionic libc库被加载时,将自动调用这个_libc_prenit,这个函数内部完成共享内存到本地进程的映射工作。(这一段说明转自:《深入理解Android:卷1》)

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

@/bionic/libc/bionic/libc_init_dynamic.cpp

// We flag the __libc_preinit function as a constructor to ensure  
// that its address is listed in libc.so's .init_array section.  
// This ensures that the function is called by the dynamic linker  
// as soon as the shared library is loaded.  
__attribute__((constructor)) static void __libc_preinit() {  
  // Read the kernel argument block pointer from TLS.  
  void* tls = const_cast<void*>(__get_tls());  
  KernelArgumentBlock** args_slot = &reinterpret_cast<KernelArgumentBlock**>(tls)[TLS_SLOT_BIONIC_PREINIT];  
  KernelArgumentBlock* args = *args_slot;  
  
  // Clear the slot so no other initializer sees its value.  
  // __libc_init_common() will change the TLS area so the old one won't be accessible anyway.  
  *args_slot = NULL;  
  
  __libc_init_common(*args);  
  
  // Hooks for the debug malloc and pthread libraries to let them know that we're starting up.  
  pthread_debug_init();  
  malloc_debug_init();  
}

@/bionic/libc/bionic/libc_init_common.cpp

void __libc_init_common(KernelArgumentBlock& args) {  
  // Initialize various globals.  
  environ = args.envp;  
  errno = 0;  
  __libc_auxv = args.auxv;  
  __progname = args.argv[0] ? args.argv[0] : "<unknown>";  
  __abort_message_ptr = args.abort_message_ptr;  
  
  // AT_RANDOM is a pointer to 16 bytes of randomness on the stack.  
  __stack_chk_guard = *reinterpret_cast<uintptr_t*>(getauxval(AT_RANDOM));  
  
  // Get the main thread from TLS and add it to the thread list.  
  pthread_internal_t* main_thread = __get_thread();  
  main_thread->allocated_on_heap = false;  
  _pthread_internal_add(main_thread);  
  
  __system_properties_init(); // Requires 'environ'.  
}

@/bionic/libc/bionic/system_properties.c

int __system_properties_init()  
{  
    return map_prop_area();  
}


static int map_prop_area()  
{  
    bool fromFile = true;  
    int result = -1;  
    int fd;  
    int ret;  
  
    fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);  
    if (fd >= 0) {  
        /* For old kernels that don't support O_CLOEXEC */  
        ret = fcntl(fd, F_SETFD, FD_CLOEXEC);  
        if (ret < 0)  
            goto cleanup;  
    }  
  
    if ((fd < 0) && (errno == ENOENT)) {  
        /* 
         * For backwards compatibility, if the file doesn't 
         * exist, we use the environment to get the file descriptor. 
         * For security reasons, we only use this backup if the kernel 
         * returns ENOENT. We don't want to use the backup if the kernel 
         * returns other errors such as ENOMEM or ENFILE, since it 
         * might be possible for an external program to trigger this 
         * condition. 
         */  
        fd = get_fd_from_env();  
        fromFile = false;  
    }  
  
    if (fd < 0) {  
        return -1;  
    }  
  
    struct stat fd_stat;  
    if (fstat(fd, &fd_stat) < 0) {  
        goto cleanup;  
    }  
  
    if ((fd_stat.st_uid != 0)  
            || (fd_stat.st_gid != 0)  
            || ((fd_stat.st_mode & (S_IWGRP | S_IWOTH)) != 0)  
            || (fd_stat.st_size < sizeof(prop_area)) ) {  
        goto cleanup;  
    }  
  
    pa_size = fd_stat.st_size;  
    pa_data_size = pa_size - sizeof(prop_area);  
    prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);  
  
    if (pa == MAP_FAILED) {  
        goto cleanup;  
    }  
  
    if((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION &&  
                pa->version != PROP_AREA_VERSION_COMPAT)) {  
        munmap(pa, pa_size);  
        goto cleanup;  
    }  
  
    if (pa->version == PROP_AREA_VERSION_COMPAT) {  
        compat_mode = true;  
    }  
  
    result = 0;  
  
    __system_property_area__ = pa;  
  
cleanup:  
    if (fromFile) {  
        close(fd);  
    }  
  
    return result;  
}


对比上面map_prop_area_rw()和map_prop_area()这两个方法,我们可以发现他们在打开文件和进行映射时的不同。在map_prop_area_rw()中:

static int map_prop_area_rw()  
{  
    ......  
  
    fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC |  
            O_EXCL, 0444);  
      
    ......  
          
    pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);//映射属性文件到进程  
  
    ......  
}

在map_prop_area()中:

static int map_prop_area()  
{  
    ......  
  
    fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);  
      
    ......  
      
    prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);  
  
    ......  
}

map_prop_area_rw()是init进程初始化属性服务时调用到的;map_prop_area()则是其他进程通过bionic初始化时调用的。通过对比,也说明只有init进程才拥有属性的写权限,其他进程只能读。好了,到这里又引出来另外一个问题,其他进程要想设置属性,应该怎么做呢?

属性服务

启动属性服务

在init进程的main()中,我们可以看见如下语句:

@system/core/init/init.c

queue_builtin_action(property_service_init_action"property_service_init");

这句话的意思是启动action链表中的property服务:

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;  
}

start_property_service()的实现如下:

@system/core/init/property_service.c

void start_property_service(void)  
{  
    int fd;  
  
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);  
    load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);  
    load_override_properties();  
    /* Read persistent properties after all default values have been loaded. */  
    load_persistent_properties();  
  
    fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 066600);  
    if(fd < 0return;  
    fcntl(fd, F_SETFD, FD_CLOEXEC);  
    fcntl(fd, F_SETFL, O_NONBLOCK);  
  
    listen(fd, 8);  
    property_set_fd = fd;  
}

关于这段代码做一些说明:

在Android中定义了5个存储属性的文件,它们分别是:

@/bionic/libc/includes/sys/_system_properties.h

#define PROP_PATH_RAMDISK_DEFAULT  "/default.prop"  
#define PROP_PATH_SYSTEM_BUILD     "/system/build.prop"  
#define PROP_PATH_SYSTEM_DEFAULT   "/system/default.prop"  
#define PROP_PATH_LOCAL_OVERRIDE   "/data/local.prop"  
#define PROP_PATH_FACTORY          "/factory/factory.prop"

load_persistent_properties()用来加载persist开头的属性文件,这些属性文件是需要保存到永久介质上的,这些属性文件存储在/data/property目录下,并且文件名必须以“psesist."开头,下面是我的手机上的persist属性文件:


在start_property_service()的最后创建了一个名为"property_service"的socket,启动监听,并将socket的句柄保存在property_set_fd中。


处理设置属性请求

接收属性设置请求的地方在init进程中:

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();  
    }  
}

可以看到,在init接收到其他进程设置属性的请求时,会调用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;  
  
    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;  
    }  
  
    r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0));  
    if(r != sizeof(prop_msg)) {  
        ERROR("sys_prop: mis-match msg size received: %d expected: %d 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);  
  
        if(memcmp(msg.name,"ctl.",4) == 0) {  
            // Keep the old close-socket-early behavior when handling  
            // ctl.* properties.  
            close(s);  
            if (check_control_perms(msg.value, cr.uid, cr.gid, 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 {  
            if (check_perms(msg.name, cr.uid, cr.gid, 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;  
    }  
}


从上面的代码可以看出在进行一系列检查和判断后,最后会调用property_set()函数设置属性,其实现如下:

int property_set(const char *name, const char *value)  
{  
    prop_info *pi;  
    int ret;  
  
    size_t namelen = strlen(name);  
    size_t valuelen = strlen(value);  
  
    if (!is_legal_property_name(name, namelen)) return -1;  
    if (valuelen >= PROP_VALUE_MAX) return -1;  
  
    pi = (prop_info*) __system_property_find(name);  
  
    if(pi != 0) {  
        /* ro.* properties may NEVER be modified once set */  
        if(!strncmp(name, "ro."3)) return -1;  
  
        __system_property_update(pi, value, valuelen);  
    } else {  
        ret = __system_property_add(name, namelen, value, valuelen);  
        if (ret < 0) {  
            ERROR("Failed to set '%s'='%s'\n", name, value);  
            return ret;  
        }  
    }  
    /* If name starts with "net." treat as a DNS property. */  
    if (strncmp("net.", name, strlen("net.")) == 0)  {  
        if (strcmp("net.change", name) == 0) {  
            return 0;  
        }  
       /* 
        * The 'net.change' property is a special property used track when any 
        * 'net.*' property name is updated. It is _ONLY_ updated here. Its value 
        * contains the last updated 'net.*' property. 
        */  
        property_set("net.change", name);  
    } else if (persistent_properties_loaded &&  
            strncmp("persist.", name, strlen("persist.")) == 0) {  
        /* 
         * Don't write properties to disk until after we have read all default properties 
         * to prevent them from being overwritten by default values. 
         */  
        write_persistent_property(name, value);  
    } else if (strcmp("selinux.reload_policy", name) == 0 &&  
               strcmp("1", value) == 0) {  
        selinux_reload_policy();  
    }  
    property_changed(name, value);  
    return 0;  
}

从上面的代码可以看出:首先会判断所设置属性的属性名和属性值是否超过限制,这个限制定义如下:

@bionic/libc/includes/sys/system _properties.h

#define PROP_NAME_MAX   32  
#define PROP_VALUE_MAX  92

可以看出属性名最长不能超过32个字符;属性值最长不能超过92个字符,到这里我们也可以根据之前map_prop_area_rw()中的信息推断出,系统总共可以支持多少个属性:

pa_size = PA_SIZE;//设置属性空间的大小为PA_SIZE(128 * 1024)  
//属性空间中可以用来保存属性的区域的大小,prop_area用来保存属性空间自身的一些信息  
pa_data_size = pa_size - sizeof(prop_area);
compat_mode = false;

按照这里的推断,系统支持1000多个属性的样子。这里与之前比较老的Android版本有所不同,在老的Android版本中系统最多支持的属性个数为247个。

在判断文新添加属性的合法性以后接下来会判断该属性是否以存在,如果已存在则更新属性的值即可,如果不存在则增加新的属性。

然后还有剩下的一些其他判断,这里就不再赘述。到时最后一句property_changed()引起了我的注意:

property_changed(name, value);

看到这里,你有没有想起些什么?是的,init.rc中类似于下面这样的句子就是在这里通过property_changed()触发的。

on property:vold.decrypt=trigger_restart_min_framework  
    class_start main  
  
on property:vold.decrypt=trigger_restart_framework  
    class_start main  
    class_start late_start

到这里属性服务相关的东东基本介绍完了。接下来简单介绍一下如何设置系统属性。


插嘴时间到:

Android中的属性主要用来保存一些全局性的信息,这里可以理解为Android中的“注册表”。

SystemProperties在SDK中是隐藏的。


虽然属性区域是由init进程创建的,但是Android系统希望其他进程也能够读取这块内存的东西。

把属性区域创建在共享内存上,而共享内存是可以跨进程的。


在Android中定义了5个存储属性的文件 (手机目录)

/default.prop

/system/build.prop

/system/default.prop

/data/local.prop

/factory/factory.prop


在/data/property下面以persist开头的都是属性文件。


由于只有init进程拥有写属性的权限,所以init进程也接受其他进程的设置属性请求。

(设置属性的最后也还会出发一些在init.rc中声明的动作action)


设置系统属性

C代码中属性的设置是由libcutils库提供的,代码如下:

@system/core/libutils/properties.c

int property_set(const char *key, const char *value)  
{  
    return __system_property_set(key, value);  
}

@/bionic/libc/bionic/system_property

int __system_property_set(const char *key, const char *value)  
{  
    int err;  
    prop_msg msg;  
  
    if(key == 0return -1;  
    if(value == 0value = "";  
    if(strlen(key) >= PROP_NAME_MAX) return -1;  
    if(strlen(value) >= PROP_VALUE_MAX) return -1;  
  
    memset(&msg, 0sizeof msg);  
    msg.cmd = PROP_MSG_SETPROP;  
    strlcpy(msg.name, key, sizeof msg.name);  
    strlcpy(msg.valuevaluesizeof msg.value);  
  
    err = send_prop_msg(&msg);  
    if(err < 0) {  
        return err;  
    }  
  
    return 0;  
}

注意这里的msg.cmd = PROP_MSG_SETPROP,send_prop_msg()的实现如下:

static int send_prop_msg(prop_msg *msg)  
{  
    struct pollfd pollfds[1];  
    struct sockaddr_un addr;  
    socklen_t alen;  
    size_t namelen;  
    int s;  
    int r;  
    int result = -1;  
  
    s = socket(AF_LOCAL, SOCK_STREAM, 0);  
    if(s < 0) {  
        return result;  
    }  
  
    memset(&addr, 0sizeof(addr));  
    namelen = strlen(property_service_socket);  
    strlcpy(addr.sun_path, property_service_socket, sizeof addr.sun_path);  
    addr.sun_family = AF_LOCAL;  
    alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;  
  
    if(TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) {  
        close(s);  
        return result;  
    }  
  
    r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));  
  
    if(r == sizeof(prop_msg)) {  
        // We successfully wrote to the property server but now we  
        // wait for the property server to finish its work.  It  
        // acknowledges its completion by closing the socket so we  
        // poll here (on nothing), waiting for the socket to close.  
        // If you 'adb shell setprop foo bar' you'll see the POLLHUP  
        // once the socket closes.  Out of paranoia we cap our poll  
        // at 250 ms.  
        pollfds[0].fd = s;  
        pollfds[0].events = 0;  
        r = TEMP_FAILURE_RETRY(poll(pollfds, 1250 /* ms */));  
        if (r == 1 && (pollfds[0].revents & POLLHUP) != 0) {  
            result = 0;  
        } else {  
            // Ignore the timeout and treat it like a success anyway.  
            // The init process is single-threaded and its property  
            // service is sometimes slow to respond (perhaps it's off  
            // starting a child process or something) and thus this  
            // times out and the caller thinks it failed, even though  
            // it's still getting around to it.  So we fake it here,  
            // mostly for ctl.* properties, but we do try and wait 250  
            // ms so callers who do read-after-write can reliably see  
            // what they've written.  Most of the time.  
            // TODO: fix the system properties design.  
            result = 0;  
        }  
    }  
  
    close(s);  
    return result;  
}

设置属性的请求在这里被通过socket发送出去。

Java层设置属性的方法位于SystemProperties类中,在该类中实现了一系列set/get方法,Java代码中就是通过这些set/get方法设置和读取系统属性的。

@/framework/base/core/ java/android/os/SystemProperties.java

/** 
 * Set the value for the given key. 
 * @throws IllegalArgumentException if the key exceeds 32 characters 
 * @throws IllegalArgumentException if the value exceeds 92 characters 
 */  
public static void set(String key, String val) {  
    if (key.length() > PROP_NAME_MAX) {  
        throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);  
    }  
    if (val != null && val.length() > PROP_VALUE_MAX) {  
        throw new IllegalArgumentException("val.length > " +  
            PROP_VALUE_MAX);  
    }  
    native_set(key, val);  
}

注意到这里最终会调用到native的set方法。

private static native void native_set(String key, String def);

看到这里,接下来会调到那里去,相信大家应该都清楚了。。。(hehe   : ) )



init进程总算看完了。

请移步到最上端,然后再看一遍,如此两边之后,记住那个流程图,以及init的功能,以及各阶段的工作。

这样就算告一段落了。



我的总结:

init 进程的作用:

1. 作为守护线程处理一些事件(属性服务事件、keychord事件和SIGNAL)

2. 挂在手机上的设备节点(目录)

3. 解析并执行init.rc 设置一些动作,跑起一些本地服务

4. 提供属性的设置服务(控制系统的全局变量)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值