Android筆記 - Android啓動之Linux內核啓動

Android 的底層基於 Linux Kernel,因此從啓動流程來看,先啓動 Linux Kernel,然後才啓動 Android Framework,最後進入應用程序 Launcher,也就是看到的主界面。因爲這一流程複雜且冗長,所以分爲三篇文章來介紹,其實也就是 Android 啓動的三個階段。第一個階段是 Linux 啓動過程,包含上電後從 Bootrom 開始,到進入 BootLoader,然後運行 Linux Kernel。第二階段是 Android Framework 的啓動過程,包含啓動 Zygote,System Server,servicemanager 等 Android 核心服務進程。第三階段爲啓動第一個 Android 應用程序 Launcher 的過程。

Android 支持多種啓動模式,主要有正常模式(normal mode),恢復模式(recovery mode),快速啓動模式(fastboot mode)等,其中後兩者是刷機模式。通過以下方式可以快速進入刷機模式:
adb reboot recovery 進入 recovery 模式
adb reboot bootloader 進入 fastboot 模式

第一階段又可以分成三個小步驟(有的資料會把步驟1和步驟2作爲一個整體,統稱爲 Bootloader)。
1. 機器上電,進入Bootrom
Bootrom 是固化在芯片中的一小段程序,主要功能是上電時完成硬件自檢,然後從固定分區加載 Bootloader。嚴格來說,由於 Bootrom 需要儘可能精簡,一般只會加載 Bootloader 頭部一小段鏡像內容,再由這一小段鏡像加載剩餘 Bootloader。

Bootrom 的功能相當於 PC 上的 BIOS,Bootloader 的功能相當於 PC 上的 GRUB,一般每個 Android 廠商都會根據實際需要對 Bootloader 進行客製化。此外,一些手機廠商會鎖住 BootLoader,這樣確保用戶只能使用官方的系統。如果想要運行第三方的 ROM,那麼就需要對 bootloader 進行解鎖。

2. Bootloader初始化軟硬件環境
Bootloader 是在進入 Linux Kernel 之前運行的程序。顧名思義,Bootloader 可以理解成 boot 和 loader 的混合體。

Bootloader 沒有統一標準,有很多種實現方式,常用的實現就有 Uboot, GRUB, LILO 等,Android 採用的 Bootloader 實現方式是 Uboot。Uboot 功能非常強大,基本支持所有通用的硬件體系結構。看過 Uboot 的代碼就可以知道,整個啓動過程絕對可以稱得上曲折艱難。如果編譯 Uboot 時將 bootdelay 參數設置爲大於0,則可以停留在 Uboot 中執行命令,比如從其他載體中手動加載 Kernel 運行,當然也可以手動升級其他分區內容。

boot 原來的意思是靴子,它來源於一句西方諺語:

pull oneself up by one’s bootstraps

中文意思是“拽着鞋帶把自己拉起來”。這個比喻在 Android 機器啓動過程中確實也生動形象。因爲 Bootloader 鏡像一般都是燒寫在 NAND Flash 中,而 NAND Flash 中一般是不能運行程序的。Bootloader 需要自己把鏡像從 Flash 加載進內存中,然後運行起來。

Flash 分爲 NAND Flash 和 NOR Flash,是市場上兩種主要的非易失閃存介質。由於工藝原理不同,兩者區別很大。總的來說,NAND Flash 容量大,價格便宜,主要用於存儲數據。而NOR Flash 則相反,主要用於存儲代碼。NAND Flash 只能順序訪問,訪問速度相對較慢,而 NOR Flash 可以隨機訪問,訪問速度快,在一些單片機中會被當成 RAM 使用。

loader 的意思是加載,實際上是初始化好 Kernel 運行時需要的設備環境,然後把 Kernel 加載進內存運行。

3. 啓動 Linux Kernel
這一階段和標準 Linux Kernel 啓動過程基本一致。Kernel 加載進內存後會進行自解壓。自解壓完成後,繼續進行一些平臺相關的初始化,然後開始 start_kernel 函數,它的實現代碼位於 init/main.c 中。

start_kernel 函數會進行一些軟件相關的初始化,解析 bootloader 傳過來的啓動參數,最後調用 rest_init 函數啓動 init 進程。

static noinline void __init_refok rest_init(void)
{
    ......


    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    ......
}

rest_init 函數會調用 kernel_thread 函數克隆出 init 子進程和 kthreadd 子進程。其中 init 子進程是 Linux 用戶空間的1號進程,擔負着在第二階段啓動 Android Framework 的重要職責。我們知道 kernel_thread 的第一個參數是進程執行的函數體,因此 init 進程會開始執行 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);
    }
    ......
}

其中,在 kernel_init_freeable 函數中會將 ramdisk_execute_command 變量賦值爲 /init,如下所示:

static noinline void __init kernel_init_freeable(void) {
    ......

    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";
    ......
}

最終,run_init_process 函數會執行 /init 程序,其源代碼位於 /system/core/init/init.c 中。

static int run_init_process(const char *init_filename)
{
    argv_init[0] = init_filename;
    return do_execve(init_filename,
        (const char __user *const __user *)argv_init,
        (const char __user *const __user *)envp_init);
}

啓動 init 進程的過程雖然比較冗長,但是基本流程和其他進程創建過程一樣,都是先調用 fork 函數克隆出子進程,然後在子進程中調用 exec family 函數運行應用程序。

至此,第一階段的 Linux 啓動過程就完成了。下面開始 init 進程中啓動 Android Framework 的過程。

參考學習資料:
1. Booting Linux kernel using U-Boot

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章