Android系統(tǒng)啟動——2init進程

本次系列的內(nèi)容如下:

Android啟動流程——1 序言拂玻、bootloader引導與Linux啟動
Android系統(tǒng)啟動——2 init進程
Android系統(tǒng)啟動——3 init.rc解析
Android系統(tǒng)啟動——4 zyogte進程
Android系統(tǒng)啟動——5 zyogte進程(Java篇)
Android系統(tǒng)啟動——6 SystemServer啟動
Android系統(tǒng)啟動——7 附錄1:Android屬性系統(tǒng)
Android系統(tǒng)啟動——8 附錄2:相關(guān)守護進程簡介

本篇文章的主要內(nèi)容如下:

  • 1谋竖、init進程簡介
  • 2瑟幕、Init.cpp的main()方法解析

一、init進程簡介

通過上篇文章我們知道充尉,Android設備啟動要經(jīng)過3個階段飘言,BootLoaderLinux Kernel和Android系統(tǒng)服務驼侠,一般情況下姿鸿,他們都會相應的啟動對動畫對應。前面我們已經(jīng)知道Andorid系統(tǒng)是如何啟動的BootLoaderLinux Kernel的倒源。

嚴格上講苛预,Android系統(tǒng)實際上是運行于Linux內(nèi)核之上的一系列"服務進程",并不算一個完成意義上的"操作系統(tǒng)"笋熬;而這一系列進程是維持Android設備正常工作的關(guān)鍵热某,所以它們肯定有一個"根進程",這個"根進程"衍生出了這一系列進程。這個"根進程"就是init進程昔馋。

init進程是Android系統(tǒng)啟動的第一個進程筹吐。它通過解析init.rc腳本來構(gòu)建出系統(tǒng)的初始形態(tài)。其他的"一系列"Android系統(tǒng)進程大部分也是通過"init.rc"來啟動的秘遏。因為要兼容不同的開發(fā)商丘薛,所以init.rc腳本的語法很簡單,并且采用的是純文本編輯的邦危,這樣導致它可讀性就會很高洋侨。

二、Init.cpp

init是Linux系統(tǒng)中用戶空間的第一個進程(pid=1)倦蚪,Linux Kernel啟動后希坚,會調(diào)用/system/core/init/Init.cpp的main()方法

那我們就來看下init.cpp的main()里面的具體實現(xiàn)

代碼在init.cpp989行

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

// ****************** 第一部分 ****************** 
// 檢查啟動程序的文件名

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

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

// ****************** 第二部分 ****************** 
// 設置文件屬性為0777
    // Clear the umask.
    umask(0);

// ****************** 第三部分 ****************** 
// 設置環(huán)境變量
    add_environment("PATH", _PATH_DEFPATH);

// ****************** 第四部分 ****************** 
// 創(chuàng)建一些基本目錄,并掛載

    //判斷是否是第一次
    bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 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
    //如果是第一次.
    if (is_first_stage) {
        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);
    }


// ****************** 第五部分 ****************** 
// 把標準輸入审丘、標準輸出和標準錯誤重定向到空設備文件"/dev/_null_"

    // 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();


// ****************** 第六部分 ****************** 
// 啟動kernel log
    klog_init();
    klog_set_level(KLOG_NOTICE_LEVEL);

    // 輸出init啟動階段的log      
    NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");


// ****************** 第七部分 ****************** 
// 設置系統(tǒng)屬性
    if (!is_first_stage) {
        // Indicate that booting is in progress to background fw loaders, etc.
// 7.1 創(chuàng)建初始化標志
        close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

//7.2 初始化Android的屬性系統(tǒng)
        property_init();

        // If arguments are passed both on the command line and in DT,
        // properties set in DT always have priority over the command-line ones.
//7.3  解析DT和命令行中的kernel啟動參數(shù)
        process_kernel_dt();
        process_kernel_cmdline();

        // Propogate the kernel variables to internal variables
        // used by init as well as the current required properties.
//7.4  設置系統(tǒng)屬性
        export_kernel_boot_props();
    }

// ****************** 第八部分 ****************** 

    // Set up SELinux, including loading the SELinux policy if we're in the kernel domain.
    // 調(diào)用selinux_initialize函數(shù)啟動SELinux
    selinux_initialize(is_first_stage);

    // If we're in the kernel domain, re-exec init to transition to the init domain now
    // that the SELinux policy has been loaded.
    if (is_first_stage) {

        // 按照selinux policy要求吏够,重新設置init文件屬性
        if (restorecon("/init") == -1) {
            ERROR("restorecon failed: %s\n", strerror(errno));
            security_failure();
        }
        char* path = argv[0];

        // 設置參數(shù)  --second-stage
        char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };

        // 執(zhí)行init進程勾给,重新進入main函數(shù)
        if (execv(path, args) == -1) {
            ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
            security_failure();
        }
    }

    // These directories were necessarily created before initial policy load
    // and therefore need their security context restored to the proper value.
    // This must happen before /dev is populated by ueventd.
    INFO("Running restorecon...\n");
    restorecon("/dev");
    restorecon("/dev/socket");
    restorecon("/dev/__properties__");
    restorecon_recursive("/sys");

// ****************** 第九部分 ****************** 
    epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    if (epoll_fd == -1) {
        ERROR("epoll_create1 failed: %s\n", strerror(errno));
        exit(1);
    }
    signal_handler_init();

// ****************** 第十部分 ****************** 

    property_load_boot_defaults();
    start_property_service();

// ****************** 第十一部分 ****************** 
// 重點部分滩报,我們后面用專門用一篇文章講解
    init_parse_config_file("/init.rc");

// ****************** 第十二部分 ****************** 

    action_for_each_trigger("early-init", action_add_queue_tail);

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
    queue_builtin_action(keychord_init_action, "keychord_init");
    queue_builtin_action(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    action_for_each_trigger("init", action_add_queue_tail);

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    char bootmode[PROP_VALUE_MAX];
    if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
        action_for_each_trigger("late-init", 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");

// ****************** 第十三部分 ****************** 
    while (true) {
        if (!waiting_for_exec) {
            execute_one_command();
            restart_processes();
        }

        int timeout = -1;
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }

        if (!action_queue_empty() || cur_action) {
            timeout = 0;
        }

        bootchart_sample(&timeout);

        epoll_event ev;
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
            ((void (*)()) ev.data.ptr)();
        }
    }
    return 0;
}

由于main()函數(shù)比較長播急,整個init進程的啟動流程都在這個函數(shù)中握截,我們把main()切割成,然后我們就來挨個解釋下

1忘朝、第一部分

主要是檢查啟動程序的文件名偎痛,這里面分成三種情況

  • 1 如果文件名是"ueventd",則執(zhí)行守護進程ueventd的主函數(shù)ueventd_main()
  • 2 如果文件名是"watchdogd"才顿,則執(zhí)行看門狗守護進程的主函數(shù)watchdogd_main()
  • 如果文件名既不是"ueventd"也不是"watchdogd",則往下執(zhí)行

2歹叮、第二部分

 umask(0);

缺省情況下慰技,一個進程創(chuàng)建出來的文件和文件夾的屬性是022,使用umask()函數(shù)能設置文件屬性的掩碼今野。參數(shù)為0意味著進程創(chuàng)建的文件屬性是0777

3夹厌、第3部分

設置環(huán)境變量地址

4采够、第4部分

創(chuàng)建一些基本的目錄冰垄,包括/dev蹬癌、/porc、/sysfc等。同時把一些文件系統(tǒng)逝薪,如tmpfs隅要、devpt、proc董济、sysfs等mount到項目的目錄步清。我們把上面的文件目錄簡單說一下

  • tmpfs
    是一種基于內(nèi)存的文件系統(tǒng),mount后就可以使用。tmpfs文件系統(tǒng)下的文件都存放在內(nèi)存中虏肾,訪問速度快尼啡,但是關(guān)機后所有內(nèi)容偶讀會丟失,因此tmpfs文件系統(tǒng)比較合適存放一些臨時性的文件询微。tmpfs文件系統(tǒng)的大小是動態(tài)變化的崖瞭,剛開始占用空間很小,隨著文件的增多會隨之變大撑毛,很節(jié)省空間书聚。Android將tmpfs文件mount到/dev目錄,dev目錄用來存放系統(tǒng)創(chuàng)建的設備節(jié)點藻雌,正好符合tmpfsw文件系統(tǒng)的特點雌续。
    -devpts
    是虛擬終端文件系統(tǒng),它通常mount在目錄dev/pts下
  • proc
    是一種基于內(nèi)存的虛擬文件系統(tǒng)胯杭,它可以看作是內(nèi)核內(nèi)部數(shù)據(jù)結(jié)構(gòu)的接口驯杜,通過它可以獲得系統(tǒng)的信息,同時能夠在運行時修改特定的內(nèi)核參數(shù)
  • sysfs
    文件系統(tǒng)和proc文件系統(tǒng)類似做个,它是Linux2.6內(nèi)核引入的鸽心,作用是把系統(tǒng)的設備和總線按層次組織起來,使得它們可以在用戶空間存取居暖,用來向用戶空間導出內(nèi)核的數(shù)據(jù)結(jié)構(gòu)及它們的屬性顽频。

5、第5部分

調(diào)用open_devnull_stdio()函數(shù)把標準輸入太闺、標準輸出和標準錯誤重定向到空設備文件"/dev/null"糯景,這是創(chuàng)建守護進程常用的手段

6、第6部分

調(diào)用klog_init()函數(shù)創(chuàng)建節(jié)點/dev/kmsg省骂,這樣init進程可以使用kernel的log系統(tǒng)來出書log了蟀淮,同時調(diào)用klog_set_level函數(shù)來設置輸出log的級別。

PS:

  • 1 這里補充下钞澳,為什么要使用kernel的log系統(tǒng)怠惶,因為此時Android系統(tǒng)的log還沒有啟動,所以需要使用kernel的log系統(tǒng)略贮。
  • 2 log的輸出級別是KLOG_NOTICE_LEVEL(5)甚疟,當log級別小于5時仗岖,這回輸出kernel log,默認值是3览妖,關(guān)于log級別如下:
define KLOG_ERROR_LEVEL 3
define KLOG_WARNING_LEVEL 4
define KLOG_NOTICE_LEVEL 5
define KLOG_INFO_LEVEL 6
define KLOG_DEBUG_LEVEL 7
define KLOG_DEFUALT_LEVEL  3

默認為3

7轧拄、第7部分

如果不是第一次,則進行一些設置讽膏,我又將這里具體劃分為4個部分

  • 7.1 創(chuàng)建初始化標志
  • 7.2 初始化Android的屬性系統(tǒng)
  • 7.3 解析DT和命令行中的kernel啟動參數(shù)
  • 7.4 設置系統(tǒng)屬性
7.1 創(chuàng)建初始化標志
 close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));

在/dev目錄下創(chuàng)建一個空文件".booting"表示初始化正在進行
is_booting()函數(shù)會依靠空文件".booting"來判斷是否進程處于初始化中檩电,初始化結(jié)束后,這個文件會被刪除

7.2 初始化Android的屬性系統(tǒng)

主要是調(diào)用property_init()函數(shù)來初始化Android的屬性系統(tǒng)府树,property_init()函數(shù)主要作用是創(chuàng)建一個共享區(qū)域來存儲屬性值

7.3 解析DT和命令行中的kernel啟動參數(shù)

這里里面又分兩個俐末,由于DT的優(yōu)先級高于cmdline,所以先調(diào)用process_kernel_dt()函數(shù)解析啟動參數(shù)奄侠,然后調(diào)用process_kernel_cmdline解析啟動參數(shù)卓箫。

為了讓大家更好的理解這個兩個方法,下面我們來看下這兩個方法的具體實現(xiàn)

  • process_kernel_dt函數(shù)解析

代碼在init.cpp816行

static void process_kernel_dt(void)
{
    static const char android_dir[] = "/proc/device-tree/firmware/android";

    std::string file_name = android::base::StringPrintf("%s/compatible", android_dir);

    std::string dt_file;
    android::base::ReadFileToString(file_name, &dt_file);

    // 判斷compatible文件內(nèi)容是否是android,firmware
    if (!dt_file.compare("android,firmware")) {
        ERROR("firmware/android is not compatible with 'android,firmware'\n");
        return;
    }

    std::unique_ptr<DIR, int(*)(DIR*)>dir(opendir(android_dir), closedir);
    if (!dir)
        return;

    struct dirent *dp;

   // 讀取目錄的每個文件
    while ((dp = readdir(dir.get())) != NULL) {
        if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible"))
            continue;

        file_name = android::base::StringPrintf("%s/%s", android_dir, dp->d_name);

        android::base::ReadFileToString(file_name, &dt_file);
        std::replace(dt_file.begin(), dt_file.end(), ',', '.');

        // 每個文件名作為屬性名垄潮,里面的內(nèi)容作為屬性值
        std::string property_name = android::base::StringPrintf("ro.boot.%s", dp->d_name);
        property_set(property_name.c_str(), dt_file.c_str());
    }
}

上面這個函數(shù)主要是在/proc/device-tree/firmware/android 這個目錄下烹卒,先看compatiable文件內(nèi)容是否是android,firmware。然后這個目錄下每個文件名作為屬性弯洗,文件里面的內(nèi)容作為屬性值旅急。這里的話就是ro.boot.hareware ro.boot.name這兩個屬性值。

我們知道內(nèi)核通常由bootloader(啟動引導程序)加載啟動牡整,前面說過了藐吮,目前使用最廣泛的是bootloader大都基于u-boot定制。內(nèi)核允許bootloader啟動自己時傳遞參數(shù)逃贝。在內(nèi)核內(nèi)核啟動完畢后谣辞,啟動參數(shù)可以通過/proc/cmdline查看。

例如android 4.4模擬器啟動后秋泳,查看其內(nèi)核啟動參數(shù)潦闲,如下:
root@generic:/ # cat /proc/cmdline
qemu.gles=0 qemu=1 console=ttyS0 android.qemud=ttyS1 android.checkjni=1 ndns=1

  • process_kernel_cmdline函數(shù)解析

代碼在init.cpp848行

static void process_kernel_cmdline(void)
{
    /* don't expose the raw commandline to nonpriv processes */
    // 第一步
    chmod("/proc/cmdline", 0440);

    /* first pass does the common stuff, and finds if we are in qemu.
     * second pass is only necessary for qemu to export all kernel params
     * as props.
     */
   // 第二步
    import_kernel_cmdline(false, import_kernel_nv);
    if (qemu[0])
        import_kernel_cmdline(true, import_kernel_nv);
}

這個函數(shù)內(nèi)部有點小復雜攒菠,我將其分為2個步驟迫皱,解析如下:

  • 第一步:修改/proc/cmdline文件權(quán)限,0440即表明只有root用戶或root組用戶可以續(xù)寫該文件辖众,其他用戶無法訪問卓起。
  • 第二步:調(diào)用import_kernel_cmdline函數(shù),就是讀取proc/cmdline中的內(nèi)容凹炸。這個函數(shù)有兩個有兩個參數(shù)戏阅,第一個采納數(shù)標識當前Android十倍是否是模擬器。第二個參數(shù)是一個函數(shù)指針啤它;主要指的是通過調(diào)用import_kernel_nv函數(shù)來設置系統(tǒng)屬性奕筐。

import_kernel_cmdline函數(shù)將/proc/cmdline內(nèi)容讀入到內(nèi)存緩沖區(qū)中舱痘,并將cmdline內(nèi)容以空格拆分為小段字符串,依次傳遞給import_kernel_nv函數(shù)處理离赫。以上面的/proc/cmdline的輸出為例子芭逝,該字符串可以拆分為以下幾段:

qemu.gles=0  
qemu=1  
console=ttyS0  
android.qemud=ttyS1  
android.checkjni=1  
ndns=1  

因此,在import_kernel_nv將會連續(xù)調(diào)用6次渊胸,依次傳入上述字符串旬盯。函數(shù)實現(xiàn)如下:

代碼在init.cpp763行

static void import_kernel_nv(char *name, bool for_emulator)
{
    char *value = strchr(name, '=');
    int name_len = strlen(name);

    if (value == 0) return;
    *value++ = 0;
    if (name_len == 0) return;

    if (for_emulator) {
        /* in the emulator, export any kernel option with the
         * ro.kernel. prefix */
        char buff[PROP_NAME_MAX];
        int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );

        if (len < (int)sizeof(buff))
            property_set( buff, value );
        return;
    }

    if (!strcmp(name,"qemu")) {
        strlcpy(qemu, value, sizeof(qemu));
    } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
        const char *boot_prop_name = name + 12;
        char prop[PROP_NAME_MAX];
        int cnt;

        cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
        if (cnt < PROP_NAME_MAX)
            property_set(prop, value);
    }
}

import_kernel_cmdline第一次執(zhí)行時,傳入import_kernel_nv的形參為for_emulator為0翎猛,因此將匹配name是否為qemu胖翰,如果是,將其值保存在qemu全局靜態(tài)緩沖區(qū)中切厘。對于android模擬器萨咳,存在/proc/cmdline中存在"qemu=1"字段。如果for_emulator為1疫稿,則將生成ro.kernel.{name}={value}屬性寫入Android屬性系統(tǒng)中某弦。

此時回到process_kernel_cmdline函數(shù),繼續(xù)執(zhí)行

if (qemu[0])  
     import_kernel_cmdline(1, import_kernel_nv);  

當系統(tǒng)為模擬器時而克,qemu[0]其值為"1"靶壮,第二次執(zhí)行import_kernel_cmdline函數(shù),將再次調(diào)用6次import_kernel_nv函數(shù)员萍,并且for_emulator為1腾降,因此將生成6個屬性。我們來確定下我們的分析

root@generic:/ # getprop | grep ro.kernel.                                       
[ro.kernel.android.checkjni]: [1]  
[ro.kernel.android.qemud]: [ttyS1]  
[ro.kernel.console]: [ttyS0]  
[ro.kernel.ndns]: [1]  
[ro.kernel.qemu.gles]: [0]  
[ro.kernel.qemu]: [1] 

可驗證我們的分析是正確的碎绎。

7.4 設置系統(tǒng)屬性

代碼在init.cpp796行

static void export_kernel_boot_props() {
    struct {
        const char *src_prop;
        const char *dst_prop;
        const char *default_value;
    } 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", },
        { "ro.boot.hardware",   "ro.hardware",   "unknown", },
        { "ro.boot.revision",   "ro.revision",   "0", },
    };

 //  通過內(nèi)核的屬性設置應用層配置文件的屬性
    for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) {
        char value[PROP_VALUE_MAX];
        int rc = property_get(prop_map[i].src_prop, value);
        property_set(prop_map[i].dst_prop, (rc > 0) ? value : prop_map[i].default_value);
    }
}

所以export_kernel_boot_props這個函數(shù)螃壤,它就是設置一些屬性,設置ro屬性根據(jù)之前的ro.boot這類的屬性值筋帖,如果沒有設置成unknown奸晴,像之前我們有ro.boot.hardware,那我們就可以設置root.hardware這樣的屬性日麸。這樣描述很有朋友看不懂寄啼,我把上面的語言轉(zhuǎn)化為最直觀的語言如下:

export_kernel_boot_props用戶設置幾個系統(tǒng)屬性,如下:

  • 讀取ro.boot.serialno代箭,若存在其值寫入ro.serialno墩划,否則ro.serialno寫入空
  • 讀取ro.boot.mode,若存在其值寫入ro.bootmode嗡综,否則ro.bootmode寫入"unkown"
  • 讀取ro.boot.baseband乙帮,若存在其值寫入ro.baseband,否則ro.baseband寫入"unkown"
  • 讀取ro.boot.bootloader极景,若存在其值寫入ro.bootloader察净,否則ro.bootloader寫入"unkown"
  • 讀取ro.boot.revision驾茴,若存在,若存在其值寫入revision中氢卡,否則ro.revision寫入"unkown"沟涨。
  • 讀取ro.boot.hardware,若存在其值寫入ro.hardware异吻,否則ro.hardware寫入"unkown"裹赴。

8、第8部分

這部分主要是selinux相關(guān)的诀浪,這部分代碼是Android4.3之后添加的安全內(nèi)核棋返,隨后伴隨著Android系統(tǒng)更新不斷迭代,這段代碼主要是設計SELinux初始化雷猪。后面有時間咱們開個專題講解SELinux睛竣。

SELinux帶給Linux的主要價值是:提供了一個靈活的,可配置的MAC機制求摇。同時SELinux是一個安全體系結(jié)構(gòu)射沟,它通過LSM(Linux Security Modules)框架被集成到Linux Kernel 2.6.x。它是NSA(United States National Security Agency)和SELinux社區(qū)的聯(lián)合項目。

這里重點分析下selinux_initialize函數(shù)
代碼在init.cpp955行

static void selinux_initialize(bool in_kernel_domain) {

    // 使用Timer計時,計算selinux初始化耗時
    Timer t;

    selinux_callback cb;

    // 用于打印Log的回調(diào)函數(shù)
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);

    // 用于檢查權(quán)限的回調(diào)函數(shù)
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    // 如果不支持锅论,則直接返回
    if (selinux_is_disabled()) {
        return;
    }

    // 內(nèi)核態(tài)處理流程齿尽,第一階段in_kernel_domain為true
    if (in_kernel_domain) {

        // 這個log打印不出读第,因為是INFO級別
        INFO("Loading SELinux policy...\n");

        // 用于加載sepolicy文件,該函數(shù)最終將sepolicy文件傳遞給kernel,這樣kernel就有了安全策略的配置文件
        if (selinux_android_load_policy() < 0) {
            ERROR("failed to load policy: %s\n", strerror(errno));
            security_failure();
        }

        // 命令行中得到的信息
        bool is_enforcing = selinux_is_enforcing();

       // 這里補充下 用于設置selinux工作模式。selinux有兩種工作模式:
       // 1绑谣、"permissive",所有的操作都被允許(即沒有MAC)拗引,但是如果違反權(quán)限的話借宵,會記錄日志。
       // 2矾削、"enforcing"壤玫,所有操作都會進行權(quán)限檢查。在一般的中斷中怔软,應有工作于enforcing模式
        security_setenforce(is_enforcing);

        if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
            security_failure();
        }

        //輸出selinux的模式垦细,與初始化耗時
        NOTICE("(Initializing SELinux %s took %.2fs.)\n",
               is_enforcing ? "enforcing" : "non-enforcing", t.duration());
    } else {
        // 如果啟動第二階段,調(diào)用該函數(shù)
        selinux_init_all_handles();
    }
}

9挡逼、第9部分

這部分分為上下按兩個階段
第一階段主要是調(diào)用epoll_create1創(chuàng)建epoll句柄,如果創(chuàng)建失敗腻豌,則退出家坎。
第二階段是調(diào)用signal_handler_init()函數(shù)嘱能,主要是裝載進程信號處理器。

signal_handler_init()函數(shù)主要是當子進程被kill之后虱疏,會在父進程接受一個信號惹骂。處理這個信號的時候往sockpair一段寫數(shù)據(jù),而另一端的fd是加入epoll中

init是一個守護進程做瞪,為了防止init的子進程稱為僵尸進程(zombie process)对粪,需要init在子進程結(jié)束時獲取子進程的結(jié)束碼,通過結(jié)束碼將程序表中的子進程移除装蓬,防止稱為僵尸進程的子進程占用程序表的空間(程序表的空間達到上線時著拭,系統(tǒng)就不能再啟動新的進城了,會引起嚴重的系統(tǒng)問題)牍帚。

現(xiàn)在我們來看下signal_handler_init()函數(shù)的內(nèi)容
signal_handler.cpp955行

void signal_handler_init() {

    // 在Linux中儡遮,父進程是通過捕捉SIGCHILD信號來得知子進程運行結(jié)束的情況
    // Create a signalling mechanism for SIGCHLD.
    int s[2];

    // 利用socketpair創(chuàng)建出已經(jīng)連接的兩個socket,分別作為信號的讀暗赶、寫端
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
        ERROR("socketpair failed: %s\n", strerror(errno));
        exit(1);
    }

    signal_write_fd = s[0];
    signal_read_fd = s[1];

    // Write to signal_write_fd if we catch SIGCHLD.
    struct sigaction act;
    memset(&act, 0, sizeof(act));

    // 信號處理器為SIGCHLD_handler鄙币,其被存在sigaction結(jié)構(gòu)體重,負責處理SIGCHLD消息
    
    // 信號處理器
    act.sa_handler = SIGCHLD_handler;

     // 僅當進程終止時才接受
    act.sa_flags = SA_NOCLDSTOP;

     // 調(diào)用信號安裝函數(shù)sigaction蹂随,將監(jiān)聽的信號及對應的信號處理器注冊到內(nèi)核中
    sigaction(SIGCHLD, &act, 0);

    reap_any_outstanding_children();

    // 定義在system/core/init/init.cpp中十嘿,注冊epoll handler,當signal_read_fd 有數(shù)據(jù)可讀時岳锁,調(diào)用handle_signal
    register_epoll_handler(signal_read_fd, handle_signal);
}

9.1信號

這里我們簡單介紹下信號:
Linux進程通過相互發(fā)送接收消息來實現(xiàn)進程間通信详幽,這些消息被稱為"信號"。每個進程在處理它進程發(fā)送的信號時浸锨,都要注冊處理者唇聘,處理者被稱為信號處理器。

每個進程在處理其他進程發(fā)送的signal信號時都需要先注冊柱搜,當進程的運行狀態(tài)改變或終止時會產(chǎn)生某種signal信號迟郎,init進程是所有用戶空間進程的父進程,當其子進程終止時產(chǎn)生SIGCHLD信號聪蘸,init進程調(diào)用信號安裝函數(shù)sigaction()宪肖,傳遞參數(shù)給sigaction結(jié)構(gòu)體,便完成信號處理的過程健爬。

這里有兩個重要的函數(shù):SIGCHLD_handler和handle_signal控乾,他倆是對應的

代碼在signal_handler.cpp 158行

static void SIGCHLD_handler(int) {
    //向signal_write_fd寫入1,知道成功為止
    if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
        ERROR("write(signal_write_fd) failed: %s\n", strerror(errno));
    }
}

SIGCHLD_handler()函數(shù)負責寫入

代碼在signal_handler.cpp 150行

static void handle_signal() {
    // Clear outstanding requests.
    char buf[32];
    // 讀取signal_read_fd數(shù)據(jù)娜遵,放入buf
    read(signal_read_fd, buf, sizeof(buf));

    reap_any_outstanding_children();
}

handle_signal()函數(shù)負責讀取數(shù)據(jù)蜕衡,上面調(diào)用了reap_any_outstanding_children()函數(shù),我們來看下

代碼在signal_handler.cpp 145行

static void reap_any_outstanding_children() {
    while (wait_for_one_process()) {
    }
}

我們看到reap_any_outstanding_children函數(shù)就是調(diào)用while循環(huán)來調(diào)用wait_for_one_process()函數(shù)设拟,下面我們來看下wait_for_one_process()函數(shù)的具體執(zhí)行

代碼在signal_handler.cpp 53行

static bool wait_for_one_process() {
    int status;

    // 等待任意子進程慨仿,如果子進程沒有退出則返回0久脯,否則則返回該子進程的pid
    pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
    if (pid == 0) {
        return false;
    } else if (pid == -1) {
        ERROR("waitpid failed: %s\n", strerror(errno));
        return false;
    }

    // 根據(jù)pid 查找相應的service
    service* svc = service_find_by_pid(pid);

    std::string name;
    if (svc) {
        name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid);
    } else {
        name = android::base::StringPrintf("Untracked pid %d", pid);
    }

    NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str());

    if (!svc) {
        return true;
    }

    // TODO: all the code from here down should be a member function on service.

    // 當flag為RESTART,且不是ONESHOT時镰吆,先kill進程組內(nèi)所有子進程或子線程
    if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
        NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid);
        kill(-pid, SIGKILL);
    }

    // Remove any sockets we may have created.
    // 移除當前服務svc中所有創(chuàng)建過的socket
    for (socketinfo* si = svc->sockets; si; si = si->next) {
        char tmp[128];
        snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
        unlink(tmp);
    }

    // 當flags為EXEC時帘撰,釋放相應的服務
    if (svc->flags & SVC_EXEC) {
        INFO("SVC_EXEC pid %d finished...\n", svc->pid);
        waiting_for_exec = false;
        list_remove(&svc->slist);
        free(svc->name);
        free(svc);
        return true;
    }

    svc->pid = 0;
    svc->flags &= (~SVC_RUNNING);

    // Oneshot processes go into the disabled state on exit,
    // except when manually restarted.
    // 對于ONESHOT服務,使其進入disabled狀態(tài)
    if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
        svc->flags |= SVC_DISABLED;
    }

    // Disabled and reset processes do not get restarted automatically.
    // 禁用和重置的服務万皿,都不再自動重啟
    if (svc->flags & (SVC_DISABLED | SVC_RESET))  {
        // 設置相應的service狀態(tài)為stopped
        svc->NotifyStateChange("stopped");
        return true;
    }


    // 服務在4分鐘內(nèi)重啟次數(shù)超過4次摧找,則重啟手機進入recovery模式
    time_t now = gettime();
    if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
        if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
            if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
                ERROR("critical process '%s' exited %d times in %d minutes; "
                      "rebooting into recovery mode\n", svc->name,
                      CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60);
                android_reboot(ANDROID_RB_RESTART2, 0, "recovery");
                return true;
            }
        } else {
            svc->time_crashed = now;
            svc->nr_crashed = 1;
        }
    }

    svc->flags &= (~SVC_RESTART);
    svc->flags |= SVC_RESTARTING;

    // Execute all onrestart commands for this service.
    // 執(zhí)行當前service中所有onrestart命令
    struct listnode* node;
    list_for_each(node, &svc->onrestart.commands) {
        command* cmd = node_to_item(node, struct command, clist);
        cmd->func(cmd->nargs, cmd->args);
    }

    // 設置相應的service狀態(tài)未restarting
    svc->NotifyStateChange("restarting");
    return true;
}

總結(jié)一下:
當init進程調(diào)用signal_handler_init后,一旦受到子進程終止帶來的SIGCHLD消息后牢硅,將利用信號處理者SIGCHLD_handler向signal_write_fd寫入信息蹬耘;epoll句柄監(jiān)聽到signal_read_fd收到消息后婆赠,將調(diào)用handle_signal進行處理休里。如下圖


image.png

10妙黍、第10部分

這部分主要分為兩部分拭嫁,上半部分是調(diào)用property_load_boot_defaults()函數(shù)解析根目錄的default.prop的屬性抓于,設置默認屬性配置的相關(guān)工作捉撮。下半部分是調(diào)用start_prperty_service()函數(shù)巾遭,啟動屬性服務灼舍,并接受屬性的socket的fd加入到epoll中,也定義了處理函數(shù)炫乓。那我們依次來看下

10.1厢岂、property_load_boot_defaults()函數(shù)解析

代碼在property_service.cpp 494行

void property_load_boot_defaults() {
    load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL);
}

我們看到他調(diào)用load_properties_from_file函數(shù)塔粒,那我們來看下load_properties_from_file函數(shù)筐摘。

代碼在property_service.cpp 426行

/*
 * Filter is used to decide which properties to load: NULL loads all keys,
 * "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match.
 */
static void load_properties_from_file(const char* filename, const char* filter) {
    Timer t;
    std::string data;
    if (read_file(filename, &data)) {
        data.push_back('\n');
        load_properties(&data[0], filter);
    }
    NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration());
}

PS:所謂充電模式是指充電器開機時設備進入的狀態(tài)咖熟。這是kernel和init進程會啟動馍管,但是大部分服務都不會啟動

10.2确沸、start_property_service()函數(shù)解析

代碼在property_service.cpp 570行

void start_property_service() {
    
    property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                    0666, 0, 0, NULL);
    if (property_set_fd == -1) {
        ERROR("start_property_service socket creation failed: %s\n", strerror(errno));
        exit(1);
    }

    listen(property_set_fd, 8);
    
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

start_property_service()函數(shù)創(chuàng)建了socket罗捎,然后監(jiān)聽桨菜,并且調(diào)用register_epoll_handler()函數(shù)把socket的fd放入epoll中
函數(shù)創(chuàng)建了socket,然后監(jiān)聽泻红,并且調(diào)用register_epoll_handler函數(shù)把socket的fd放入了epoll中

11谊路、第11部分

這部分主要是解析init.rc文件根悼,我們將會用單獨一篇文章來講解挤巡。這里先略過

12矿卑、第12部分

本部分是將把Action加入執(zhí)行隊列中

代碼在init.cpp 570行

    // 執(zhí)行init.rc中觸發(fā)器為 on early-init的語句,即將early-init的Action添加到鏈表action_queue中
    action_for_each_trigger("early-init", action_add_queue_tail);

    // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...

    // 等冷插拔設備初始化完成糊肤,即創(chuàng)建wait_for_coldboot_done Action并添加到action_queue和action_list中
    queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
    // ... so that we can start queuing up actions that require stuff from /dev.
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // 設備組合鍵的初始化操作馆揉,創(chuàng)建keychord_init Action 并添加到鏈表action_queue和action_list中 
    queue_builtin_action(keychord_init_action, "keychord_init");
    // 創(chuàng)建console_init動作并添加到鏈表action_queue和action_list中
    queue_builtin_action(console_init_action, "console_init");

    // Trigger all the boot actions to get us started.
    // 執(zhí)行init.rc文件中觸發(fā)器為 on init 的語句升酣,將init動作添加到鏈表action_queue中
    action_for_each_trigger("init", action_add_queue_tail);

    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
    // wasn't ready immediately after wait_for_coldboot_done
    queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");

    // Don't mount filesystems or start core system services in charger mode.
    char bootmode[PROP_VALUE_MAX];

    // 當處于充電模式态罪,則charger加入執(zhí)行隊列复颈;否則late-init加入隊列。
    if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) {
        action_for_each_trigger("charger", action_add_queue_tail);
    } else {
        action_for_each_trigger("late-init", action_add_queue_tail);
    }

    // Run all property triggers based on current state of the properties.
    // 觸發(fā)器為屬性是否設置
    queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");

上面大量的調(diào)用了action_for_each_trigger函數(shù)凿菩、action_add_queue_tai函數(shù)和queue_builtin_action蓄髓,那我們就依次研究下這兩個函數(shù)

12.1 action_for_each_trigger()函數(shù)解析

代碼在init_parser.cpp 546行

void action_for_each_trigger(const char *trigger,
                             void (*func)(struct action *act))
{
    struct listnode *node, *node2;
    struct action *act;
    struct trigger *cur_trigger;

    list_for_each(node, &action_list) {
        // 遍歷每個action
        act = node_to_item(node, struct action, alist);
        list_for_each(node2, &act->triggers) {
            // 遍歷每個action的triggers
            cur_trigger = node_to_item(node2, struct trigger, nlist);
            // 判斷是否與傳入的trigger名字匹配
            if (!strcmp(cur_trigger->name, trigger)) {
                // 調(diào)用回調(diào)函數(shù)
                func(act);
            }
        }
    }
}
12.2 action_add_queue_tail()函數(shù)解析

代碼在init_parser.cpp 643行

void action_add_queue_tail(struct action *act)
{
    if (list_empty(&act->qlist)) {
        list_add_tail(&action_queue, &act->qlist);
    }
}

這個函數(shù)就是把action加入到執(zhí)行列表中

12.3 queue_builtin_action()函數(shù)解析

代碼在init_parser.cpp 623行

void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
{
    action* act = (action*) calloc(1, sizeof(*act));
    trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));
    cur_trigger->name = name;
    list_init(&act->triggers);
    list_add_tail(&act->triggers, &cur_trigger->nlist);
    list_init(&act->commands);
    list_init(&act->qlist);

    command* cmd = (command*) calloc(1, sizeof(*cmd));
    cmd->func = func;
    cmd->args[0] = const_cast<char*>(name);
    cmd->nargs = 1;
    list_add_tail(&act->commands, &cmd->clist);

    list_add_tail(&action_list, &act->alist);
    action_add_queue_tail(act);
}

這個函數(shù)的話会喝,就是直接創(chuàng)建一個action,然后新建command译红,關(guān)鍵是func會調(diào)用函數(shù)設置好,最后把action加入執(zhí)行隊列中。

queue_builtin_action函數(shù)用來動態(tài)生成一個Action并插入到執(zhí)行列表"action_queue中"诗宣。插入的Action由一個函數(shù)指針和一個表示名字的字符串組成想诅。Android在以前版本中直接調(diào)用這些函數(shù)來完成某些初始化的工作,但是忘古,這些函數(shù)可能會依賴init.rc里面定義的一些命令和服務的執(zhí)行情況∷杩埃現(xiàn)在把這些初始化函數(shù)也通過Action的形式插入到執(zhí)行列表中旦袋,這樣就能控制他們的執(zhí)行順序了。

插入的函數(shù)有:

  • wait_for_coldboot_done_action()函數(shù),等待冷插拔設備初始化完成
  • mix_hwrng_into_linux_rng_action()函數(shù)鲜戒,從硬件PNG的設備文件/dev/hw_random中讀取512字節(jié)并寫到Linux RNG的設備文件/dev/urandom中
  • keychord_init_action()函數(shù):初始化組合鍵監(jiān)聽模塊
  • console_init_action()函數(shù):在屏幕個上顯示Android字樣的Logo
  • property_service_init_action()函數(shù):初始化屬性服務,讀取系統(tǒng)預制的屬性值
  • singal_init_action()函數(shù):初始化信號處理模塊失都。
  • check_startup_action()函數(shù):檢查是否已經(jīng)完成init進程初始化粹庞,如果完成則刪除.booting文件。
  • queue_property_triggers_action()函數(shù):檢查Action列表中通過修改屬性來觸發(fā)的Action延刘,查看相關(guān)屬性值是否已經(jīng)設置觉阅,如果已經(jīng)設置叮趴,則將Action加入到執(zhí)行列表中。

執(zhí)行流程如下圖:

13宫静、第13部分

代碼在init.cp 1109行

    while (true) {
        // 判斷是否還有事件需要處理
        if (!waiting_for_exec) {
             //依次執(zhí)行每個action中攜帶的command對應的執(zhí)行函數(shù)
            execute_one_command();
             // 重啟一些掛掉的進程
            restart_processes();
        }

        // 決定timeout的時間,將影響while循環(huán)的間隔
        int timeout = -1;

       // 有進程需要重啟是,等待進程重啟
        if (process_needs_restart) {
            timeout = (process_needs_restart - gettime()) * 1000;
            if (timeout < 0)
                timeout = 0;
        }

        if (!action_queue_empty() || cur_action) {
            timeout = 0;
        }

        // 進行性能數(shù)據(jù)采樣
        bootchart_sample(&timeout);

        epoll_event ev;
        // 沒有事件來的話,最多阻塞timeout時間
        int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout));
        if (nr == -1) {
            ERROR("epoll_wait failed: %s\n", strerror(errno));
        } else if (nr == 1) {
             // 根據(jù)上下文知道套才,epoll句柄(即epoll_fd)主要監(jiān)聽子進程結(jié)束,及其他進程設置系統(tǒng)屬性的請求 ,根據(jù)事件的到來携兵,執(zhí)行對應處理函數(shù)
            ((void (*)()) ev.data.ptr)();
        }
    }

最后init進程會進入到一個無線循環(huán)中去炭懊,在這個無線循環(huán)中,init進程會做以下五件事:

  • 第一件事:調(diào)用函數(shù)execute_one_command來檢查action_queue列表是否為空。如果不為空的話加矛,那么init進程就將保存在列表頭部中的action移除,并且執(zhí)行這個被移除的action塌鸯。由于前面我們將一個名稱為"console_init"的action添加到action_queue列表中星持,因此饥努,在這個無線循環(huán)中,這個action就會被執(zhí)行,即console_init_action函數(shù)會被調(diào)用拱烁。

  • 第二件事:調(diào)用函數(shù)restart_processes來檢查系統(tǒng)中是否有進程需要重啟生蚁。在啟動腳本init.rc中,我們可以指定一個進程在退出之后會自動重啟戏自。在這種情況下邦投,函數(shù)restart_processes就會檢查是否存在需要重新啟動的進程,如果存在的話擅笔,那么就將它重新啟動起來志衣。

  • 第三件事:處理系統(tǒng)屬性變化事件。當我們調(diào)用函數(shù)property_set來改變一個系統(tǒng)屬性時猛们,系統(tǒng)就會通過一個socket(通過調(diào)用函數(shù)get_property_set_fd可以獲得它的文件描述符)來向init進程發(fā)送一個屬性值改變事件通知念脯。init進程接受到這個屬性值改變事件之后,就會調(diào)用函數(shù)handle_property_set_fd來進行相應的處理弯淘。后面在分析第三個開機畫面顯示過程時绿店,我們就會看到,SurfaceFlinger服務就是通過修改“ctl.start”和“ctl.stop”屬性來啟動和停止三個開機畫面的庐橙。

  • 第四件事:處理一種被稱為"chorded keyboard"的鍵盤輸入時間假勿。這種類型為"chorded keyboard"的鍵盤設備通過不同的按鍵組合來描述不同的命令或者操作,它對應的設備為/dev/keychord态鳖。我們可以通過調(diào)用函數(shù)get_keychord_fd()來獲的這個設備的文件描述符转培,以便可以監(jiān)控它的輸入事件,并且調(diào)用函數(shù)handle_keychord來對這些輸入事件進行處理浆竭。

  • 第五件事:回收僵尸進程浸须。我們知道,在Linux內(nèi)核中兆蕉,如果父進程不等待子進程結(jié)束就退出羽戒,那么當子進程結(jié)束的時候,就會變成一個僵尸進程虎韵,從而占用系統(tǒng)的資源易稠。為了回收這些僵尸進程,init進程會安裝一個SIGCHLD信號接收器包蓝。當這些父進程已經(jīng)退出了子進程退出的時候驶社,內(nèi)核就會發(fā)出一個SIGCHLD信號企量,給init進程,init進程就可以通過一個socket(通過調(diào)用函數(shù)get_signal_fd可以獲得它的文件描述符)來將接受到的SIGCHLD信號讀取回來亡电,并且調(diào)用函數(shù)handle_signal來對接收到的SIGCHLD信號來進行處理届巩,即回收哪些已經(jīng)變成僵尸進程的子進程。

PS:后面三件事都是可以通過文件描述符來描述的份乒,因此恕汇,initJ進程的入口函數(shù)main使用poll機制來同時輪訓它們,以便可以提高效率或辖。

下面 這里我們來看一下execute_one_command()函數(shù)和restart_processes()函數(shù)的具體執(zhí)行

13.1 execute_one_command函數(shù)的執(zhí)行

代碼在init.cpp 584行

void execute_one_command() {
    Timer t;

    char cmd_str[256] = "";
    char name_str[256] = "";

    //如果是第一次啟動瘾英,所以都是NULL,所以肯定可以進入這個判斷
    //如果不是第一次啟動颂暇,因為得到cur_action或者cur_command都是null缺谴,并且如果這個command是當前action的最后一個command的話,會進入下面的這個判斷
    if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) {

        // 依次從action_queue獲取action
        cur_action = action_remove_queue_head();
        cur_command = NULL;

        if (!cur_action) {
            // 如果沒有action了耳鸯,則返回
            return;
        }

        build_triggers_string(name_str, sizeof(name_str), cur_action);

        INFO("processing action %p (%s)\n", cur_action, name_str);
        // 如果是一個新的action的話湿蛔,會執(zhí)行到這一步去獲得first command
        cur_command = get_first_command(cur_action);
    } else {
         // 如果還在action的內(nèi)部鏈表中,如果仍然存在沒有獲取到的command的話县爬,則會去獲取一下一個command
        cur_command = get_next_command(cur_action, cur_command);
    }

    if (!cur_command) {
        //如果可以獲取到command為空的話阳啥,會返回,反之捌省,繼續(xù)
        return;
    }

    // 會調(diào)用這個command的func去執(zhí)行苫纤,執(zhí)行的參數(shù)個數(shù)為nargs,命令為args
    int result = cur_command->func(cur_command->nargs, cur_command->args);
    if (klog_get_level() >= KLOG_INFO_LEVEL) {
        for (int i = 0; i < cur_command->nargs; i++) {
            strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str));
            if (i < cur_command->nargs - 1) {
                strlcat(cmd_str, " ", sizeof(cmd_str));
            }
        }
        char source[256];
        if (cur_command->filename) {
            snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line);
        } else {
            *source = '\0';
        }
        INFO("Command '%s' action=%s%s returned %d took %.2fs\n",
             cmd_str, cur_action ? name_str : "", source, result, t.duration());
    }
}

其實這個函數(shù)就是執(zhí)行一個command纲缓。大體流程如下:

  • 首選從action_queue取下struct action *act賦值給cur_action
  • 其次從cur_action獲得struct command * 賦值給curcommand
  • 最后執(zhí)行cur_command->func(cur_command->nargs, cur_command->args)
13.1.1 action_remove_queue_head函數(shù)的執(zhí)行

上面調(diào)用了action_remove_queue_head()函數(shù),我們來看下
代碼在init_parser.cpp 650行

struct action *action_remove_queue_head(void)
{
    // 先做非空判斷
    if (list_empty(&action_queue)) {
        return 0;
    } else {

        // 如果還有未被執(zhí)行的隊列的話喊废,就將node指向現(xiàn)在的action_queue的頭指針
        struct listnode *node = list_head(&action_queue);

        // 取出action
        struct action *act = node_to_item(node, struct action, qlist);

         // 刪將這個節(jié)點從整個action_queue的列表中刪除
        list_remove(node);
   
        // 刪除節(jié)點后祝高,為了安全起見,將node自己指向自己污筷,以避免出現(xiàn)野指針工闺。
        list_init(node);

         // 返回 已經(jīng)找到的action
        return act;
    }
}

我們知道,其實是從action_queue中拿每一個結(jié)構(gòu)體的瓣蛀,拿到action之后陆蟆,就從action里面去取對應的command了。

13.1.2 action_remove_queue_head函數(shù)的執(zhí)行

代碼在init.cpp 543行

// 由于這個函數(shù)主要是從一個action里面找到一個command惋增,所以在傳遞的時候只傳遞action即可 
static struct command *get_first_command(struct action *act)
{
    struct listnode *node;

    // 將node 指向action的commands結(jié)構(gòu)體
    node = list_head(&act->commands);
    if (!node || list_empty(&act->commands))
        // 如果這話節(jié)點不存在叠殷,或者這個action的commands結(jié)構(gòu)體為空,則返回null
        return NULL;

    // 返回 第一個節(jié)點
    return node_to_item(node, struct command, clist);
}
13.1.3 get_next_command函數(shù)的執(zhí)行
{  
static struct command *get_next_command(struct action *act, struct command *cmd)
{
    struct listnode *node;
    node = cmd->clist.next;

    // 如果不存在诈皿,則返回null
    if (!node)
        return NULL;

    // 如果這個節(jié)點已經(jīng)是頭節(jié)點了林束,則返回null
    if (node == &act->commands)
        return NULL;

    // 返回 next節(jié)點
    return node_to_item(node, struct command, clist);
}

這個函數(shù) 主要是返回當前commands的下一個command

13.2 restart_processes()函數(shù)的執(zhí)行

當內(nèi)存不足時像棘,Android系統(tǒng)會自動殺死一些進程來釋放空間,所以當某些重要服務被殺壶冒,同時該服務進程并未設置為oneshot缕题,則必須重新啟動該服務進程。

代碼在init.cpp 473行

static void restart_processes()
{
    process_needs_restart = 0;
    service_for_each_flags(SVC_RESTARTING,
                           restart_service_if_needed);
}

我們看到這個函數(shù)什么都做胖腾,就是調(diào)用了service_for_each_flags函數(shù)烟零,那我們再來研究下這個函數(shù)

代碼在init_parser.cpp 533行

void service_for_each_flags(unsigned matchflags,
                            void (*func)(struct service *svc))
{
    struct listnode *node;
    struct service *svc;
    list_for_each(node, &service_list) {
        svc = node_to_item(node, struct service, slist);
        if (svc->flags & matchflags) {
            func(svc);
        }
    }
}

通過代碼我們知道 函數(shù)service_for_each_flags主要是循環(huán)遍歷服務鏈表,查找標志位為SVC_RESTARTING的服務咸作,當該服務進程死亡的時候锨阿,init進程監(jiān)控進程死亡事件,在處理該事件的時候會為該進程設置SVC_RESTARTING標志性宏,并調(diào)用restart_service_if_needed函數(shù)重啟服務群井。

我們來看下restart_service_if_needed函數(shù)
代碼在init.cpp 457行

static void restart_service_if_needed(struct service *svc)
{
    time_t next_start_time = svc->time_started + 5;

    if (next_start_time <= gettime()) {
        svc->flags &= (~SVC_RESTARTING);
        service_start(svc, NULL);
        return;
    }

    if ((next_start_time < process_needs_restart) ||
        (process_needs_restart == 0)) {
        process_needs_restart = next_start_time;
    }
}

只有當前時間大于服務啟動時間時,清除服務重啟標志并啟動該服務毫胜,

上一篇文章 1序言书斜、bootloader引導與Linux啟動
下一篇文章 Android系統(tǒng)啟動——3init.rc解析

官人[飛吻],你都把臣妾從頭看到尾了酵使,喜歡就點個贊唄(眉眼)<黾!?谟妗样屠!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缺脉,隨后出現(xiàn)的幾起案子痪欲,更是在濱河造成了極大的恐慌,老刑警劉巖攻礼,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件业踢,死亡現(xiàn)場離奇詭異,居然都是意外死亡礁扮,警方通過查閱死者的電腦和手機知举,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來太伊,“玉大人雇锡,你說我怎么就攤上這事×沤梗” “怎么了锰提?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我欲账,道長屡江,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任赛不,我火速辦了婚禮惩嘉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘踢故。我一直安慰自己文黎,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布殿较。 她就那樣靜靜地躺著耸峭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淋纲。 梳的紋絲不亂的頭發(fā)上劳闹,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音洽瞬,去河邊找鬼本涕。 笑死,一個胖子當著我的面吹牛伙窃,可吹牛的內(nèi)容都是我干的菩颖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼为障,長吁一口氣:“原來是場噩夢啊……” “哼晦闰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鳍怨,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤呻右,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鞋喇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窿冯,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年确徙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片执桌。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡鄙皇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仰挣,到底是詐尸還是另有隱情伴逸,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布膘壶,位于F島的核電站错蝴,受9級特大地震影響洲愤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜顷锰,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一柬赐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧官紫,春花似錦肛宋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毁涉,卻和暖如春沉帮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贫堰。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工穆壕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人严嗜。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓粱檀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親漫玄。 傳聞我的和親對象是個殘疾皇子茄蚯,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容