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(); } }}// ==================================================
- Android启动过程分析——init.c(二)
- Android启动过程分析——init.c(一)
- Android init 启动过程分析(二)
- android启动代码init.c文件分析(二)
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- Android init 启动过程分析
- 【Linux】——搭建nexus
- GPS、Wifi等各种手机定位方式的含义及原理详解
- 1063. Set Similarity (25)
- 利用VS制作软件安装包的步骤
- Java数据结构----树--二叉查找(搜索或排序)树BST
- Android启动过程分析——init.c(二)
- 函数签名
- java对文件简单的加密解密(异或运算)
- 这样几招玩转知道,你也是引流大神!
- 此证书的签发者无效 Your account already has a valid iOS Distribution certificate
- Java常用工具类(2)
- Java设计模式:组合模式 Component
- mplayer: could not connect to socket mplayer
- HTTP GET 304