Android 启动分析 1
1.概述
Android雖然被稱作一種操作系統(tǒng),其實(shí)它仍然使用的Linux的kernel。所以本質(zhì)上可以說(shuō),Android是一個(gè)適用于移動(dòng)設(shè)備的Linux發(fā)行版。也就是說(shuō),之前的分析Linux內(nèi)核的經(jīng)驗(yàn)可以拿來(lái)用于分析Android。不過(guò),值得注意的是,Android除去對(duì)Linux內(nèi)核的一些改動(dòng)外,它的大部分代碼還是在Linux內(nèi)核啟動(dòng)后的用戶空間程序上。所以,分析Android代碼時(shí),不僅要對(duì)Linux內(nèi)核代碼熟悉,還要對(duì)熟悉Linux系統(tǒng)編程要用到的函數(shù),比如fcntl、mmap、open、read、write等。
2. Android啟動(dòng)流程概述
像大多數(shù)的Linux發(fā)行版那樣,在加載啟動(dòng)kernel后,會(huì)執(zhí)行第一個(gè)用戶空間程序/init。
簡(jiǎn)單流程就是在start_kernel函數(shù)的最后調(diào)用函數(shù)rest_init。rest_init函數(shù)如下
這里通過(guò)內(nèi)核產(chǎn)生線程kernel_init--我們所說(shuō)的0號(hào)進(jìn)程。kernel_init函數(shù)如下:
static int __ref kernel_init(void *unused) {kernel_init_freeable();....../*關(guān)鍵*/if (ramdisk_execute_command) {if (!run_init_process(ramdisk_execute_command))return 0;pr_err("Failed to execute %s\n", ramdisk_execute_command);}/*這里ramdisk_execute_command是有值的,在kernel_init_freeable中設(shè)置*/......panic("No init found. Try passing init= option to kernel. ""See Linux Documentation/init.txt for guidance."); }我們?cè)倏磌ernel_init中調(diào)用的kernel_init_freeable。kernel_init_freeable函數(shù)如下:
static noinline void __init kernel_init_freeable(void) {....../* Open the /dev/console on the rootfs, this should never fail */if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)pr_err("Warning: unable to open an initial console.\n");(void) sys_dup(0);(void) sys_dup(0);/** check if there is an early userspace init. If yes, let it do all* the work*//*這是關(guān)鍵*/if (!ramdisk_execute_command)ramdisk_execute_command = "/init";//這里設(shè)置成/init,android系統(tǒng)的第一個(gè)用戶態(tài)程序if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {ramdisk_execute_command = NULL;prepare_namespace();}/*向上看*//** Ok, we have completed the initial bootup, and* we're essentially up and running. Get rid of the* initmem segments and start the user-mode stuff..*//* rootfs is available now, try loading default modules */load_default_modules(); }所以android在啟動(dòng)時(shí)會(huì)執(zhí)行/init。我們整理一下android的啟動(dòng)流程:
- start_kenel調(diào)用rest_init
- rest_init調(diào)用kernel_init
- kernel_init調(diào)用kernel_init_freeable
- kernel_init_freeable中把ramdisk_execute_command設(shè)置為/init
- 最后讓在kernel_init中調(diào)用run_init_process(ramdisk_execute_command)
那么/init是什么程序呢?
/init就是Android自己的程序了,源代碼位于/system/core/init/init.c中
仔細(xì)分析發(fā)現(xiàn),該c文件中是有main函數(shù)的,說(shuō)明該文件可以編譯鏈接成用戶態(tài)的可執(zhí)行程序——即/init
3.?/init分析
3.1?/init程序的第一部分
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.*/mkdir("/dev", 0755);mkdir("/proc", 0755);mkdir("/sys", 0755);/*dev下的文件系統(tǒng)是tmpfs,之后的null設(shè)備和klog設(shè)備都是在該文件夾下mknod做成的*/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));可以注意到的是,由于已經(jīng)是用戶態(tài)程序了。代碼中可以放心大膽的使用umask、mkdir、mount這些函數(shù),所以熟悉Linux系統(tǒng)編程是很必要的(雖然Android沒(méi)用glibc,而是bionic,但是接口接口基本一致)。
這段代碼之簡(jiǎn)單的掛載了一些必要的文件系統(tǒng),剩下的要通過(guò)解析rc文件在掛載。
3.2?/init/程序的第二部分
open_devnull_stdio();klog_init();property_init();get_hardware_name(hardware, &revision);process_kernel_cmdline();3.2.1 函數(shù)open_devnull_stdio(在/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); }該函數(shù)創(chuàng)建一個(gè)null設(shè)備,然后通過(guò)dup2系統(tǒng)調(diào)用把它重定向到stdio、stdout、stderr上。注意這時(shí)還在/init進(jìn)程中。
3.2.2函數(shù)klong_init(在/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);//只能寫(xiě),我發(fā)現(xiàn)在klog.c中只有klog_write函數(shù)fcntl(klog_fd, F_SETFD, FD_CLOEXEC);unlink(name);} }通過(guò)fcntl設(shè)置FD_CLOEXEC標(biāo)志有什么用?(注意當(dāng)前只有一個(gè)這樣的file descriptor flag)
close on exec, not on-fork, 意為如果對(duì)描述符設(shè)置了FD_CLOEXEC,使用exec-family執(zhí)行的程序里,此描述符被關(guān)閉,不能再使用它,但是在使用fork調(diào)用的子進(jìn)程中,此描述符并不關(guān)閉,仍可使用。之后我們會(huì)發(fā)現(xiàn)/init中產(chǎn)生子進(jìn)程都是通過(guò)fork后在exeve實(shí)現(xiàn)的,所以子進(jìn)程(其實(shí)就是init啟動(dòng)的那些服務(wù))中klog文件是關(guān)閉的。
3.2.3函數(shù)property_init
- property_init是init_property_area的wrapper函數(shù)
- init_property_area調(diào)用init_workspace函數(shù)并給libc中的__system_property_area__賦值
函數(shù)init_workspace
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);//沒(méi)有用mknodif (fd < 0)return -1;if (ftruncate(fd, size) < 0)//調(diào)整文件大小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; }workspace定義
typedef struct { void *data;size_t size;int fd; } workspace;pa_workspace就是類型為workspace的全局變量,init_workspace函數(shù)通過(guò)open和mmap函數(shù)創(chuàng)建文件并映射到內(nèi)存空間,并為字段data、size、fd賦初值,而data字段就是剛剛mmap映射的空間地址。
函數(shù)init_property_area
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; }struct prop_area定義
(/bionic/libc/include/sys/_system_properties.h)
struct prop_area {unsigned volatile count;unsigned volatile serial;unsigned magic;unsigned version;unsigned reserved[4];unsigned toc[1]; };通過(guò)代碼
pa = pa_workspace.data; ...... __system_property_area__ = pa;libc庫(kù)要用到的全局變量__system_property_area__的值就被賦成上面講到的共享內(nèi)存的地址了,這樣方便之后的/init產(chǎn)生的各個(gè)子進(jìn)程都能使用libc庫(kù)的函數(shù)訪問(wèn)這個(gè)地址。
可以看出__system_property_area__地址開(kāi)始的部分的內(nèi)容就是struct prop_area所定義的字段。
自此我們知道__system_property_area__、pa_workspace.data指向的是同一地址,之所以區(qū)分的原因是前者是libc庫(kù)的全局變量之后的進(jìn)程都可以通過(guò)調(diào)用libc庫(kù)函數(shù)使用,而后者是/init本身的全局變量可以直接使用。
struct prop_info定義
(/bionic/libc/include/sys/_system_properties.h)
struct prop_info {char name[PROP_NAME_MAX];unsigned volatile serial;char value[PROP_VALUE_MAX]; };該結(jié)構(gòu)就是property system中的key/alue鍵值對(duì),也是全局變量pa_info_array的類型。
通過(guò)代碼
pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);使得pa_info_array指向那塊共享內(nèi)存地址后面PA_INFO_START處。
整個(gè)property system的數(shù)據(jù)結(jié)構(gòu)如圖:
?這里只是對(duì)property system的數(shù)據(jù)結(jié)構(gòu)的初始化,真正的服務(wù)還要在之后啟動(dòng)。
3.2.4 函數(shù)get_hardware_name
/system/core/init/util.c
void get_hardware_name(char *hardware, unsigned int *revision) {......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 (rev) {x = strstr(rev, ": ");......} }函數(shù)很簡(jiǎn)單就是通過(guò)讀取/proc/cpuinfo中的信息獲得hardware和revision信息。函數(shù)的參數(shù)指向的是全局變量。
3.2.5 函數(shù)process_kernel_cmdline
/system/core/init/init.c
static void process_kernel_cmdline(void) {......import_kernel_cmdline(0, import_kernel_nv);if (qemu[0])//qemu是`/init`的全局變量import_kernel_cmdline(1, import_kernel_nv);export_kernel_boot_props(); }該函數(shù)分兩部分,第一部分讀取kernel cmdline,第二部分輸出kernel cmdline.
函數(shù)import_kernel_cmdline(在/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);.....close(fd);} else {cmdline[0] = 0;}ptr = cmdline;while (ptr && *ptr) {char *x = strchr(ptr, ' ');if (x != 0) *x++ = 0;import_kernel_nv(ptr, in_qemu);ptr = x;} }函數(shù)很簡(jiǎn)單,就是讀取/proc/cmdline中內(nèi)容,以空格為分隔符分解字符串,把分得的字符串傳給import_kernel_nv函數(shù)。(值得注意的是import_kernel_nv有兩個(gè)定義,此處通過(guò)參數(shù)傳進(jìn)來(lái)的是/system/core/init/init.c中的static原型,在同目錄下的ueventd.c中也有)
函數(shù)import_kernel_nv
static void import_kernel_nv(char *name, int in_qemu) {char *value = strchr(name, '=');if (value == 0) return;*value++ = 0;if (*name == 0) return;if (!in_qemu){/* on a real device, white-list the kernel options */if (!strcmp(name,"qemu")) {strlcpy(qemu, value, sizeof(qemu));} else if (!strcmp(name,"androidboot.console")) {strlcpy(console, value, sizeof(console));} else if (!strcmp(name,"androidboot.mode")) {strlcpy(bootmode, value, sizeof(bootmode));} else if (!strcmp(name,"androidboot.serialno")) {strlcpy(serialno, value, sizeof(serialno));} else if (!strcmp(name,"androidboot.baseband")) {strlcpy(baseband, value, sizeof(baseband));} else if (!strcmp(name,"androidboot.carrier")) {strlcpy(carrier, value, sizeof(carrier));} else if (!strcmp(name,"androidboot.bootloader")) {strlcpy(bootloader, value, sizeof(bootloader));} else if (!strcmp(name,"androidboot.hardware")) {strlcpy(hardware, value, sizeof(hardware));} else if (!strcmp(name,"androidboot.modelno")) {strlcpy(modelno, value, sizeof(modelno));}} else {/* in the emulator, export any kernel option with the* ro.kernel. prefix */char buff[32];int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );if (len < (int)sizeof(buff)) {property_set( buff, value );}} }該函數(shù)分兩種情況處理傳進(jìn)來(lái)的字符串。
函數(shù)export_kernel_boot_props(在/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);/* 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");elseproperty_set("ro.factorytest", "0"); }該函數(shù)就是把讀取的kernel cmdline賦值給property system.
3.3?/init/程序的第三部分
/*is_charger和bootmode的意義是一樣的,把bootmode轉(zhuǎn)換成is_charger的init變量方便之后的判斷*/is_charger = !strcmp(bootmode, "charger");INFO("property init\n");if (!is_charger)property_load_boot_defaults();//裝載/default.propINFO("reading config file\n");init_parse_config_file("/init.rc");//解析/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(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);}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");這里charger mode就是android的充電模式,就是沒(méi)開(kāi)機(jī),屏幕顯示一個(gè)不斷充電的圖標(biāo)的模式。 給函數(shù)做了以下事情(我們假設(shè)是非充電模式):
裝載默認(rèn)的prop文件,在文件一般是/default.prop
解析/init.rc文件,在/init中注冊(cè)Action?和?Service,如何解析/init.rc,之后做詳細(xì)分析。
接下來(lái)的一些系列函數(shù)把從1)init.rc中注冊(cè)的Action以及2)一些Builtin Action加入到Action Queue中,因?yàn)橹挥?strong>Action Queue中的Action才會(huì)被執(zhí)行。執(zhí)行順序是先進(jìn)先出的隊(duì)列模式。
其實(shí)本部分是Android啟動(dòng)的重點(diǎn)內(nèi)容,我會(huì)另開(kāi)一篇文章詳細(xì)分析的。
3.4?/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;}/*只要還有action要執(zhí)行,timeout就是0,也就是不等待*/if (!action_queue_empty() || cur_action)timeout = 0;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();}}}/init的最后一部分就是在for循環(huán)中不斷從Action Queue中取得Action來(lái)執(zhí)行Action中的命令。
execute_one_command();restart_processes();在execute_one_command函數(shù)中執(zhí)行Action中的Command(關(guān)于Action的結(jié)構(gòu),我會(huì)在詳細(xì)介紹/init第三部分程序的文章中分析的)。然后在restart_processes中運(yùn)行需要重啟的服務(wù)。
循環(huán)的最后通過(guò)poll處理property_set_fd、signal_fd、keychord_fd文件發(fā)生的POLLIN事件。這些文件的建立也是通過(guò)之前的Action中執(zhí)行的命令建立的。
總結(jié)
以上是生活随笔為你收集整理的Android 启动分析 1的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 退出出库复核是什么意思_细思极恐!为什么
- 下一篇: Android init.rc分析