日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android 启动分析 1

發布時間:2025/3/15 Android 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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函數如下

static noinline void __init_refok rest_init(void) {....../*這里是關鍵*/ kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);/*看上邊*/numa_default_policy();pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);rcu_read_lock();kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);rcu_read_unlock();complete(&kthreadd_done);....... }

這里通過內核產生線程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 );}} }

該函數分兩種情況處理傳進來的字符串。

  • 不再模擬器中時,解析字符串,賦值給qemu,console等/init的全局變量。
  • 在模擬器中時,則給字符串添加ro.kernel.的前綴,然后添加到property system中。
  • 函數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的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。