概述
Android本质上是一个基于Linux内核的开源操作系统,与我现在用的Ubuntu系统类似,但是所有的Android设备都是运行在ARM处理器(ARM源自进阶精简指令集机器,源自ARM架构)上,而像Ubuntu操作系统是x86(x86是一系列的基于intel 8086 CPU计算机微处理器指令集架构)系统。不过既然Android也是基于Linux内核的系统,那么基本的启动过程也应该符合Linux的规则。下图基本描述了当你按下电源开关后Android设备的执行步骤:
一个完整的Linux系统首先会将一个Linux内核装载到内存,也就是编译Linux内核源代码生产的bzImage文件,对于为Android优化的LInux内核源代码会生成zImage文件。该文件就是Linux内核的二进制版本。由于zImage在内核空间运行,而我们平常使用的软件都是在应用空间运行,内核空间和应用空间是不能直接通过内存地址级别访问的,所以就需要建立某种通讯机制。目前Linux有很多通讯机制可以在用户空间和内核空间之间交互,例如设备驱动文件(位于/dev目录中)、内存文件(/proc、/sys目录等)。Linux的重要特征之一就是一切都是以文件的形式存在,例如,一个设备通常与一个或多个设备文件对应。这些与内核空间交互的文件都在用户空间,所以在Linux内核装载完,需要首先建立这些文件所在的目录,而完成这些工作的程序就是init进程。
在分析init的核心代码之前,可以初步了解init的主要做了如下工作:
1. Android系统有很多属性,init提供了一个property_service(属性服务)来管理它们。
2. 处理配置文件的命令(主要是init.rc文件),包括处理各种Action。
3. 性能分析(使用bootchart工具)。
4. 无限循环执行command。
init进程源码
源码位于:system/core/init/目录,本文将基于Android4.4.4_r1版本进行分析。
其中init.c是init的主文件,由于init是命令行程序,所以分析init.c需要从main函数入手,我对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; bool is_charger = false; if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); if (!strcmp(basename(argv[0]), "watchdogd")) return watchdogd_main(argc, argv); /* clear the umask */ umask(0); // 创建用户空间的目录,例如/dev,/proc,/sys等。 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); // 检测/dev/.booting文件是否可读写和可创建。 close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000)); // 将标准输入、输出、错误输出重定向到/dev/__null__。 open_devnull_stdio(); // 将init的日志输出设备设置为/dev/__kmsg__。 klog_init(); // 初始化和属性相关的资源 property_init(); get_hardware_name(hardware, &revision); // 处理内核命令行 process_kernel_cmdline(); 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"); is_charger = !strcmp(bootmode, "charger"); INFO("property init\n"); if (!is_charger) property_load_boot_defaults(); INFO("reading config file\n"); // 分析/init.rc文件的内容 init_parse_config_file("/init.rc"); // 解析完init.rc配置文件后,会得到一系列的Action动作。 // init将动作的执行时间划分为四个阶段:early-init,init,early-boot,boot 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的子进程(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; #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; }
我们可以看到init.c的main函数还是非常复杂的,但是我们不需要每条语句都很清楚(没这个必要,而且这样搞难度太大)。我们通常只需要了解init的主线就可以了。从上述main函数分析,可以看出,init实际上就分为两个部分:
1. 初始化(包括建立/dev、/proc等目录、初始化属性、执行init.rc等初始化文件中的action等。
2. 使用for循环无限循环建立子进程。
初始化
我们先来分析初始化部分的代码。main函数最开始完成的建立目录的工作比较简单,没什么可分析的,就是调用了一些普通的API(mkdir)建立了一些目录。我们重点分析接下来执行的2个函数:
1. get_hardware_name,主要源码如下:
void get_hardware_name(char *hardware, unsigned int *revision) { char data[1024]; int fd, n; char *x, *hw, *rev; // 如果hardware已经有值,说明hardware通过内核命令行提供,直接返回 if (hardware[0]) return; // 已只读方式打开/proc/cpuinfo文件 fd = open("/proc/cpuinfo", O_RDONLY); if (fd < 0) return; // 读取/proc/cpuinfo文件的内容 n = read(fd, data, 1023); close(fd); if (n < 0) return; data[n] = 0; // 从/proc/cpuinfo文件中读取Hardware第一次出现的位置 hw = strstr(data, "\nHardware"); rev = strstr(data, "\nRevision"); if (hw) { x = strstr(hw, ": "); if (x) { x += 2; n = 0; while (*x && *x != '\n') { if (!isspace(*x)) // 将Hardware字段的值都转换为小写,并更新hareware参数的值 // hardware也就是在init.c文件中定义的hardware数组 hardware[n++] = tolower(*x); x++; if (n == 31) break; } hardware[n] = 0; } } if (rev) { x = strstr(rev, ": "); if (x) { *revision = strtoul(x + 2, 0, 16); } } }
从get_hardware_name方法的代码可知,该方法主要用于确定hardware和version变量的值。获取hardware的来源是Linux命令行或者/proc/cpuinfo文件的内容。我们可以直接从手机adb模式看一下/proc/cpuinfo的内容,如下图所示:
从上图可以看出,设备的Hadrware的名称为:MT6592。这里多介绍一点,init.rc文件中的开始部分用了很多import语句导入了其他配置文件,例如,import /init.usb.rc。但是有一处import语句它使用的是一个变量,import /init.${ro.hardware}.rc,这个ro.hardware就是上问介绍的方法获得的hardware的值。
2. process_kernel_cmdline,函数源码如下:
static void process_kernel_cmdline(void) { /* don't expose the raw commandline to nonpriv processes */ chmod("/proc/cmdline", 0440); /* first pass does the common stuff, and finds if we are in qemu. * second pass is only necessary for qemu to export all kernel params * as props. */ import_kernel_cmdline(0, import_kernel_nv); if (qemu[0]) import_kernel_cmdline(1, import_kernel_nv); // 用属性值设置内核变量 export_kernel_boot_props(); }
在process_kernel_cmdline函数中,除了使用import_kernel_cmdline函数导入内核变量外,主要的功能就是调用export_kernel_boot_props函数通过属性设置内核变量。我们来看一下export_kernel_boot_props函数的源码:
static void export_kernel_boot_props(void) { char tmp[PROP_VALUE_MAX]; int ret; unsigned i; struct { const char *src_prop; const char *dest_prop; const char *def_val; } prop_map[] = { { "ro.boot.serialno", "ro.serialno", "", }, { "ro.boot.mode", "ro.bootmode", "unknown", }, { "ro.boot.baseband", "ro.baseband", "unknown", }, { "ro.boot.bootloader", "ro.bootloader", "unknown", }, }; // 通过内核的属性设置应用层配置文件的属性 for (i = 0; i < ARRAY_SIZE(prop_map); i++) { ret = property_get(prop_map[i].src_prop, tmp); if (ret > 0) property_set(prop_map[i].dest_prop, tmp); else property_set(prop_map[i].dest_prop, prop_map[i].def_val); } // 根据ro.boot.console属性的值设置console变量 ret = property_get("ro.boot.console", tmp); if (ret) strlcpy(console, tmp, sizeof(console)); /* save a copy for init's usage during boot */ property_get("ro.bootmode", tmp); strlcpy(bootmode, tmp, sizeof(bootmode)); // 获取ro.boot.hardware的值,保存在tmp字符串数组中 ret = property_get("ro.boot.hardware", tmp); if (ret) strlcpy(hardware, tmp, sizeof(hardware)); // 利用hardware的值设置ro.hardware属性。 // 这个属性就是前面提到的初始化文件名的属性 property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp); /* TODO: these are obsolete. We should delete them */ 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"); }
从export_kernel_boot_props函数的代码可以看出,该函数实际上就是来回设置一些属性值。
介绍完前面的2个函数,那么问题来了,前面多次提到属性或属性服务,那么Android系统到底有哪些属性?属性服务又是如何工作的?看接下来的代码讲解。
属性服务
我们知道,在Windows平台上有一个叫注册表的东西,注册表可以存储一些类似 key/value 的键值对。一般而言,系统或某些应用程序会把自己的一些属性存储在注册表中,即使系统重启或者应用程序重启,它还能根据之前在注册表中设置的属性,进行相应的初始化工作。Amdroid平台也提供了类似的机制,称之为属性服务(property service)。
init在启动的过程中会启动属性服务(Socket服务),并且在内存中建立一块存储区域,用来存储这些属性。init.c代码中跟属性服务有关的代码有下面两行:
property_init(); start_property_service();
属性服务初始化
创建存储空间。先看一下property_init函数,源码位置:system/core/init/property_service.c,源码如下:
void property_init(void) { init_property_area(); }
可以看到。property_init函数中,只是简单的调用了init_property_area函数,那我们接下来看一下init_property_area函数是如何工作的,源码如下:
static int init_property_area(void) { if (property_area_inited) return -1; if(__system_property_area_init()) return -1; if(init_workspace(&pa_workspace, 0)) return -1; fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); property_area_inited = 1; return 0; }
从init_property_area函数,我们可以看出,函数首先判断内存区域是否已经初始化过,如果已经初始化,则直接返回-1。如果没有初始化,我们接下来会发现有两个关键函数__system_property_area_init和init_workspace应该是真正的跟内存区域初始化有关。分别分析一下这两个函数。
__system_property_area_init函数位于bionic/libc/bionic/system_properties.c文件中,其源码如下:
int __system_property_area_init() { return map_prop_area_rw(); }
这里__system_property_area_init也是简单的调用了map_prop_area_rw方法,我们只能继续跟踪了(蛋疼的嵌套啊,不过map_prop_area_rw确实是真正的内存分配函数)。map_prop_area_rw源码如下:
static int map_prop_area_rw() { prop_area *pa; int fd; int ret; /* dev is a tmpfs that we can use to carve a shared workspace * out of, so let's do that... * O_RDWR ==> 读写 * O_CREAT ==> 若不存在则创建 * O_NOFOLLOW ==> 如果filename是软链接,则打开失败 * O_EXCL ==> 如果使用O_CREAT时文件存在,则可返回错误信息 */ fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444); if (fd < 0) { if (errno == EACCES) { /* for consistency with the case where the process has already * mapped the page in and segfaults when trying to write to it */ abort(); } return -1; } ret = fcntl(fd, F_SETFD, FD_CLOEXEC); if (ret < 0) goto out; if (ftruncate(fd, PA_SIZE) < 0) goto out; pa_size = PA_SIZE; pa_data_size = pa_size - sizeof(prop_area); compat_mode = false; // mmap映射文件实现共享内存 pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(pa == MAP_FAILED) goto out; memset(pa, 0, pa_size); pa->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; /* reserve root node */ pa->bytes_used = sizeof(prop_bt); /* plug into the lib property services */ __system_property_area__ = pa; close(fd); return 0; out: close(fd); return -1; }
代码还是比较好理解的,这块代码主要作用是建立了一块共享内存。
接下来,我们继续看一下init_workspace的源码:
typedef struct { size_t size; int fd; } workspace; static int init_workspace(workspace *w, size_t size) { void *data; int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW); if (fd < 0) return -1; w->size = size; w->fd = fd; return 0; } static workspace pa_workspace;
其实,上面两个函数最关键的一点在于 __system_property_area__ = pa的赋值。__system_property_area__是bionic_libc库中输出的一个变量,为什么这里要给它赋值呢?
是因为,虽然属性区域是init进程创建的,但是Android系统希望其他进程也能够读取这块内存里的东西。为了做到这一点,因此init进程做了如下两项工作:
- 把属性区域创建在共享内存上,而共享内存是可以跨进程的。这一点,在上述代码中是通过mmap映射普通文件实现的。init_workspace也保持了映射文件的句柄。
- 如果让其他进程知道这个共享内存呢?Android利用了gcc的constructor属性,这个属性指明了一个__lib_prenit函数,当bionic_libc库被加载时,将自动调用这个libc_prenit,这个函数内部将完成共享内存到本地进程的映射工作。
关于使用mmap映射普通文件实现共享内存IPC通信机制,可以参考我之前转载的这篇文章:http://blog.csdn.net/wzy_1988/article/details/40858765 。
属性服务器的分析
init进程会启动一个属性服务器,而客户端只能通过与属性服务器交互来设置属性。先来看一下属性服务器的内容,它由start_property_service函数启动,代码如下所示:
void start_property_service(void) { int fd; /* * 加载属性文件,其实就是解析这些文件中的属性,然后把属性设置到共享内存中去。 * Android系统一共提供了四个存储属性的文件,它们分别是: * #define PROP_PATH_RAMDISK_DEFAULT "/default.prop" * #define PROP_PATH_SYSTEM_BUILD "/system/build.prop" * #define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop" * #define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop" */ 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(); // 创建一个socket,用于IPC通信。 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; }
主要的宏我已经在代码中通过注释标记出来了。load_properties_from_file函数主要是从文件中读取property属性,并通过property_set函数写入到共享内存中,这里了解一下property_set函数源码:
int property_set(const char *name, const char *value) { prop_info *pi; int ret; size_t namelen = strlen(name); size_t valuelen = strlen(value); if (!is_legal_property_name(name, namelen)) return -1; if (valuelen >= PROP_VALUE_MAX) return -1; pi = (prop_info*) __system_property_find(name); if(pi != 0) { // 如果属性以"ro."开头,表示是只读属性,不能设置,所以直接返回-1 if(!strncmp(name, "ro.", 3)) return -1; __system_property_update(pi, value, valuelen); } else { ret = __system_property_add(name, namelen, value, valuelen); if (ret < 0) { ERROR("Failed to set '%s'='%s'\n", name, value); return ret; } } /* If name starts with "net." treat as a DNS property. */ if (strncmp("net.", name, strlen("net.")) == 0) { if (strcmp("net.change", name) == 0) { return 0; } /* * The 'net.change' property is a special property used track when any * 'net.*' property name is updated. It is _ONLY_ updated here. Its value * contains the last updated 'net.*' property. */ property_set("net.change", name); } else if (persistent_properties_loaded && strncmp("persist.", name, strlen("persist.")) == 0) { /* * Don't write properties to disk until after we have read all default properties * to prevent them from being overwritten by default values. */ write_persistent_property(name, value); } else if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) { selinux_reload_policy(); } property_changed(name, value); return 0; }
客户端可以通过发送socket请求,来设置property。
参考链接
1. 《深入理解Android卷I》
2. http://blog.csdn.net/nokiaguy/article/details/8800962