Android-4.0.3-init.c启动源码分析

作为第一个内核启动的进程,init进程初始了化安卓运行环境。执行了文件夹建立,文件系统挂载,全局property初始化,建立监听 socket,根据init.rc文件启动service,执行action,等一系列操作,下面来具体看看在Android 4.0.3中的源代码具体实现:

main函数:

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;

    if (!strcmp(basename(argv[0]), "ueventd"))
        return ueventd_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", 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));

        /* 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();
    klog_init();

    INFO("reading config file\n");
    init_parse_config_file("/init.rc");

    /* pull the kernel commandline and ramdisk properties file in */
    import_kernel_cmdline(0, import_kernel_nv);
    /* don't expose the raw commandline to nonpriv processes */
    chmod("/proc/cmdline", 0440);
    get_hardware_name(hardware, &revision);
    snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
    init_parse_config_file(tmp);

    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(property_init_action, "property_init");
    queue_builtin_action(keychord_init_action, "keychord_init");
    queue_builtin_action(console_init_action, "console_init");
    queue_builtin_action(set_init_properties_action, "set_init_properties");

    /* 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 (strcmp(bootmode, "charger") != 0) {
        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);
    }

    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, "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_propety_triggers");


#if BOOTCHART
    queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif

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

从main函数中可以看到init程序具体做了一下几件事:

1.创建相应的文件目录,如:各种mkdir操作

2.挂载相应的文件系统,如:各种mount操作

3.初始化日志系统,打开/dev/null作为输出设备等系统初始化操作

4.解析init.rc文件操作,如:init_parse_config_file("/init.rc");

5.配置安卓系统环境变量操作,如:import_kernel_cmdline(0, import_kernel_nv);

6.添加各种action操作,如:action_for_each_trigger,queue_builtin_action;

7.循环执行action:execute_one_command;

8.启动或重启各种service:restart_processes;

9.poll相应的文件描述符,包括property service、signal和keychord对应的文件描述符。


下面来具体分析各项工作的实现细节:

1-3:这三个步骤较为简单,主要是一些linux系统调用,不具体阐述。


4:调用init_parse_config_file函数读取/init.rc中的配置信息,初始化action和service等,该函数的实现如下:

init_parse_config_file

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

    parse_config(fn, data);
    DUMP();
    return 0;
}
可以看到函数定义了一个char*指针,然后调用 read_file函数将文件内容载入内存中并返回给data,并调用 parse_config函数进行解析。

read_file函数的逻辑较为简单,实现代码如下:

read_file

void *read_file(const char *fn, unsigned *_sz)
{
    char *data;
    int sz;
    int fd;

    data = 0;
    fd = open(fn, O_RDONLY);
    if(fd < 0) return 0;

    sz = lseek(fd, 0, SEEK_END);
    if(sz < 0) goto oops;

    if(lseek(fd, 0, SEEK_SET) != 0) goto oops;

    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;
}
函数首先通过open系统调用打开文件,然后调用lseek将文件定位到文件末端,返回的值表示文件的长度存放在变量sz中,然后在调用lseek重新定位到文件头部。接下来调用malloc申请大小为文件长度加上2的内存空间,并通过read将文件内容全部读入到内存,并把剩余两个字节分别置为换行和0,方便下面的文件内容解析操作,最后返回data。

将文件加载之后,通过parse_config对文件内容进行解析,这个解析过程有点意思,具体细节可以参考parse_config。简单地说,parse_config函数对init.rc文件中的service标签建立对应的service结构体,插入service_list;对on标签建立相应的action,设置action对应的commands以及将action插入action_list;对import标签则调用init_parse_config_file递归加载对应的文件内容,你可以理解为一条import是一个新的文件,在init.rc中将其替换为相应的文件内容进行解析。

另外在第5个操作之后,还会根据全局变量hardware再次载入一个rc文件并解析。


5:这个步骤主要调用的是import_kernel_cmdline(0, import_kernel_nv),这是第一次调用该方法,读取/proc/cmdline文件,然后根据空格将文件内容分割,每一部分通过调用import_nernel_nv函数,传入的参数是一个name=value类型的字符串,根据字符串将全局变量qemu、console、bootmode、serialno、baseband、carrier、bootloader、hardware设置成文件中相应的值。

static void import_kernel_nv(char *name, int in_qemu)
{
    char *value = strchr(name, '=');

    if (value == 0) return;
    *value++ = 0;
    if (*name == 0) return;

    if (!in_qemu)
    {
        /* on a real device, white-list the kernel options */
        if (!strcmp(name,"qemu")) {
            strlcpy(qemu, value, sizeof(qemu));
        } else if (!strcmp(name,"androidboot.console")) {
            strlcpy(console, value, sizeof(console));
        } else if (!strcmp(name,"androidboot.mode")) {
            strlcpy(bootmode, value, sizeof(bootmode));
        } else if (!strcmp(name,"androidboot.serialno")) {
            strlcpy(serialno, value, sizeof(serialno));
        } else if (!strcmp(name,"androidboot.baseband")) {
            strlcpy(baseband, value, sizeof(baseband));
        } else if (!strcmp(name,"androidboot.carrier")) {
            strlcpy(carrier, value, sizeof(carrier));
        } else if (!strcmp(name,"androidboot.bootloader")) {
            strlcpy(bootloader, value, sizeof(bootloader));
        } else if (!strcmp(name,"androidboot.hardware")) {
            strlcpy(hardware, value, sizeof(hardware));
        }
    } else {
        /* in the emulator, export any kernel option with the
         * ro.kernel. prefix */
        char  buff[32];
        int   len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );
        if (len < (int)sizeof(buff)) {
            property_set( buff, value );
        }
    }
}

可以理解为从cmdline文件中设置系统参数。/proc/cmdline文件中的内容是一个name=value的形式,例如我手机上的文件如下:


6:这里主要有两个函数与action操作有关:

A:action_for_each_trigger(const char *name, void (*func)(struct action *act));

该函数会遍历action_list(现在action_list中的action都是根据.rc文件中添加的),对于每个名称等于“name”的action,调用action_add_queue_tail将其添加到action queue。注意到这里有两个action存储结构,action_list存储的是系统中所有的action,是静态的,而如果这些action需要被执行,必须添加到action queue中去,系统只执行位于队列中的action。

B:queue_builtin_action(int (*func)(int nargs, char **args), char *name);

该函数会根据参数name创建一个action结构,并根据函数指针func创建一个command结构,将其添加到action的command list中去,最后再把这个action添加到action list中去。


可以看到,除了rc文件中的action外,安卓额外还添加了一些重要的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(property_init_action, "property_init");
    queue_builtin_action(keychord_init_action, "keychord_init");
    queue_builtin_action(console_init_action, "console_init");
    queue_builtin_action(set_init_properties_action, "set_init_properties");

    /* 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 (strcmp(bootmode, "charger") != 0) {
        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);
    }

    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, "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);
    }

从各个action的名字也可以看出其相应的功能,由于安卓的property机制较为重要,这里只介绍property相对应的action,对于console和keychord的实现过程其实也是类似的。

property有点类似于windows中的注册表,保存系统的一些环境变量信息,通过property_getproperty_set函数进行取值和设值。在实现中为了考虑效率,get是直接从共享内存中读取数据,而init进程中的set函数也是直接操作共享内存进行设值,但是当property service启动之后(其实就init程序本身,在property area初始化完成之后init便作为一个property service身份运行着),其他的程序调用set函数是通过socket发送消息给property service,然后由property service来直接修改共享内存中的service。



property_init_action这个action会初始化property area,实际上调用的是property_init

property_init首先调用init_property_area初始化property机制需要用到的共享内存区域,最后调用的是init_workspace函数,init_workspace函数首先打开或创建一个文件/dev/__properties__,并调用truncate函数将其大小调整为32KB,然后调用mmap把这个文件映射到内存空间,把这个内存地址赋值给全局变量pa_workplace中的data,也赋值给__system_property_area__,达到共享内存的效果。这个command被执行之后,property area就被初始化了,全局变量property_area_inited置为1。

property_init然后会调用load_properties_from_file,从文件中初始化一些property。依次调用的是load_propertiesproperty_set,这里的property_set函数不同于后期其他程序调用property_set,它是定义在property_service.c文件中,因为在初始化property时还没有创建相应的服务,所以只能直接对共享内存区域进行操作,后期的set函数是bionic库下的set函数,通过socket将消息发送给property service间接设定property


set_init_properties_action,该action也是设定一些property,主要是根据文件/proc/cmdline文件内容来设置,实现代码如下:

static int set_init_properties_action(int nargs, char **args)
{
    char tmp[PROP_VALUE_MAX];

    if (qemu[0])
        import_kernel_cmdline(1, import_kernel_nv);

    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");

    property_set("ro.serialno", serialno[0] ? serialno : "");
    property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");
    property_set("ro.baseband", baseband[0] ? baseband : "unknown");
    property_set("ro.carrier", carrier[0] ? carrier : "unknown");
    property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");

    property_set("ro.hardware", hardware);
    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
    property_set("ro.revision", tmp);
    return 0;
}

qemu在第一次调用import_kernel_cmdline时被赋值了,所以这里会再次调用import_kernel_cmdline同样的读取/proc/cmdline中的文件信息。但是 这个调用不再给全局变量赋值了,因为第一个参数是1,在import_kernel_nv走的的另一条分支property_set 每一个属性都调用property_set函数设置property, 这个property_set和上面的一样,都是直接操作的是共享内存,将cmdline文件 中的name、value存储到property area中。

再次回到set_init_propeties_action函数中,再继续设置一些property。


最后还有一个property_service_init_action,它调用的是start_property_service,虽然名字是启动property service,但本质上还是从多个文件中再次载入一些property,然后创建一个套接字并监听,专门用来处理property的set请求,并把这个socket对应的文件描述符赋值给全局变量property_set_fd,方便在init程序中进行poll操作。


总结来说,在init中和property相关的主要有三个:property_init_actionset_init_properties_actionproperty_service_init_action。三个action的工作分别为:初始化property共享内存区域;设置一些初始property;启动property监听套接字。这三个action执行完后,整个Android的property机制就运行起来了。


到目前为止,Android好像只是把action和service插入到了队列或者链表中,并未真正运行它们,相应的套接字也还未真正工作,所以init程序最后把7、8、9三个工作都放到了一个无限循环中去执行。

for(;;) {
        int nr, i, timeout = -1;
        /* 判断cur_action和cur_command是否为空等,cur_action为空的话从action_queue
         * 队列中取出头部,然后尝试获取cur_action中一个新的command,通过调用cmd的
         * func执行相应的函数。即每次循环都会执行一个cmd */
        execute_one_command();

        /* 实际上调用的是service_for_each_flags,对于service_list中的每一个service,
         * 如果设置了SVC_RESTARTING标志位,执行restart_service_if_needed,不同于
         * action,service在每次循环都会尝试restart所有的service */  
        restart_processes();

restart_service_if_needed函数的具体分析可以看 安卓启动服务

接下来循环体中的三个for循环如下所示:

 /*  property_set_fd_init在第一次循环中还是0,start_property_service会将property_set_fd置为1,所以符合判断条件 */
        if (!property_set_fd_init && get_property_set_fd() > 0) {
            ufds[fd_count].fd = get_property_set_fd();        /* 将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;
        }

在循环体的最后会调用poll系统调用,根据不同的时间执行不同的操作

        /* 循环遍历poll的socket数组,激活相应的操作 */
        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();
            }
        }

handle_property_set_fd函数是对property service的文件描述符的处理函数,实现如下:

void handle_property_set_fd()
{
    ......
    /* 后续程序调用property_set时是通过socket进行操作的,首先会和property service建立连接,所以需要先调用accept接收连接请求 */
    if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
        return;
    }

    ......

    switch(msg.cmd) {
    case PROP_MSG_SETPROP:
        /* 如果是一个set请求,调用property_set对property对应的共享内存进行设值 */
        msg.name[PROP_NAME_MAX-1] = 0;
        msg.value[PROP_VALUE_MAX-1] = 0;

        if(memcmp(msg.name,"ctl.",4) == 0) {
            ......
        } else {
            if (check_perms(msg.name, cr.uid, cr.gid)) {
                property_set((char*) msg.name, (char*) msg.value);
            } else {
                ERROR("sys_prop: permission denied uid:%d  name:%s\n",
                      cr.uid, msg.name);
            }
            close(s);
        }
        break;

    default:
        close(s);
        break;
    }
}

至此,整个init程序的执行流程分析完毕!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值