Android系統啓動流程(一)—— init進程的啓動流程

最近開始閱讀android系統啓動模塊的源碼了,記錄一下從中學到的東西。 文章中的源碼基於android8.0.0java

init進程是android在用戶空間啓動的第一個進程,也是用戶空間其餘進程的父進程,它的進程號是1,系統經過init進程來進行一些初始化工做,包括啓動Zygoto、SystemServer等重要進程。linux

系統在加載linux內核後便會建立init進程,並會執行init文件中的main方法,接下來咱們經過分析main方法的代碼來看一下init進程的啓動流程。android

1.執行第一階段,掛載系統運行時目錄

源碼路徑:\system\core\init\init.cppios

int main(int argc, char** argv) {

    // 1.根據參數argv來判斷是不是ueventd或watchdogd
    //strcmp函數用來比較字符串的值是否相等,相等返回0
    //basename函數用來獲取字符串中最後一個‘/’以後的內容
    if (!strcmp(basename(argv[0]), "ueventd")) {
        return ueventd_main(argc, argv);
    }

    if (!strcmp(basename(argv[0]), "watchdogd")) {
        return watchdogd_main(argc, argv);
    }

    if (REBOOT_BOOTLOADER_ON_PANIC) {
        install_reboot_signal_handlers();//設置一些信號量
    }

    //添加環境變量
    add_environment("PATH", _PATH_DEFPATH);

    // 2.判斷是不是第一階段
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {//if內的代碼只在第一階段執行
        boot_clock::time_point start_time = boot_clock::now();

        // 清空文件權限
        umask(0);

        // 3.掛載和建立一些系統運行時必要的目錄
        mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
        mkdir("/dev/pts", 0755);
        mkdir("/dev/socket", 0755);
        mount("devpts", "/dev/pts", "devpts", 0, NULL);
        #define MAKE_STR(x) __STRING(x)
        mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
        // Don't expose the raw commandline to unprivileged processes.
        chmod("/proc/cmdline", 0440);
        gid_t groups[] = { AID_READPROC };
        setgroups(arraysize(groups), groups);
        mount("sysfs", "/sys", "sysfs", 0, NULL);
        mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
        mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
        mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
        mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));

        //初始化log
        InitKernelLogging(argv);

        LOG(INFO) << "init first stage started!";
        
        //準備開始轉入第二階段
       ...
    }

    ...
}
複製代碼

在註釋1處,首先經過main函數的參數argv來判斷是不是ueventd或watchdogd,咱們閱讀源碼主要是爲了瞭解init進程的啓動流程,就不對ueventd和watchdogd進行研究了。c++

在註釋2處,經過環境變量INIT_SECOND_STAGE來判斷當前是第一階段仍是第二階段,在init進程的啓動過程當中,main函數一共會執行兩次,分別表明第一階段和第二階段,若是是第一階段,則執行if內的代碼。windows

在註釋3處,經過mount和mkdir來掛載和建立了一些系統運行時所須要的目錄,這些目錄只有在系統運行時纔會建立,當系統關閉後這些目錄都會消失。安全

以後便開始準備進入第二階段。markdown

2.轉入第二階段

//初始化Selinux安全模塊,SELinux是美國國家安全局(NSA)對於強制訪問控制的實現,是 Linux的一個安全子系統
        selinux_initialize(true);

       ...

        // 1.設置環境變量,將INIT_SECOND_STAGE的值設置爲true
        setenv("INIT_SECOND_STAGE", "true", 1);

        static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
        uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
        setenv("INIT_STARTED_AT", StringPrintf("%" PRIu64, start_ms).c_str(), 1);

        char* path = argv[0];
        char* args[] = { path, nullptr };
        //2.從新執行main方法
        execv(path, args);
複製代碼

在註釋1處將環境變量INIT_SECOND_STAGE設置爲true,而後再註釋2處經過execv函數從新執行main方法,其中path的值即當前init文件的路徑,execv會終止當前進程並根據path執行新的進程,可是進程id不會改變。app

3.開啓屬性服務並初始化信號處理函數

//第二階段從新執行main方法
int main(int argc, char** argv) {
    ...

    //因爲INIT_SECOND_STAGE的值已經爲true了,當第二次運行main函數時會跳過if (is_first_stage) 內的代碼.
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if (is_first_stage) {
        ...
    }

    InitKernelLogging(argv);
    LOG(INFO) << "init second stage started!";//表示如今已經處於第二階段了

   ...

    property_init();// 1.初始化屬性服務
    
    process_kernel_dt();//處理DT屬性
    process_kernel_cmdline();//處理命令行屬性

    export_kernel_boot_props();//處理系統屬性

   ...

    // 清除不須要的環境變量
    unsetenv("INIT_SECOND_STAGE");
    unsetenv("INIT_STARTED_AT");
    unsetenv("INIT_SELINUX_TOOK");
    unsetenv("INIT_AVB_VERSION");

    // 加載Selinux
    selinux_initialize(false);
    selinux_restore_context();

    // 2.初始化信號處理函數
    signal_handler_init();

    property_load_boot_defaults();
    export_oem_lock_status();

    //開始屬性服務
    start_property_service();

    set_usb_controller();
    ...
複製代碼

如今進入第二階段 。上面的這段代碼的邏輯比較簡單,主要開啓了屬性服務並初始化了信號處理函數。dom

在註釋1出對屬性服務進行了初始化,屬性服務的做用相似於windows系統的註冊表。

在註釋2出初始化信號處理函數,它的主要做用是防止init進程的子進程成爲殭屍進程。

在註釋3出開啓了屬性服務。

4.解析init.rc文件

...
    Parser& parser = Parser::GetInstance();  // 1.獲取解析器對象
    parser.AddSectionParser("service",std::make_unique<ServiceParser>());//加載解析Service語句的解析器
    parser.AddSectionParser("on", std::make_unique<ActionParser>());//加載解析on語句的解析器
    parser.AddSectionParser("import", std::make_unique<ImportParser>());//加載解析import語句的解析器
    std::string bootscript = GetProperty("ro.boot.init_rc", "");
    if (bootscript.empty()) {
        parser.ParseConfig("/init.rc");// 2.解析init.rc文件
        parser.set_is_system_etc_init_loaded(
                parser.ParseConfig("/system/etc/init"));
        parser.set_is_vendor_etc_init_loaded(
                parser.ParseConfig("/vendor/etc/init"));
        parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
    } else {
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    }

    ...
複製代碼

這段代碼主要用來解析init.rc文件,在註釋1處,經過Parser::GetInstance()獲取一個Parser解析器對象,而後使用AddSectionParser方法爲該對象添加了service、on和import語句的解析器。

在註釋2處調用了parser的ParseConfig方法,對init.rc文件進行解析

咱們先來了解一下什麼是.rc文件。

4.1 .rc文件簡介

.rc文件是使用android初始化語言編寫的一種腳本,這種語言由許多section塊組成,這些section能夠分爲兩類:動做(Action)和服務(Service),它們的基本形式以下:

//動做(Action)
on <trigger> [&&<trigger>]*  //設置觸發器,Action類型的語句爲on
    <command>  //觸發以後要執行的命令
    <command>
    ...

//服務(Service)
service <name> <pathname> [<argument>]*  //<service的名稱> <執行程序的路徑> <參數>
    <option>  //一些選項
    <option>

複製代碼

以init.rc中的一段代碼爲例:

on early-init  //設置early-init觸發器
   
    //下面的語句都是一些command,當early-init被觸發後便執行下面的語句

    write /proc/1/oom_score_adj -1000

    # Disable sysrq from keyboard
    write /proc/sys/kernel/sysrq 0

    # Set the security context of /adb_keys if present.
    restorecon /adb_keys

    # Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
    mkdir /mnt 0775 root system

    # Set the security context of /postinstall if present.
    restorecon /postinstall

    start ueventd
複製代碼

在.rc文件中還有一類import語句,用來引入其餘.rc文件。

4.2init.rc源碼分析

咱們來看一下init.rc文件的源碼:

源碼路徑:\system\core\rootdir\init.rc

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc  // 1.加載init.zygote.rc文件

//下面是一系列的觸發器

on early-init
   
    write /proc/1/oom_score_adj -1000

    write /proc/sys/kernel/sysrq 0

    restorecon /adb_keys

    mkdir /mnt 0775 root system

    restorecon /postinstall

    start ueventd

on init
    sysclktz 0

    copy /proc/cmdline /dev/urandom
    copy /default.prop /dev/urandom

    symlink /system/etc /etc
    symlink /sys/kernel/debug /d

    symlink /system/vendor /vendor

    mount cgroup none /acct cpuacct
    mkdir /acct/uid
    ... 

on late-init
    trigger early-fs

    trigger fs
    trigger post-fs

    trigger late-fs

    trigger post-fs-data

    trigger zygote-start  // 2.觸發zygote-start

    trigger load_persist_props_action

    trigger firmware_mounts_complete

    trigger early-boot
    trigger boot
    ...

on zygote-start && property:ro.crypto.state=unencrypted
    exec_start update_verifier_nonencrypted
    start netd
    start zygote  //3.啓動zygote
    start zygote_secondary

...
...
複製代碼

在註釋1處使用import語句引入了init.zygote.rc文件,這是後面啓動zygote進程的關鍵一步,能夠看到init.zygote.rc文件的名字是不固定的,那是由於根據手機處理器位數的不通,將init.zygote.rc拆分紅了多個不一樣的文件,如init.zygote32.rc、init.zygote64.rc等。

而後經過on語句定義了一系列的觸發器,包括early-init、init等等。

能夠看到在late-init被觸發時會去觸發zygote-start這個觸發器(註釋2處),而在zygote-start中則會調用start zygote(註釋3處),咱們來看一下init.zygote32.rc文件的源碼:

源碼位置:\system\core\rootdir\init.zygote32.rc

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks
複製代碼

能夠看到,該文件經過service語句來建立zygote進程,該進程的代碼位於/system/bin/app_process目錄下。

這樣,當相關的觸發器被觸發後,便會啓動zygote進程。

4.3 init.rc文件的解析流程

回到init.cpp的main方法中來,前面咱們說過parser對象調用AddSectionParser方法爲該對象添加了service、on和import語句的解析器,並使用ParseConfig方法對init.rc文件進行解析,咱們來看一下這兩個方法的源碼:

文件路徑:\system\core\init\init_parser.cpp

void Parser::AddSectionParser(const std::string& name, std::unique_ptr<SectionParser> parser) {
    section_parsers_[name] = std::move(parser);
}
複製代碼

section_parsers_有點相似於java中的map對象,AddSectionParser方法就是將一個觸發器對象放到了一個map集合中。

bool Parser::ParseConfig(const std::string& path) {
    if (is_dir(path.c_str())) {  // 1.判斷是否目錄
        return ParseConfigDir(path);
    }
    return ParseConfigFile(path);
}

bool Parser::ParseConfigFile(const std::string& path) {
    ...
    std::string data;
    if (!read_file(path, &data)) {  // 2.將要解析的文件的數據讀入到data中
        return false;
    }
    ...
    ParseData(path, data);  //3.調用ParseData方法進行解析
    ...
}
複製代碼

在ParseConfig方法中,首先判斷傳入的文件是不是一個目錄,若是是目錄則調用ParseConfigDir方法,若是是文件則調用ParseConfigFile方法,因爲咱們須要解析的init.rc是一個文件,咱們直接來看ParseConfigFile方法。

在註釋2出,將要解析的文件的數據讀取到了一個string類型的變量data中,而後再註釋3處調用ParseData方法進行數據解析。

咱們來看ParseData方法的源碼:

void Parser::ParseData(const std::string& filename, const std::string& data) {
    
    std::vector<char> data_copy(data.begin(), data.end());// 1.將數據存入一個char類型的鏈表中
    data_copy.push_back('\0');//在結尾添加‘\0’做爲結束標示

    parse_state state;   // 建立一個state對象
    state.filename = filename.c_str();
    state.line = 0;
    state.ptr = &data_copy[0];  //state對象內的ptr指針指向data_copy鏈表
    state.nexttoken = 0;

    SectionParser* section_parser = nullptr;//section解析器
    std::vector<std::string> args;

    for (;;) {
        switch (next_token(&state)) {  
        case T_EOF:  //當讀取到結束標示符時
            if (section_parser) {
                section_parser->EndSection();  //1.調用EndSection
            }
            return;
        case T_NEWLINE:  //讀取到回車符時
            ...
            //section_parsers_是一個map,以前咱們建立的on service import語句解析器便放入了這個map中
            if (section_parsers_.count(args[0])) {//2.若是args[0]是on、service、import語句
                ...
                if (!section_parser->ParseSection(args, &ret_err)) {//調用ParseSection進行解析
                    ...
                }
            } else if (section_parser) {
                ...
                if (!section_parser->ParseLineSection(args, state.filename, state.line, &ret_err)) {//調用ParseLineSection
                    ...
                }
            }
            args.clear();  //清空
            break;
        case T_TEXT:  
            args.emplace_back(state.text);  //3.將讀入的數據放入args中
            break;
        }
    }
}
複製代碼

在ParseData函數中開啓了一個無限循環來進行數據的讀取,當讀取到一個單詞時,便將這個單詞放到一個字符串鏈表args中(註釋3處),這樣便將一行語句拆分紅了多個單詞。

在註釋2處,經過args[0]來從section_parsers_這個map中獲取解析器,在第4節咱們建立了on、service、import類型的解析器並放入到了這個map中,所以若是args[0]是這三種語句中的一種即可以獲取到相應的解析器,不然的話這條語句多是一條command或option。而後分別調用ParseSection或ParseLineSection方法來進行解析。

當讀取到結束符號時,則調用EndSection方法(註釋1處)。

ParseSection方法是一個抽象方法,在各個解析器中的實現不一樣,咱們主要來看一下ServiceParser中的實現:

源碼位置:\system\core\init\service.cpp

bool ServiceParser::ParseSection(const std::vector<std::string>& args, std::string* err) {
   ...
    service_ = std::make_unique<Service>(name, str_args);//1.生成service_對象
    return true;
}
複製代碼

從源碼中咱們能夠看到,ParseSection主要是根據傳入的service語句來構建了一個service_對象。

咱們再來看一下EndSection方法在ServiceParser中的實現:

void ServiceParser::EndSection() {
    if (service_) {
        ServiceManager::GetInstance().AddService(std::move(service_));
    }
}
複製代碼

該方法即便是service_對象存入到了ServiceManager中。

ActionParser中的ParseSection方法與ServiceParser相似,只是ActionParser是根據action語句建立了一個action_對象,並把這個action_放入了ActionManager中。

至此init.rc中的各種Action和Service便都加載完成了。

5.將須要被觸發的觸發器加入隊列

咱們回到init.cpp的main方法中繼續往下看:

ActionManager& am = ActionManager::GetInstance();

    am.QueueEventTrigger("early-init");  //early-init觸發器

    am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
    am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
    am.QueueBuiltinAction(keychord_init_action, "keychord_init");
    am.QueueBuiltinAction(console_init_action, "console_init");
    am.QueueEventTrigger("init");  //init觸發器

    am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    std::string bootmode = GetProperty("ro.bootmode", "");
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");  //late-init觸發器
    }
    am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");

複製代碼

在解析完init.rc文件以後,咱們還要進行一些準備工做才能對各種事件進行觸發。在上面的代碼中,咱們先獲取了ActionManager的實例,而後經過QueueEventTrigger方法將須要被觸發的觸發器加入到了觸發隊列中,並使用QueueBuiltinAction方法用於動態建立了一些Action。能夠看到early-init、init、late-init觸發器都前後被加入到了隊列中。

QueueEventTrigger的源碼以下: 源碼路徑:\system\core\init\action.cpp

//std::queue<std::unique_ptr<Trigger>> trigger_queue_;定義於.h中
void ActionManager::QueueEventTrigger(const std::string& trigger) {
    trigger_queue_.push(std::make_unique<EventTrigger>(trigger));//將觸發器加入到隊列中
}
複製代碼

6.觸發事件,並不斷監聽新事件

while (true) {
        ...
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            am.ExecuteOneCommand();  //執行一條command
        }
        if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
            restart_processes();  //重啓須要重啓的進程
            ...
        }
        ...
    }
複製代碼

經過while(true)開啓了一個事件循環模型,在這個無限循環中,經過ActionManager的ExecuteOneCommand來逐條執行Action的command。

至此init進程的啓動便完成了。

7.總結

總結一下init進程啓動所作的事情:

  1. 掛載和建立系統目錄
  2. 初始化系統log、開啓屬性服務、加載Selinux模塊等工做
  3. 解析init.rc文件,加載各類Action和Service
  4. 觸發Action,並不斷監聽新的Action

參考資料:
www.jianshu.com/p/befff3d70…
www.jianshu.com/p/464c3d120… 《Android》進階解密——劉望舒 著

相關文章
相關標籤/搜索