首頁 > 軟體

Android 10 啟動Init程序解析

2022-10-12 14:01:58

按下電源鍵時,android做了啥?

當我們按下電源鍵時,手機開始上電,並從地址0x00000000處開始執行,而這個地址通常是Bootloader程式的首地址。

bootloader是一段裸機程式,是直接與硬體打交道的,其最終目的是“初始化並檢測硬體裝置,準備好軟體環境,最後呼叫作業系統核心”。除此之外,bootloader還有保護功能,部分品牌的手機對bootloader做了加鎖操作,防止boot分割區和recovery分割區被寫入。

或許有人會問了,什麼是boot分割區,什麼又是recovery分割區?

我們先來認識一下Android系統的常見分割區:

/boot

這個分割區上有Android的載入程式,包括核心和記憶體操作程式。沒有這個分割區裝置就不能被引導。恢復系統的時候會擦除這個分割區,並且必須重新安裝載入程式和ROM才能重啟系統。

/recovery

recovery分割區被認為是另一個啟動分割區,你可以啟動裝置進入recovery控制檯去執行高階的系統恢復和管理操作。

/data

這個分割區儲存著使用者資料。通訊錄、簡訊、設定和你安裝的apps都在這個分割區上。擦除這個分割區相當於恢復出廠設定,當你第一次啟動裝置的時候或者在安裝了官方或者客戶的ROM之後系統會自動重建這個分割區。當你執行恢復出廠設定時,就是在擦除這個分割區。

/cache

這個分割區是Android系統儲存頻繁存取的資料和app的地方。擦除這個分割區不影響你的個人資料,當你繼續使用裝置時,被擦除的資料就會自動被建立。

/apex

Android Q新增特性,將系統功能模組化,允許系統按模組來獨立升級。此分割區用於存放apex 相關的內容。

為什麼需要bootloader去拉起linux核心,而不把bootloader這些功能直接內建在linux核心中呢?這個問題不在此做出回答,留給大家自行去思考。

bootloader完成初始化工作後,會載入 /boot 目錄下面的 kernel,此時控制權轉交給作業系統。作業系統將要完成的儲存管理、裝置管理、檔案管理、程序管理、載入驅動等任務的初始化工作,以便進入使用者態。

核心啟動完成後,將會尋找init檔案(init檔案位於/system/bin/init),啟動init程序,也就是android的第一個程序。

我們來關注一下核心的common/init/main.c中的kernel_init方法。

static int __ref kernel_init(void *unused)
{
    ...
    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
    }
    if (CONFIG_DEFAULT_INIT[0] != '') {
    ret = run_init_process(CONFIG_DEFAULT_INIT);
    if (ret)
        pr_err("Default init %s failed (error %d)n",CONFIG_DEFAULT_INIT, ret);
      else
        return 0;
    }
if (!try_to_run_init_process("/sbin/init") ||!try_to_run_init_process("/etc/init") ||
    !try_to_run_init_process("/bin/init") ||!try_to_run_init_process("/bin/sh"))
    return 0;
}

可以看到,在init_kernel的最後,會呼叫run_init_process方法來啟動init程序。

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

kernel_execve是核心空間呼叫使用者空間的應用程式的函數。

接下來我們來重點分析init程序。

init程序解析

我們從system/core/init/main.cpp 這個檔案開始看起。

int main(int argc, char** argv) {
#if __has_feature(address_sanitizer)
    __asan_set_error_report_callback(AsanReportCallback);
#endif
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }
    if (argc > 1) {
        if (!strcmp(argv[1], "subcontext")) {
            android::base::InitLogging(argv, &android::base::KernelLogger);
            const BuiltinFunctionMap function_map;
            return SubcontextMain(argc, argv, &function_map);
        }
        if (!strcmp(argv[1], "selinux_setup")) {
            return SetupSelinux(argv);
        }
        if (!strcmp(argv[1], "second_stage")) {
            return SecondStageMain(argc, argv);
        }
    }
    return FirstStageMain(argc, argv);
}

第一個引數argc表示引數個數,第二個引數是參數列,也就是具體的引數。

main函數有四個引數入口:

  • 一是引數中有ueventd,進入ueventd_main
  • 二是引數中有subcontext,進入InitLogging 和SubcontextMain
  • 三是引數中有selinux_setup,進入SetupSelinux
  • 四是引數中有second_stage,進入SecondStageMain

main的執行順序如下:

  •  FirstStageMain  啟動第一階段
  • SetupSelinux    載入selinux規則,並設定selinux紀錄檔,完成SELinux相關工作
  • SecondStageMain  啟動第二階段
  •  ueventd_main    init程序建立子程序ueventd,並將建立裝置節點檔案的工作託付給ueventd。

FirstStageMain

我們來從FirstStageMain的原始碼看起,原始碼位於/system/core/init/first_stage_init.cpp

int FirstStageMain(int argc, char** argv) {
    boot_clock::time_point start_time = boot_clock::now();
#define CHECKCALL(x) 
    if (x != 0) errors.emplace_back(#x " failed", errno);
    // Clear the umask.
    umask(0);
    //初始化系統環境變數
    CHECKCALL(clearenv());
    CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
    // 掛載及建立基本的檔案系統,並設定合適的存取許可權
    CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
    CHECKCALL(mkdir("/dev/pts", 0755));
    CHECKCALL(mkdir("/dev/socket", 0755));
    CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
#define MAKE_STR(x) __STRING(x)
    CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
#undef MAKE_STR
    // 不要將原始命令列公開給非特權程序
    CHECKCALL(chmod("/proc/cmdline", 0440));
    gid_t groups[] = {AID_READPROC};
    CHECKCALL(setgroups(arraysize(groups), groups));
    CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
    CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));
    CHECKCALL(mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11)));
    if constexpr (WORLD_WRITABLE_KMSG) {
        CHECKCALL(mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11)));
    }
    //建立linux隨機偽裝置檔案
    CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));
    CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));
    //log wrapper所必須的,需要在ueventd執行之前被呼叫
    CHECKCALL(mknod("/dev/ptmx", S_IFCHR | 0666, makedev(5, 2)));
    CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));
    ...
    //將核心的stdin/stdout/stderr 全都重定向/dev/null,關閉預設控制檯輸出
    SetStdioToDevNull(argv);
    // tmpfs已經掛載到/dev上,同時我們也掛載了/dev/kmsg,我們能夠與外界開始溝通了
    //初始化核心log
    InitKernelLogging(argv);
    //檢測上面的操作是否發生了錯誤
    if (!errors.empty()) {
        for (const auto& [error_string, error_errno] : errors) {
            LOG(ERROR) << error_string << " " << strerror(error_errno);
        }
        LOG(FATAL) << "Init encountered errors starting first stage, aborting";
    }
    LOG(INFO) << "init first stage started!";
    auto old_root_dir = std::unique_ptr<DIR, decltype(&closedir)>{opendir("/"), closedir};
    if (!old_root_dir) {
        PLOG(ERROR) << "Could not opendir("/"), not freeing ramdisk";
    }
    struct stat old_root_info;
    ...
    //掛載 system、cache、data 等系統分割區
    if (!DoFirstStageMount()) {
        LOG(FATAL) << "Failed to mount required partitions early ...";
    }
    ...
    //進入下一步,SetupSelinux
    const char* path = "/system/bin/init";
    const char* args[] = {path, "selinux_setup", nullptr};
    execv(path, const_cast<char**>(args));
    return 1;
}

我們來總結一下,FirstStageMain到底做了哪些重要的事情:

  • 掛載及建立基本的檔案系統,並設定合適的存取許可權
  • 關閉預設控制檯輸出,並初始化核心級log。
  • 掛載 system、cache、data 等系統分割區

SetupSelinux

這個模組主要的工作是設定SELinux安全策略,本章內容主要聚焦於android的啟動流程,selinux的內容在此不做展開。

int SetupSelinux(char** argv) {
   ...
    const char* path = "/system/bin/init";
    const char* args[] = {path, "second_stage", nullptr};
    execv(path, const_cast<char**>(args));
    return 1;
}

SetupSelinux的最後,進入了init的第二階段SecondStageMain。

SecondStageMain

不多說,先上程式碼。

int SecondStageMain(int argc, char** argv) {
    // 禁止OOM killer 結束該程序以及它的子程序
    if (auto result = WriteFile("/proc/1/oom_score_adj", "-1000"); !result) {
        LOG(ERROR) << "Unable to write -1000 to /proc/1/oom_score_adj: " << result.error();
    }
    // 啟用全域性Seccomp,Seccomp是什麼請自行查閱資料
    GlobalSeccomp();
    // 設定所有程序都能存取的對談金鑰
    keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
    // 建立 /dev/.booting 檔案,就是個標記,表示booting進行中
    close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
    //初始化屬性的服務,並從指定檔案讀取屬性
    property_init();
     ...
    // 進行SELinux第二階段並恢復一些檔案安全上下文
    SelinuxSetupKernelLogging();
    SelabelInitialize();
    SelinuxRestoreContext();
    //初始化Epoll,android這裡對epoll做了一層封裝
    Epoll epoll;
    if (auto result = epoll.Open(); !result) {
        PLOG(FATAL) << result.error();
    }
    //epoll 中註冊signalfd,主要是為了建立handler處理子程序終止訊號
    InstallSignalFdHandler(&epoll);
    ...
    //epoll 中註冊property_set_fd,設定其他系統屬性並開啟系統屬性的服務
    StartPropertyService(&epoll);
    MountHandler mount_handler(&epoll);
    ...
    ActionManager& am = ActionManager::GetInstance();
    ServiceList& sm = ServiceList::GetInstance();
    //解析init.rc等檔案,建立rc檔案的action 、service,啟動其他程序,十分關鍵的一步
    LoadBootScripts(am, sm);
    ...
    am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
    //執行rc檔案中觸發器為 on early-init 的語句
    am.QueueEventTrigger("early-init");
    // 等冷插拔裝置初始化完成
    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
    am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
    // 裝置組合鍵的初始化操作
    Keychords keychords;
    am.QueueBuiltinAction(
        [&epoll, &keychords](const BuiltinArguments& args) -> Result<Success> {
            for (const auto& svc : ServiceList::GetInstance()) {
                keychords.Register(svc->keycodes());
            }
            keychords.Start(&epoll, HandleKeychord);
            return Success();
        },
        "KeychordInit");
    am.QueueBuiltinAction(console_init_action, "console_init");
   // 執行rc檔案中觸發器為on init的語句
    am.QueueEventTrigger("init");
    // Starting the BoringSSL self test, for NIAP certification compliance.
    am.QueueBuiltinAction(StartBoringSslSelfTest, "StartBoringSslSelfTest");
    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
    am.QueueBuiltinAction(InitBinder, "InitBinder");
    // 當裝置處於充電模式時,不需要mount檔案系統或者啟動系統服務,充電模式下,將charger設為執行佇列,否則把late-init設為執行佇列
    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }
    // 基於屬性當前狀態 執行所有的屬性觸發器.
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
    while (true) {
        //開始進入死迴圈狀態
        auto epoll_timeout = std::optional<std::chrono::milliseconds>{};
        //執行關機重啟流程
        if (do_shutdown && !shutting_down) {
            do_shutdown = false;
            if (HandlePowerctlMessage(shutdown_command)) {
                shutting_down = true;
            }
        }
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            am.ExecuteOneCommand();
        }
        if (!(waiting_for_prop || Service::is_exec_service_running())) {
            if (!shutting_down) {
                auto next_process_action_time = HandleProcessActions();
                // If there's a process that needs restarting, wake up in time for that.
                if (next_process_action_time) {
                    epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
                            *next_process_action_time - boot_clock::now());
                    if (*epoll_timeout < 0ms) epoll_timeout = 0ms;
                }
            }
            // If there's more work to do, wake up again immediately.
            if (am.HasMoreCommands()) epoll_timeout = 0ms;
        }
        // 迴圈等待事件發生
        if (auto result = epoll.Wait(epoll_timeout); !result) {
            LOG(ERROR) << result.error();
        }
    }
    return 0;
}

總結一下,第二階段做了以下這些比較重要的事情:

  • 初始化屬性的服務,並從指定檔案讀取屬性
  • 初始化epoll,並註冊signalfd和property_set_fd,建立和init的子程序以及部分服務的通訊橋樑
  • 初始化裝置組合鍵,使系統能夠對組合鍵訊號做出響應
  • 解析init.rc檔案,並按rc裡的定義去啟動服務
  • 開啟死迴圈,用於接收epoll的事件

在第二階段,我們需要重點關注以下問題:

init程序是如何通過init.rc組態檔去啟動其他的程序的呢?

init.rc 解析

我們從 LoadBootScripts(am, sm)這個方法開始看起,一步一部來挖掘init.rc 的解析流程。

static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
   //初始化ServiceParse、ActionParser、ImportParser三個解析器
   Parser parser = CreateParser(action_manager, service_list);
   std::string bootscript = GetProperty("ro.boot.init_rc", "");
   if (bootscript.empty()) {
       //bootscript為空,進入此分支
       parser.ParseConfig("/init.rc");
       if (!parser.ParseConfig("/system/etc/init")) {
           late_import_paths.emplace_back("/system/etc/init");
       }
       if (!parser.ParseConfig("/product/etc/init")) {
           late_import_paths.emplace_back("/product/etc/init");
       }
       if (!parser.ParseConfig("/product_services/etc/init")) {
           late_import_paths.emplace_back("/product_services/etc/init");
       }
       if (!parser.ParseConfig("/odm/etc/init")) {
           late_import_paths.emplace_back("/odm/etc/init");
       }
       if (!parser.ParseConfig("/vendor/etc/init")) {
           late_import_paths.emplace_back("/vendor/etc/init");
       }
   } else {
       parser.ParseConfig(bootscript);
   }
}

我們可以看到這句話,Parse開始解析init.rc檔案,在深入下去之前,讓我們先來認識一下init.rc。

 parser.ParseConfig("/init.rc")

init.rc是一個可設定的初始化檔案,負責系統的初步建立。它的原始檔的路徑為 /system/core/rootdir/init.rc

init.rc檔案有著固定的語法,由於內容過多,限制於篇幅的原因,在此另外單獨開了一篇文章進行講解:

Android 10 啟動分析之init語法

瞭解了init.rc的語法後,我們來看看init.rc檔案裡的內容。

import /init.environ.rc  //匯入全域性環境變數
import /init.usb.rc   //adb 服務、USB相關內容的定義
import /init.${ro.hardware}.rc  //硬體相關的初始化,一般是廠商客製化
import /vendor/etc/init/hw/init.${ro.hardware}.rc  
import /init.usb.configfs.rc  
import /init.${ro.zygote}.rc   //定義Zygote服務

我們可以看到,在/system/core/init目錄下,存在以下四個zygote相關的檔案

怎樣才能知道我們當前的手機用的是哪個組態檔呢?

答案是通過adb shell getprop | findstr ro.zygote命令,看看${ro.zygote}這個環境變數具體的值是什麼,筆者所使用的華為手機的ro.zygote值如下所示:

什麼是Zygote,Zygote的啟動過程是怎樣的,它的啟動組態檔裡又做了啥,在這裡我們不再做進一步探討, 只需要知道init在一開始在這個檔案中對Zygote服務做了定義,而上述的這些問題將留到 啟動分析之Zygote篇 再去說明。

on early-init
    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0
    # Set the security context of /adb_keys if present.
    restorecon /adb_keys
    # Set the security context of /postinstall if present.
    restorecon /postinstall
    mkdir /acct/uid
    # memory.pressure_level used by lmkd
    chown root system /dev/memcg/memory.pressure_level
    chmod 0040 /dev/memcg/memory.pressure_level
    # app mem cgroups, used by activity manager, lmkd and zygote
    mkdir /dev/memcg/apps/ 0755 system system
    # cgroup for system_server and surfaceflinger
    mkdir /dev/memcg/system 0550 system system
    start ueventd
    # Run apexd-bootstrap so that APEXes that provide critical libraries
    # become available. Note that this is executed as exec_start to ensure that
    # the libraries are available to the processes started after this statement.
    exec_start apexd-bootstrap

緊接著是一個Action,Action的Trigger 為early-init,在這個 Action中,我們需要關注最後兩行,它啟動了ueventd服務和apex相關服務。還記得什麼是ueventd和apex嗎?不記得的讀者請往上翻越再自行回顧一下。

ueventd服務的定義也可以在init.rc檔案的結尾找到,具體程式碼及含義如下:

service ueventd    //ueventd服務的可執行檔案的路徑為 /system/bin/ueventd
    class core    //ueventd 歸屬於 core class,同樣歸屬於core class的還有adbd 、console等服務
    critical //表明這個Service對裝置至關重要,如果Service在四分鐘內退出超過4次,則裝置將重啟進入恢復模式。
    seclabel u:r:ueventd:s0 //selinux相關的設定
    shutdown critical  //ueventd服務關閉行為

然而,early-init 這個Trigger到底什麼時候觸發呢?

答案是通過init.cpp程式碼呼叫觸發。

我們可以在init.cpp 程式碼中找到如下程式碼片段:

am.QueueEventTrigger("early-init");

QueueEventTrigger這個方法的實現機制我們稍後再進行探討,目前我們只需要瞭解, ActionManager 這個類中的 QueueEventTrigger方法,負責觸發init.rc中的Action。

我們繼續往下看init.rc的內容。

on init
    ...
    # Start logd before any other services run to ensure we capture all of their logs.
    start logd
    # Start essential services.
    start servicemanager
    ...

在Trigger 為init的Action中,我們只需要關注以上的關鍵內容。在init的action中啟動了一些核心的系統服務,這些服務具體的含義為 :

服務名含義
logdAndroid L加入的服務,用於儲存Android執行期間的紀錄檔
servicemanagerandroid系統服務管理者,負責查詢和註冊服務

接下來是late-init Action:

on late-init
    //啟動vold服務(管理和控制Android平臺外部儲存裝置,包括SD插撥、掛載、解除安裝、格式化等)
    trigger early-fs
    trigger fs
    trigger post-fs
    trigger late-fs
    //掛載/data , 啟動 apexd 服務
    trigger post-fs-data
    # 讀取持久化屬性或者從/data 中讀取並覆蓋屬性
    trigger load_persist_props_action
    //啟動zygote服務!!在啟動zygote服務前會先啟動netd服務(專門負責網路管理和控制的後臺守護行程)
    trigger zygote-start
    //移除/dev/.booting 檔案
    trigger firmware_mounts_complete
    trigger early-boot
    trigger boot  //初始化網路環境,設定系統環境和守護行程的許可權

最後,我們用流程圖來總結一下上述的啟動過程:

以上就是Android 10 啟動Init程序解析的詳細內容,更多關於Android 10 啟動Init程序的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com