Android启动过程分析——init.c(二)

来源:互联网 发布:js模拟按键f11 编辑:程序博客网 时间:2024/05/22 03:05

Part 4

// ==================================================   // Part 4union 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");// ==================================================

这部分与selinux有关,会进行一些设置,并将值写入相关设备节点。

Part 5

// ==================================================// Part 5is_charger = !strcmp(bootmode, "charger");// ==================================================

这部分只有一句话,就是检查bootmode,根据是不是charger模式给is_charger赋值。bootmode是在export_kernel_boot_props()函数里从属性连读取的。我猜测,这个变量的作用是判断是不是充电开机的。因为Android在关机的时候插入充电器,屏幕会点亮,并显示当前点亮,这种开机与正常开机相比,所需要支持的功能要简单得多,所以,init在后面的进程中,会判断是否是充电开机,如果是充电开机,某些过程会直接省略掉。

    // ==================================================    // Part 6    INFO("property init\n");    if (!is_charger)        property_load_boot_defaults();    INFO("reading config file\n");    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");    /* 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    // ==================================================

这一部分是init进程的重头戏,我们将重点分析。

INFO("property init\n");if (!is_charger)    property_load_boot_defaults();void property_load_boot_defaults(void){    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT);}static void load_properties_from_file(const char *fn){    char *data;    unsigned sz;    data = read_file(fn, &sz);    if(data != 0) {        load_properties(data);        free(data);    }}static void load_properties(char *data){    char *key, *value, *eol, *sol, *tmp;    sol = data;    while((eol = strchr(sol, '\n'))) {        key = sol;        *eol++ = 0;        sol = eol;        value = strchr(key, '=');        if(value == 0) continue;        *value++ = 0;        while(isspace(*key)) key++;        if(*key == '#') continue;        tmp = value - 2;        while((tmp > key) && isspace(*tmp)) *tmp-- = 0;        while(isspace(*value)) value++;        tmp = eol - 2;        while((tmp > value) && isspace(*tmp)) *tmp-- = 0;        property_set(key, value);    }}

首先是从PROP_PATH_RAMDISK_DEFAULT指定的设备节点中读取数据,然后解析这些数据,再调用property_set()函数将数据写入property系统。

接下来解析init.rc文件。

INFO("reading config file\n");init_parse_config_file("/init.rc");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;}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.rc这个文件,然后在parse_config()函数里面解析读到的数据。解析的过程在for循环里。

注意parse_config()里面的parser_done这个标签。list_for_each是一个宏,作用是遍历解析到的import节点。然后递归的调用init_parse_config_file()这个函数来遍历import进来的.rc文件。

之后就是一堆action_for_each_trigger()和queue_builtin_action()。

void action_for_each_trigger(const char *trigger,                             void (*func)(struct action *act)){    struct listnode *node;    struct action *act;    list_for_each(node, &action_list) {        act = node_to_item(node, struct action, alist);        if (!strcmp(act->name, trigger)) {            func(act);        }    }}

action_for_each_trigge的作用是遍历action_list,将act的名称与指定的trigger比较,如果一致,就将act传递给func函数并执行。

在这里,就是在init.rc解析出来的action_list中查找指定的trigger,然后将这些trigger对应的act通过函数action_add_queue_tail添加到队列中:

void action_add_queue_tail(struct action *act){    if (list_empty(&act->qlist)) {        list_add_tail(&action_queue, &act->qlist);    }}void list_add_tail(struct listnode *head, struct listnode *item){    item->next = head;    item->prev = head->prev;    head->prev->next = item;    head->prev = item;}
void queue_builtin_action(int (*func)(int nargs, char **args), char *name){    struct action *act;    struct command *cmd;    act = calloc(1, sizeof(*act));    act->name = name;    list_init(&act->commands);    list_init(&act->qlist);    cmd = calloc(1, sizeof(*cmd));    cmd->func = func;    cmd->args[0] = name;    list_add_tail(&act->commands, &cmd->clist);    list_add_tail(&action_list, &act->alist);    action_add_queue_tail(act);}

可以看到,这个函数的最后也是调用action_add_queue_tail函数。所以,这两个函数的作用是一样的,区别在于一个是从init.rc文件得到act,一个是从函数得到的。

在《Android框架揭秘》中,没有看到queue_builtin_action()的介绍,所以,我猜测,这是Android4.4与Android4.2有区别的地方。

至于为什么要这样做,我猜测,Google是将可以配置的act放在了init.rc中,而不可配置,必须要有的act就放在了代码中写死。

下面来分析一下queue_builtin_action中的各种act来源。

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

当系统冷启动完成之后,会创建/dev/.coldboot_done文件。这里就是等待冷启动结束,再执行下面的动作。

/* * Writes 512 bytes of output from Hardware RNG (/dev/hw_random, backed * by Linux kernel's hw_random framework) into Linux RNG's via /dev/urandom. * Does nothing if Hardware RNG is not present. * * Since we don't yet trust the quality of Hardware RNG, these bytes are not * mixed into the primary pool of Linux RNG and the entropy estimate is left * unmodified. * * If the HW RNG device /dev/hw_random is present, we require that at least * 512 bytes read from it are written into Linux RNG. QA is expected to catch * devices/configurations where these I/O operations are blocking for a long * time. We do not reboot or halt on failures, as this is a best-effort * attempt. */static int mix_hwrng_into_linux_rng_action(int nargs, char **args){    int result = -1;    int hwrandom_fd = -1;    int urandom_fd = -1;    char buf[512];    ssize_t chunk_size;    size_t total_bytes_written = 0;    hwrandom_fd = TEMP_FAILURE_RETRY(            open("/dev/hw_random", O_RDONLY | O_NOFOLLOW));    if (hwrandom_fd == -1) {        if (errno == ENOENT) {          ERROR("/dev/hw_random not found\n");          /* It's not an error to not have a Hardware RNG. */          result = 0;        } else {          ERROR("Failed to open /dev/hw_random: %s\n", strerror(errno));        }        goto ret;    }    urandom_fd = TEMP_FAILURE_RETRY(            open("/dev/urandom", O_WRONLY | O_NOFOLLOW));    if (urandom_fd == -1) {        ERROR("Failed to open /dev/urandom: %s\n", strerror(errno));        goto ret;    }    while (total_bytes_written < sizeof(buf)) {        chunk_size = TEMP_FAILURE_RETRY(                read(hwrandom_fd, buf, sizeof(buf) - total_bytes_written));        if (chunk_size == -1) {            ERROR("Failed to read from /dev/hw_random: %s\n", strerror(errno));            goto ret;        } else if (chunk_size == 0) {            ERROR("Failed to read from /dev/hw_random: EOF\n");            goto ret;        }        chunk_size = TEMP_FAILURE_RETRY(write(urandom_fd, buf, chunk_size));        if (chunk_size == -1) {            ERROR("Failed to write to /dev/urandom: %s\n", strerror(errno));            goto ret;        }        total_bytes_written += chunk_size;    }    INFO("Mixed %d bytes from /dev/hw_random into /dev/urandom",                total_bytes_written);    result = 0;ret:    if (hwrandom_fd != -1) {        close(hwrandom_fd);    }    if (urandom_fd != -1) {        close(urandom_fd);    }    memset(buf, 0, sizeof(buf));    return result;}

这个函数比较复杂,好像是和随机数的生成机制有关。从硬件随机数读512个字节写入软件随机数。

static int keychord_init_action(int nargs, char **args){    keychord_init();    return 0;}void keychord_init(){    int fd, ret;    service_for_each(add_service_keycodes);    /* nothing to do if no services require keychords */    if (!keychords)        return;    fd = open("/dev/keychord", O_RDWR);    if (fd < 0) {        ERROR("could not open /dev/keychord\n");        return;    }    fcntl(fd, F_SETFD, FD_CLOEXEC);    ret = write(fd, keychords, keychords_length);    if (ret != keychords_length) {        ERROR("could not configure /dev/keychord %d (%d)\n", ret, errno);        close(fd);        fd = -1;    }    free(keychords);    keychords = 0;    keychord_fd = fd;}

这个就是打开多点触控的设备节点。

static char console_name[PROP_VALUE_MAX] = "/dev/console";static int console_init_action(int nargs, char **args){    int fd;    if (console[0]) {        snprintf(console_name, sizeof(console_name), "/dev/%s", console);    }    fd = open(console_name, O_RDWR);    if (fd >= 0)        have_console = 1;    close(fd);    if( load_565rle_image(INIT_IMAGE_FILE) ) {        fd = open("/dev/tty0", O_WRONLY);        if (fd >= 0) {            const char *msg;                msg = "\n"            "\n"            "\n"            "\n"            "\n"            "\n"            "\n"  // console is 40 cols x 30 lines            "\n"            "\n"            "\n"            "\n"            "\n"            "\n"            "\n"            "             A N D R O I D ";            write(fd, msg, strlen(msg));            close(fd);        }    }    return 0;}

这里是打开一个设备节点,然后显示INIT_IMAGE_FILE指定的开机画面(这里的开机画面是565rle格式的)。

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;}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, 0666, 0, 0);    if(fd < 0) return;    fcntl(fd, F_SETFD, FD_CLOEXEC);    fcntl(fd, F_SETFL, O_NONBLOCK);    listen(fd, 8);    property_set_fd = fd;}

这里就是启动property系统。一次载入PROP_PATH_SYSTEM_BUILD、PROP_PATH_SYSTEM_BUILD指定的文件中的属性,覆盖的属性、persist属性。

属性值的更改仅能在init进程中进行,即一个进程若想修改属性值,必须向init进程提交申请,为此init进程生成“/dev/socket/property_service”套接字,以接受其他进程提交的申请。

static int signal_init_action(int nargs, char **args){    signal_init();    return 0;}void signal_init(void){    int s[2];    struct sigaction act;    memset(&act, 0, sizeof(act));    act.sa_handler = sigchld_handler;    act.sa_flags = SA_NOCLDSTOP;    sigaction(SIGCHLD, &act, 0);    /* create a signalling mechanism for the sigchld handler */    if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {        signal_fd = s[0];        signal_recv_fd = s[1];        fcntl(s[0], F_SETFD, FD_CLOEXEC);        fcntl(s[0], F_SETFL, O_NONBLOCK);        fcntl(s[1], F_SETFD, FD_CLOEXEC);        fcntl(s[1], F_SETFL, O_NONBLOCK);    }    handle_signal();}void handle_signal(void){    char tmp[32];    /* we got a SIGCHLD - reap and restart as needed */    read(signal_recv_fd, tmp, sizeof(tmp));    while (!wait_for_one_process(0))        ;}

首先,将sigchld_handler()函数注册为SIGCHLD信号处理函数。然后,创建socket,来接收信号。然后,开始处理SIGCHLD信号。

static int check_startup_action(int nargs, char **args){    /* make sure we actually have all the pieces we need */    if ((get_property_set_fd() < 0) ||        (get_signal_fd() < 0)) {        ERROR("init startup failure\n");        exit(1);    }        /* signal that we hit this point */    unlink("/dev/.booting");    return 0;}

这里仅仅是做个检查。

static int queue_property_triggers_action(int nargs, char **args){    queue_all_property_triggers();    /* enable property triggers */    property_triggers_enabled = 1;    return 0;}void queue_all_property_triggers(){    struct listnode *node;    struct action *act;    list_for_each(node, &action_list) {        act = node_to_item(node, struct action, alist);        if (!strncmp(act->name, "property:", strlen("property:"))) {            /* parse property name and value               syntax is property:<name>=<value> */            const char* name = act->name + strlen("property:");            const char* equals = strchr(name, '=');            if (equals) {                char prop_name[PROP_NAME_MAX + 1];                char value[PROP_VALUE_MAX];                int length = equals - name;                if (length > PROP_NAME_MAX) {                    ERROR("property name too long in trigger %s", act->name);                } else {                    memcpy(prop_name, name, length);                    prop_name[length] = 0;                    /* does the property exist, and match the trigger value? */                    property_get(prop_name, value);                    if (!strcmp(equals + 1, value) ||!strcmp(equals + 1, "*")) {                        action_add_queue_tail(act);                    }                }            }        }    }}

在init.rc中定义了很多on propertychange的act,这里就是根据当前属性的状态,来触发这些act。

#if BOOTCHARTstatic int bootchart_init_action(int nargs, char **args){    bootchart_count = bootchart_init();    if (bootchart_count < 0) {        ERROR("bootcharting init failure\n");    } else if (bootchart_count > 0) {        NOTICE("bootcharting started (period=%d ms)\n", bootchart_count*BOOTCHART_POLLING_MS);    } else {        NOTICE("bootcharting ignored\n");    }    return 0;}#endif

bootchart是一个用于分析开机启动的实用程序,可以借助于它来分析开机过程中cpu的使用情况和各个进程的启动耗时。

Part 7

这一部分是init进程的事件处理循环。

// ==================================================// Part 7for(;;) {    int nr, i, timeout = -1;    // 在确认时间发生前,先要在action_list的命令中确认是否有尚未执行的命令,并执行之。    execute_one_command();    // 当紫禁城终止退出时,此命令用于重启或终止子进程。    restart_processes();    // 注册init进程监视的文件描述符。    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()) // 处理SIGCHLD信号                handle_signal();        }    }}// ==================================================
0 0
原创粉丝点击