Android 源碼分析 -- (一) Android啟動過程


將創建的PDF文件導入到cnBlogs中排版實在是不太方便,所以直接分享到slideshare上了。

簡單分析了一下Android啟動過程,錯漏之處敬請指正。

 
Android 源碼分析 -- (一) Android啟動過程
1. 源碼文件路徑: platform/system/core/init/init.c
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;
if (!strcmp(basename(argv[0]), "ueventd"))
2) 基於C語言的風格,在函數入口處聲明一些后續會使用的變量。
0) 這個代碼文件主要用於實現Android系統中init進程 (init進程為Android系統中用戶空間啟動的第一個進程,其作用相當於Linux系統中的init進程)
      NOTE: 如果調用此文件生成的可執行文件的第一個參數為“ueventd”,那么此文件將實現Android系統中的 “ueventd” 進程。
1) 在進行編譯后,此文件生成的可執行程序名稱為”init”,同時會生成一個指向此文件的軟鏈接: “ueventd”
3) 如果執行此文件生成的可執行程序的方式類似於: “ueventd xxx”。也即是基於執行此文件對應的軟鏈接: /sbin/ueventd時會調用”ueventd_main”函數,進而生成”ueventd”       進程。
4) Android系統中的 ueventd 進程用於實現用戶態進程與內核進行數據通信。
5) 在Android系統的init.rc文件: platform/system/core/rootdir/init.rc中通過如下方式來啟動 ueventd 進程:
  on early-init
    # Set init and its forked children's oom_adj.
write /proc/1/oom_adj -16
start ueventd
return ueventd_main(argc, 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.
*/
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.
*/
open_devnull_stdio();
klog_init();
INFO("reading config file\n");
6) 生成Android系統中的一些基本的系統目錄並掛載對應的文件系統。
7) 生成 “/dev/__null__” 虛擬設備(類似於Linux系統中的 /dev/null 設備)並將stdin/stdout/stderr三個文件重定向到 “/dev/__null__”
8) 生成 ” /dev/__kmsg__” 虛擬設備用於記錄log。
Klog_init 實現文件: system/core//libcutils/klog.c
9) 解析 init.rc (platform/system/core/rootdir/init.rc)。
init_parse_config_file("/init.rc");
/* pull the kernel commandline and ramdisk properties file in */
import_kernel_cmdline(0, import_kernel_nv);
/* don't expose the raw commandline to nonpriv processes */
chmod("/proc/cmdline", 0440);
get_hardware_name(hardware, &revision);
snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware);
init_parse_config_file(tmp);
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(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");
/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);
10) 從 “/proc/cmdline” 中讀取內核命令行參數,
      對應函數實現路徑: platform/system/core/init/util.c
11) 在第 10 步讀取完 /proc/cmdline 中的參數后,修改此文件的權限,禁止非授權用戶操作此文件。
12) 從 “/proc/cpuinfo” 中讀取系統的CPU硬件信息。
        對應函數實現路徑: platform/system/core/init/util.c
13) 基於第12步中讀取的硬件信息來解析特定於硬件的配置信息。
14) 基於”early-init”,”property_init”,”keychord_init”,”console_init”標識,使用” queue_builtin_action”來過濾上述操作中解析的init.rc文件中的action        並將符合條件的action添加到對應的 action_queue 中,然后調用” action_for_each_trigger”來運行這些actions(實際上是action在list中的分類移動操作)。
        對應函數實現路徑: platform/system/core/init/init_parser.c
基於下面的運行邏輯可以看出,運行”init.rc”中的action的順序為:“early-init” -> “init” -> “early-boot” -> “boot”
/* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0)
{
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 (!strcmp(bootmode, "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_propety_triggers");
#if BOOTCHART
queue_builtin_action(bootchart_init_action, "bootchart_init");
#endif
for (;;)
{
int nr, i, timeout = -1;
execute_one_command();
restart_processes();
15) “init” 進程開始進行”循環事件處理”邏輯。
16) 運行第14步操作中所分類整理的action_queue中對應的action。
17) 查看當前的服務進程狀態,如果有服務進程退出,重啟對應服務進程。
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;
}
18) 基於 property_service 的事件句柄填充 poll event 結構體,用於后續poll操作。
22) 如果當前的action_queue 中有需要處理的action,那么下面調用poll時的timeout設置為0,這樣就不會因為poll在無事件激發時而阻塞導致當前的init進程            對action處理的的延遲,從而提高 init 進程對action處理的實時性。
19) 處理當前init 進程上接收到的signal,主要是處理SIGCHLD。
20) 基於 keychord service 的事件句柄填充 poll event 結構體,用於后續poll操作。
21) 如果有所監控的子進程退出,此時init進程需要負責重新啟動這些退出的服務進程,因此下面調用poll時的timeout設置為0,這樣就不會因為poll在無事件激發              時而阻塞導致當前的init進程對”重啟服務進程”處理的的延遲,從而可以盡快恢復退出的服務進程。
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())
23) 關於” BOOTCHART”參數的解釋:
/*
* 如果在編譯選項中添加了BOOTCHART 參數,那么意味着在系統的啟動
* 過程中需要生成bootchart(
http://www.bootchart.org/),用於后續
* Android 啟動過程中的性能分析並生成系統啟動過程的可視圖表。
* makefile中的編譯選項如下:
ifeq ($(strip $(INIT_BOOTCHART)),true)
LOCAL_SRC_FILES += bootchart.c
LOCAL_CFLAGS += -DBOOTCHART=1
endif
bootchart的實現文件: platform/system/core/init/bootchart.c
*/
24) 類似於網絡服務器開發中常見的基於”poll”機制來檢測所關注的句柄上是否有指定的事件激發。
25) 如果當前所關注的事件句柄上有事件發生,進行對應的事件處理。
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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM