Android啟動過程分析——init.c(二)
Part 4
// ==================================================
// Part 4
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");
// ==================================================
這部分與selinux有關,會進行一些設定,並將值寫入相關裝置節點。
Part 5
// ==================================================
// Part 5
is_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 BOOTCHART
static 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 7
for(;;) {
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();
}
}
}
// ==================================================