Android系统的框架架构图如下(来自网上):
Linux内核启动之后----->就到Android的Init进程 ----->进而启动Android相关的服务和应用。
整个的启动过程如下图所示:
以下针对Android 4.2内核代码的启动部分进行分析。
Init进程,是一个由内核启动的用户级进程。内核自行启动(已被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,就通过启动用户级程序init的方式,完成引导进程。
Init进程始终是第一个进程。Init进程的对应的代码的main函数在目录~/my_android/system/core/init/init.c
整个Android系统的启动分为Linux kernel的启动和Android系统的启动。
Linux kernel启动起来后,然后就运行第一个用户程序,在Android中,就是init程序,在目录~/my_android/system/core/init/init.c,对其中的main()函数分段进行介绍
1. 首先声明一些局部变量,代码如下:
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; ...... ...... }
main函数中该段代码主要是声明了一些后续会使用的变量,其中涉及一个结构体pollfd,后续对其操作时再进行介绍
2.对传入的argv[0]进行判断,决定程序的执行分支,代码如下:
int main(int argc, char **argv) { ... ... if (!strcmp(basename(argv[0]), "ueventd")) return ueventd_main(argc, argv); if (!strcmp(basename(argv[0]), "watchdogd")) return watchdogd_main(argc, argv); ... ... }
说明了这里处理kernel执行会跳转到以外,还有其他地方会调用这个main函数。
其中的argv[0]就是表示要执行的函数名称。
从这里看,应该有三个地方会执行此处的main()函数:
int main(int argc, char **argv) { ... ... /* clear the umask */ umask(0); /* Get the basic filesystem setup we need put * together in the initramdisk on / and then we'll * let the rc file figure out the rest. */ /* Don't repeat the setup of these filesystems, * it creates double mount points with an unknown effect * on the system. This init file is for 2nd-init anyway. */ #ifndef NO_DEVFS_SETUP 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); /* indicate that booting is in progress to background fw loaders, etc */ close(open("/dev/.booting", O_WRONLY | O_CREAT, 0000)); /* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won't * be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually * talk to the outside world. */ ... ... }
4.生成log设备,以及一些属性设置
int main(int argc, char **argv) { ... ... /* We must have some place other than / to create the * device nodes for kmsg and null, otherwise we won't * be able to remount / read-only later on. * Now that tmpfs is mounted on /dev, we can actually * talk to the outside world. */ open_devnull_stdio(); klog_init(); #endif property_init(); get_hardware_name(hardware, &revision); process_kernel_cmdline(); ... ... }其中open_devnull_stdio()的定义在~/my_android/system/core/init/util.c中,代码如下:
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); }
回到main()函数中,接着是执行klog_init()函数,其定义在:~/my_android/system/core/libcutils/klog.c中,实现代码如下:
void klog_init(void) { static const char *name = "/dev/__kmsg__"; if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { klog_fd = open(name, O_WRONLY); fcntl(klog_fd, F_SETFD, FD_CLOEXEC); unlink(name); } }该函数和open_devnull_stdio的实现很相像,创建设备节点,打开,操作,然后删除文件。其中的fcntl(klog_fd, F_SETFD, FD_CLOEXEC); 表示当在子进程中使用exec执行其他程序时会把这个文件描述符关闭。
接着是执行main()函数中的property_init()函数,其定义在:~/my_android/system/core/init/property_service.c文件中。实现的代码如下:
void property_init(void) { init_property_area(); }此处调用了另一函数init_property_area(),其定义也在~/my_android/system/core/init/property_service.c文件中,实现代码如下:
static int init_property_area(void) { prop_area *pa; if(pa_info_array) return -1; if(init_workspace(&pa_workspace, PA_SIZE)) return -1; fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START); pa = pa_workspace.data; memset(pa, 0, PA_SIZE); pa->magic = PROP_AREA_MAGIC; pa->version = PROP_AREA_VERSION; /* plug into the lib property services */ __system_property_area__ = pa; property_area_inited = 1; return 0; }在该函数中,涉及了几个结构体变量,首先看一下各结构体的定义
prop_area结构体的定义如下,其定义在:
struct prop_area{ nsigned volatile count; unsigned volatile serial; unsigned magic; unsigned version; unsigned reserved[4]; unsigned toc[1]; };
static pro_info *pa_info_array;
static int init_workspace(workspace *w, size_t size) { void *data; int fd; /* dev is a tmpfs that we can use to carve a shared workspace * out of, so let's do that... */ fd = open("/dev/__properties__", O_RDWR | O_CREAT, 0600); if (fd < 0) return -1; if (ftruncate(fd, size) < 0) goto out; data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(data == MAP_FAILED) goto out; close(fd); fd = open("/dev/__properties__", O_RDONLY); if (fd < 0) return -1; unlink("/dev/__properties__"); w->data = data; w->size = size; w->fd = fd; return 0; out: close(fd); return -1; }pa_workspace和PA_SIZE也在当前文件夹中定义,如下:
#define PA_SIZE 49152 static workspace pa_workspace;
typedef struct { void *data; size_t size; int fd; } workspace;这个文件夹中存储了三个变量,数据、大小和文件描述符。init_workplace()这个函数里面就要初始化一个这样的结构体。
首先,打开一个设备文件/dev/__properties__", 通过ftruncate()函数调用将这个文件的大小改为size。size是通过调用函数时形参传递进来的。
然后,调用mmap()函数映射一段内存。返回映射区的地址保存在data中。
最后,将对应的data、size、fd分别给workplace结构体指针w赋值。
函数执行成功,返回0。回到init_property_area函数中。init_workplace()函数返回后进行if判断,若执行成功,返回0,所以接着往下执行。
调用fcntl()函数,关于fcntl函数的功能,
参考:http://www.cnblogs.com/andtt/articles/2178875.html和http://blog.csdn.net/ustc_dylan/article/details/6930189
pa_workspace.data表示的是一段大小为PA_SIZE的内存地址,将这个地址加上PA_INFO_START赋值给pa_info_array。PA_INFO_START定义为:
#define PA_INFO_START 1536然后将pa_workplace.data代表的那段大小为PA_SIZE的内存通过调用memset()函数将其内存清0。
执行get_hardware_name(hardware, &revision);该函数定义在:~/my_android/system/core/init/util.c文件中,实现代码如下:
void get_hardware_name(char *hardware, unsigned int *revision) { char data[1024]; int fd, n; char *x, *hw, *rev; /* Hardware string was provided on kernel command line */ if (hardware[0]) return; fd = open("/proc/cpuinfo", O_RDONLY); if (fd < 0) return; n = read(fd, data, 1023); close(fd); if (n < 0) return; data[n] = 0; 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[n++] = tolower(*x); x++; if (n == 31) break; } hardware[n] = 0; } } if (rev) { x = strstr(rev, ": "); if (x) { *revision = strtoul(x + 2, 0, 16); } } }
static char hardware[32]; static unsigned revision = 0;
get_hardware_name()函数从"proc/cpuinfo"文件读取相应字符串到data中,然后通过调用strstr函数将data中"\nHardware"开始的字符保存到hw中,将“\nRevision”开始的字符保存到rev中。
strstr()函数的功能:就是在第一个参数中查找第二个参数第一次出现的地址,将地址赋值给一个字符指针,接着就可以利用这个字符指针找到从这个地址开始往后的字符。
"/proc/cpuinfo中"中的内容,可以通过adb shell登录模拟器来查看,其内容如下:
后面两个if语句对hw和rev进行处理,最终得到我们想要的数值。其中hw部分,提取Goldfish这几个字符,并将其大写转为小写。rev那部分数据转化为十六进制表示。
回到main()函数,接着执行process_kernel_cmdline();该函数和main()函数定义在同一文件夹中,实现代码如下:
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); /* now propogate the info given on command line to internal variables * used by init as well as the current required properties */ export_kernel_boot_props(); }除了使用import_kernel_cmdline函数导入内核变量外,主要的功能就是调用export_kernel_boot_props函数通过属性设置内核变量,主要实现的功能是处理内核命令行,以下从细节进行分析。
首先调用chmod()函数改变"/proc/cmdline"的文件属性。
import_kernel_cmdline(0, import_kernel_nv);的定义在~/my_android/system/core/init/util.c中,实现代码如下:
void import_kernel_cmdline(int in_qemu, void (*import_kernel_nv)(char *name, int in_qemu)) { char cmdline[1024]; char *ptr; int fd; fd = open("/proc/cmdline", O_RDONLY); if (fd >= 0) { int n = read(fd, cmdline, 1023); if (n < 0) n = 0; /* get rid of trailing newline, it happens */ if (n > 0 && cmdline[n-1] == '\n') n--; cmdline[n] = 0; close(fd); } else { cmdline[0] = 0; } ptr = cmdline; while (ptr && *ptr) { char *x = strchr(ptr, ' '); if (x != 0) *x++ = 0; //可以拆分为*x = 0; x++; import_kernel_nv(ptr, in_qemu); ptr = x; } }
其后对cmdline的字符数组处理非常简单。然后到while()循环,
strchr函数返回第二个变量在第一个变量中第一次出现的位置,具体用法可参考:http://blog.csdn.net/sky2098/article/details/1530433
import_kernel_nv(ptr, in_qemu)定义在~/my_android/system/core/init/init.c中,实现的代码如下:
static void import_kernel_nv(char *name, int for_emulator) { char *value = strchr(name, '='); int name_len = strlen(name); if (value == 0) return; *value++ = 0; if (name_len == 0) return; #ifdef HAVE_SELINUX if (!strcmp(name,"selinux")) { selinux_enabled = atoi(value); } #endif if (for_emulator) { /* in the emulator, export any kernel option with the * ro.kernel. prefix */ char buff[PROP_NAME_MAX]; int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name ); if (len < (int)sizeof(buff)) property_set( buff, value ); return; } if (!strcmp(name,"qemu")) { strlcpy(qemu, value, sizeof(qemu)); #ifdef WANTS_EMMC_BOOT } else if (!strcmp(name,"androidboot.emmc")) { if (!strcmp(value,"true")) { emmc_boot = 1; } #endif } else if (!strcmp(name,BOARD_CHARGING_CMDLINE_NAME)) { strlcpy(battchg_pause, value, sizeof(battchg_pause)); } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) { const char *boot_prop_name = name + 12; char prop[PROP_NAME_MAX]; int cnt; cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name); if (cnt < PROP_NAME_MAX) property_set(prop, value); } }
然后,返回到process_kernel_cmdline()中,由于变量qemu的定义为:
static char qemu[32];
由于没有初始化,所以if(qemu[0])判断为否,所以接着执行export_kernel_boot_props()函数,其定义在~/my_android/system/core/init/init.c中,实现的代码如下:
static void export_kernel_boot_props(void) { char tmp[PROP_VALUE_MAX]; const char *pval; 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++) { pval = property_get(prop_map[i].src_prop); property_set(prop_map[i].dest_prop, pval ?: prop_map[i].def_val); } pval = property_get("ro.boot.console"); if (pval) strlcpy(console, pval, sizeof(console)); /* save a copy for init's usage during boot */ strlcpy(bootmode, property_get("ro.bootmode"), sizeof(bootmode)); /* if this was given on kernel command line, override what we read * before (e.g. from /proc/cpuinfo), if anything */ pval = property_get("ro.boot.hardware"); if (pval) strlcpy(hardware, pval, sizeof(hardware)); property_set("ro.hardware", hardware); snprintf(tmp, PROP_VALUE_MAX, "%d", revision); property_set("ro.revision", tmp); property_set("ro.emmc",emmc_boot ? "1" : "0"); property_set("ro.boot.emmc", emmc_boot ? "1" : "0"); /* 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函数的代码可以看出,该函数实际上就是来回设置一些属性值,并且利用某些属性值修改console、hardware等变量。其中hardware变量(就是一个长度为32的字符数组)在get_hardware_name函数中已经从/proc/cpuinfo文件中获得过一次值了,在export_kernel_boot_props函数中又通过ro.boot.hardware属性设置了一次值。
其中property_get(prop_map[i].src_prop)和property_set(prop_map[i].dest_prop, pval ?: prop_map[i].def_val);的定义在~/my_android/system/core/init/property_service.c中,实现的代码如下:
const char* property_get(const char *name) { prop_info *pi; if(strlen(name) >= PROP_NAME_MAX) return 0; pi = (prop_info*) __system_property_find(name); if(pi != 0) { return pi->value; } else { return 0; } }
int property_set(const char *name, const char *value) { prop_area *pa; prop_info *pi; int namelen = strlen(name); int valuelen = strlen(value); if(namelen >= PROP_NAME_MAX) return -1; if(valuelen >= PROP_VALUE_MAX) return -1; if(namelen < 1) return -1; pi = (prop_info*) __system_property_find(name); if(pi != 0) { /* ro.* properties may NEVER be modified once set */ if(!strncmp(name, "ro.", 3)) return -1; pa = __system_property_area__; update_prop_info(pi, value, valuelen); pa->serial++; __futex_wake(&pa->serial, INT32_MAX); } else { pa = __system_property_area__; if(pa->count == PA_COUNT_MAX) return -1; pi = pa_info_array + pa->count; pi->serial = (valuelen << 24); memcpy(pi->name, name, namelen + 1); memcpy(pi->value, value, valuelen + 1); pa->toc[pa->count] = (namelen << 24) | (((unsigned) pi) - ((unsigned) pa)); pa->count++; pa->serial++; __futex_wake(&pa->serial, INT32_MAX); } /* 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); #ifdef HAVE_SELINUX } else if (strcmp("selinux.reload_policy", name) == 0 && strcmp("1", value) == 0) { selinux_reload_policy(); #endif } property_changed(name, value); return 0; }
关于Android中的property机制,可参考http://leave001.blog.163.com/blog/static/1626912932013030101531571/
接下来的#ifdef HAVE_SELINUX……#endif,代码如下:
int main(int argc, char **argv) { ... #ifdef HAVE_SELINUX 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); INFO("loading selinux policy\n"); if (selinux_enabled) { if (selinux_android_load_policy() < 0) { selinux_enabled = 0; INFO("SELinux: Disabled due to failed policy load\n"); } else { selinux_init_all_handles(); } } else { INFO("SELinux: Disabled by command line option\n"); } /* 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"); #endif ... }
是和Security-Enhanced Android相关的。
参考: