Android 启动分析 1
1.概述
Android雖然被稱作一種操作系統,其實它仍然使用的Linux的kernel。所以本質上可以說,Android是一個適用于移動設備的Linux發行版。也就是說,之前的分析Linux內核的經驗可以拿來用于分析Android。不過,值得注意的是,Android除去對Linux內核的一些改動外,它的大部分代碼還是在Linux內核啟動后的用戶空間程序上。所以,分析Android代碼時,不僅要對Linux內核代碼熟悉,還要對熟悉Linux系統編程要用到的函數,比如fcntl、mmap、open、read、write等。
2. Android啟動流程概述
像大多數的Linux發行版那樣,在加載啟動kernel后,會執行第一個用戶空間程序/init。
簡單流程就是在start_kernel函數的最后調用函數rest_init。rest_init函數如下
這里通過內核產生線程kernel_init--我們所說的0號進程。kernel_init函數如下:
static int __ref kernel_init(void *unused) {kernel_init_freeable();....../*關鍵*/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中設置*/......panic("No init found. Try passing init= option to kernel. ""See Linux Documentation/init.txt for guidance."); }我們再看kernel_init中調用的kernel_init_freeable。kernel_init_freeable函數如下:
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*//*這是關鍵*/if (!ramdisk_execute_command)ramdisk_execute_command = "/init";//這里設置成/init,android系統的第一個用戶態程序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在啟動時會執行/init。我們整理一下android的啟動流程:
- start_kenel調用rest_init
- rest_init調用kernel_init
- kernel_init調用kernel_init_freeable
- kernel_init_freeable中把ramdisk_execute_command設置為/init
- 最后讓在kernel_init中調用run_init_process(ramdisk_execute_command)
那么/init是什么程序呢?
/init就是Android自己的程序了,源代碼位于/system/core/init/init.c中
仔細分析發現,該c文件中是有main函數的,說明該文件可以編譯鏈接成用戶態的可執行程序——即/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下的文件系統是tmpfs,之后的null設備和klog設備都是在該文件夾下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));可以注意到的是,由于已經是用戶態程序了。代碼中可以放心大膽的使用umask、mkdir、mount這些函數,所以熟悉Linux系統編程是很必要的(雖然Android沒用glibc,而是bionic,但是接口接口基本一致)。
這段代碼之簡單的掛載了一些必要的文件系統,剩下的要通過解析rc文件在掛載。
3.2?/init/程序的第二部分
open_devnull_stdio();klog_init();property_init();get_hardware_name(hardware, &revision);process_kernel_cmdline();3.2.1 函數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); }該函數創建一個null設備,然后通過dup2系統調用把它重定向到stdio、stdout、stderr上。注意這時還在/init進程中。
3.2.2函數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);//只能寫,我發現在klog.c中只有klog_write函數fcntl(klog_fd, F_SETFD, FD_CLOEXEC);unlink(name);} }通過fcntl設置FD_CLOEXEC標志有什么用?(注意當前只有一個這樣的file descriptor flag)
close on exec, not on-fork, 意為如果對描述符設置了FD_CLOEXEC,使用exec-family執行的程序里,此描述符被關閉,不能再使用它,但是在使用fork調用的子進程中,此描述符并不關閉,仍可使用。之后我們會發現/init中產生子進程都是通過fork后在exeve實現的,所以子進程(其實就是init啟動的那些服務)中klog文件是關閉的。
3.2.3函數property_init
- property_init是init_property_area的wrapper函數
- init_property_area調用init_workspace函數并給libc中的__system_property_area__賦值
函數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);//沒有用mknodif (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; }workspace定義
typedef struct { void *data;size_t size;int fd; } workspace;pa_workspace就是類型為workspace的全局變量,init_workspace函數通過open和mmap函數創建文件并映射到內存空間,并為字段data、size、fd賦初值,而data字段就是剛剛mmap映射的空間地址。
函數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]; };通過代碼
pa = pa_workspace.data; ...... __system_property_area__ = pa;libc庫要用到的全局變量__system_property_area__的值就被賦成上面講到的共享內存的地址了,這樣方便之后的/init產生的各個子進程都能使用libc庫的函數訪問這個地址。
可以看出__system_property_area__地址開始的部分的內容就是struct prop_area所定義的字段。
自此我們知道__system_property_area__、pa_workspace.data指向的是同一地址,之所以區分的原因是前者是libc庫的全局變量之后的進程都可以通過調用libc庫函數使用,而后者是/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]; };該結構就是property system中的key/alue鍵值對,也是全局變量pa_info_array的類型。
通過代碼
pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START);使得pa_info_array指向那塊共享內存地址后面PA_INFO_START處。
整個property system的數據結構如圖:
?這里只是對property system的數據結構的初始化,真正的服務還要在之后啟動。
3.2.4 函數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, ": ");......} }函數很簡單就是通過讀取/proc/cpuinfo中的信息獲得hardware和revision信息。函數的參數指向的是全局變量。
3.2.5 函數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(); }該函數分兩部分,第一部分讀取kernel cmdline,第二部分輸出kernel cmdline.
函數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;} }函數很簡單,就是讀取/proc/cmdline中內容,以空格為分隔符分解字符串,把分得的字符串傳給import_kernel_nv函數。(值得注意的是import_kernel_nv有兩個定義,此處通過參數傳進來的是/system/core/init/init.c中的static原型,在同目錄下的ueventd.c中也有)
函數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 );}} }該函數分兩種情況處理傳進來的字符串。
函數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"); }該函數就是把讀取的kernel cmdline賦值給property system.
3.3?/init/程序的第三部分
/*is_charger和bootmode的意義是一樣的,把bootmode轉換成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的充電模式,就是沒開機,屏幕顯示一個不斷充電的圖標的模式。 給函數做了以下事情(我們假設是非充電模式):
裝載默認的prop文件,在文件一般是/default.prop
解析/init.rc文件,在/init中注冊Action?和?Service,如何解析/init.rc,之后做詳細分析。
接下來的一些系列函數把從1)init.rc中注冊的Action以及2)一些Builtin Action加入到Action Queue中,因為只有Action Queue中的Action才會被執行。執行順序是先進先出的隊列模式。
其實本部分是Android啟動的重點內容,我會另開一篇文章詳細分析的。
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要執行,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循環中不斷從Action Queue中取得Action來執行Action中的命令。
execute_one_command();restart_processes();在execute_one_command函數中執行Action中的Command(關于Action的結構,我會在詳細介紹/init第三部分程序的文章中分析的)。然后在restart_processes中運行需要重啟的服務。
循環的最后通過poll處理property_set_fd、signal_fd、keychord_fd文件發生的POLLIN事件。這些文件的建立也是通過之前的Action中執行的命令建立的。
總結
以上是生活随笔為你收集整理的Android 启动分析 1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 退出出库复核是什么意思_细思极恐!为什么
- 下一篇: JavaWeb三大组件(ServletF