Android init相关知识

    init是android运行的第一个程序,在init中,android首先配置一些内核与用户空间交互所需的文件路径,接着开启属性服务,初始化一系列属性信息,解析配置文件,并根据解析的结果开启相关服务进程。init还会循环监听属性服务、多点触控、信号的请求,并作出响应处理。本文分析init流程及大体框架,并对相关知识做出分析。

 

init流程

    分析init流程,就看init.c的main函数。精简后的框架代码如下:

int main(int argc, char **argv)
{
    ...
    mkdir("/dev", 0755);
    mkdir("/proc", 0755);
    mkdir("/sys", 0755);

    ...
    open_devnull_stdio();
    klog_init();
    property_init();

    ...
    init_parse_config_file("/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");
    ...

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

 

    open_devnull_stdio函数用于把标准输出重定向,每个标准输出需要有一个文件作为承载,这里使用__null__作为临时承载。

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

    这里有几个小知识点:

    1.dup2会首先关闭dst文件描述符指向的文件,然后将dst文件描述符绑定到src所指向的文件上。

    2.系统输出有3个,分别为0、1、2,代表stdin、stdout及stderr。

    3.unlink类似于引用计数-1,当一个文件的打开引用计数为0时,调用close函数会删除该文件。

    property_init函数创建了一块共享内存,用于属性的存取,所有进程均可以访问。

    

属性文件的解析

    属性文件的解析是init的重要工作之一,在实际开发工作中,调整服务启动顺序、配置开机启动服务都需要对这部分内容有所了解,因此单独列出来分析。    

static void parse_config(const char *fn, char *s)
{
    struct parse_state state;
    struct listnode import_list;
    struct listnode *node;
    char *args[INIT_PARSER_MAXARGS];
    int nargs;

    nargs = 0;
    state.filename = fn; 
    state.line = 0;
    state.ptr = s;
    state.nexttoken = 0;
    state.parse_line = parse_line_no_op;

    list_init(&import_list);
    state.priv = &import_list;

    for (;;) {
        switch (next_token(&state)) {
        case T_EOF:
            state.parse_line(&state, 0, 0); 
            goto parser_done;
        case T_NEWLINE:
            state.line++;
            if (nargs) {
                int kw = lookup_keyword(args[0]);
                if (kw_is(kw, SECTION)) {
                    state.parse_line(&state, 0, 0); 
                    parse_new_section(&state, kw, nargs, args);
                } else {
                    state.parse_line(&state, nargs, args);
                }   
                nargs = 0;
            }   
            break;
        case T_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);
    }
}
    

    init_parse_config_file函数最终会调用parse_config做实际的解析。实际的解析工作是基于token的,可以理解为分词。首先将一行的分词保存为多个参数,然后当遇到换行符时,分析第一个参数是哪个关键词。parse_config会首先调用当前的parse_line函数做处理,然后若关键词属于SECTION,调用parse_new_section做解析。

    parse_new_section也会根据不同的关键词做不同的处理,同时将parse_line设置对对应的处理函数。

static 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:
        state->context = parse_service(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_service;
            return;
        }
        break;
    case K_on:
        state->context = parse_action(state, nargs, args);
        if (state->context) {
            state->parse_line = parse_line_action;
            return;
        }
        break;
    case K_import:
        parse_import(state, nargs, args);
        break;
    }
    state->parse_line = parse_line_no_op;
}

    解析工作实际做的事情,就是将各个service及action放到对应的全局链表中。其中parse_service将service加到service_list中,parse_action将action加到action_list中。一共有三个全局链表:service_list、action_list、action_queue,service_list及action_list分别存储解析出来的全部service及action,而action_queue用于存储等待执行的action。

    完成配置文件的解析后,init会调用action_for_each_trigger,将action_list中符合时间节点要求的action,加入到action_queue中等待执行。例如:action_for_each_trigger("early-init", action_add_queue_tail); 就是将early-init阶段要执行的action加入到action_queue中。

    queue_builtin_action函数会直接创建action,并添加到action_list及action_queue中。

    那么这些action什么时候被执行呢,答案就是,当init进入for死循环后,会调用execute_one_command函数。如果当前没有正在执行的命令,execute_one_command会找到action_queue头的action,然后找到这个action中应该执行的command,调用command的执行处理函数执行命令。

void execute_one_command(void)
{
    int ret, i;
    char cmd_str[256] = "";

    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {
        cur_action = action_remove_queue_head();
        cur_command = NULL;
        if (!cur_action)
            return;
        INFO("processing action %p (%s)\n", cur_action, cur_action->name);
        cur_command = get_first_command(cur_action);
    } else {
        cur_command = get_next_command(cur_action, cur_command);
    }

    if (!cur_command)
        return;

    ret = cur_command->func(cur_command->nargs, cur_command->args);
    if (klog_get_level() >= KLOG_INFO_LEVEL) {
        for (i = 0; i < cur_command->nargs; i++) {
            strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));
            if (i < cur_command->nargs - 1) {
                strlcat(cmd_str, " ", sizeof(cmd_str));
            }
        }
        INFO("command '%s' action=%s status=%d (%s:%d)\n",
             cmd_str, cur_action ? cur_action->name : "", ret, cur_command->filename,
             cur_command->line);
    }
}

 

pollfd

    pollfd用于存放Socket文件描述符,其定义如下:

struct pollfd 
{ 
    int fd; /* 想查询的文件描述符. */ 
    short int events; /* fd 上,我们感兴趣的事件*/ 
    short int revents; /* Types of events that actually occurred. */ 
};

    其中events为我们感兴趣的事件,revents为实际发生的事件。我们可以使用events注册我们想要监听的事件,在poll函数返回时,通过revents与不同时间&操作,得知实际发生的事件。具体用法可参考init的for循环。

 

listnode分析

    Android init定义了一种数据结构listnode,用于双向链表Tip:双向链表是环形的。奇妙的是listnode中只有指向前后节点的指针,并没有保存数据,那么listnode如何使用呢?

    秘密就在两个宏里

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define node_to_item(node, container, member) \
    (container *) (((char*) (node)) - offsetof(container, member))

    offsetof的作用比较好理解,是获得成员MEMBER在TYPE结构体中的位置偏移。那么node_to_item呢?

    其实node_to_item的作用是将一个保存有listnode成员的结构体,根据listnode地址,找到结构体本身。char*的作用是将listnode地址位对齐,再减去listnode节点本身在结构体中的偏移量,即获得了结构体本身地址。这样用的好处是,一个结构体可以通过包含多个listnode成员,使数据同时包含在多个双向链表中。

    使用实例如下:

struct action {
        /* node in list of all actions */
    struct listnode alist;
        /* node in the queue of pending actions */
    struct listnode qlist;
        /* node in list of actions for a trigger */
    struct listnode tlist;

    unsigned hash;
    const char *name;

    struct listnode commands;
    struct command *current;
};

 

posted @ 2016-03-09 17:52  小狗奇奇  阅读(218)  评论(0编辑  收藏  举报