鳥人的Android揭祕(10)——Init進程源代碼分析(一)

      本節開始依次分析init進程源代碼中main()函數內的代碼。受限於篇幅,咱們沒法將全部源代碼一一列出講解,這裏分析主要流程和思路,但願讀者可以參考init進程的實際代碼,一塊兒研究學習。linux

      init進程分析init.rc啓動腳本文件,並根據相關文件中包含的內容,執行相應的功能。另外,init進程提供屬性服務,保存系統運行所需的環境變量。此外,其還負責監視子進程的運行,處理子進程的終止和重啓。當應用程序訪問設備驅動時,還會生成設備節點文件。接下來咱們參考main()函數逐一分析代碼。網絡

      init進程的運行分紅兩階段,第一階段只完成最主要的初始化工做,如目錄生成和掛載、日誌初始化和設置、SELinux初始化等,以後第二階段才完成餘下的初始化過程,如屬性的初始化、屬性服務啓動、init.rc文件分析和相關服務的啓動等。以下代碼所示,當第一階段完成後,init進程調用execv[1]函數並帶上「--second-stag」標誌切換到第二階段。數據結構

bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
...
if (is_first_stage) {
    ...
    char* path = argv[0];
    char* args[] = { path, const_cast<char*>("--second-stage"), nullptr };
    if (execv(path, args) == -1) {
        ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno));
        security_failure();
    }
}

      編譯Android系統源代碼時,在生成的根文件系統中,並不存在/dev、/proc、/sys這類目錄,它們是系統運行時的目錄,由init進程啓動後,在運行過程當中建立和掛載的,以下代碼所示。當系統終止時,這類目錄就會消失。socket

mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");[2]
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);[3]
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0,
 "hidepid=2,gid=" MAKE_STR(AID_READPROC));[4]
mount("sysfs", "/sys", "sysfs", 0, NULL);[5]

      init進程建立系統運行所需的目錄,造成下圖所示的層次目錄結構。在圖中,[]內表示掛載在相應目錄下的文件系統。ide

init進程建立的目錄層次結構

      下列代碼用於生成log設備並設置日誌輸出級別,以便輸出init進程的運行信息。函數

open_devnull_stdio();[6]
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);

      init進程經過執行前面的代碼生成/dev目錄,包含系統中使用的設備,然後調用open_devnull_stdio()函數,建立運行日誌輸出設備。open_devnull_stdio()函數會優先使用/sys/fs/selinux/null設備節點文件,若是該節點不可用,則在/dev目錄下生成__null__設備節點文件,最後再將標準輸入、標準輸出和標準錯誤輸出所有重定向到__null__設備中,以下圖所示。學習

 main()-改變log信息輸出設備

      由上圖可見,open_devnull_stdio()函數將標準輸入輸出全都重定向到__null__設備中,爲了查看進程輸出的日誌,init進程調用klog_init()[7]函數提供輸出日誌信息的設備,以下面的代碼所示。日誌

void klog_init(void) {
    if (klog_fd >= 0) return; /* Already initialized */
    klog_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
    if (klog_fd >= 0) {
        return;
    }
    static const char* name = "/dev/__kmsg__";
    if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) {
        klog_fd = open(name, O_WRONLY | O_CLOEXEC);
        unlink(name);
    }
}

 

      init進程經過調用klog_init()函數初始化日誌輸出設備節點。klog_init()函數優先嚐試打開「/dev/kmsg」節點文件,若是嘗試失敗,則生成「/dev/__kmsg__」設備節點文件。在底層該設備調用內核信息輸出函數printk(),init進程最終是經過該函數來輸出日誌信息。如下代碼所示是可用於輸出信息的宏定義。code

 

#define ERROR(x...)   init_klog_write(KLOG_ERROR_LEVEL, x)
#define WARNING(x...) init_klog_write(KLOG_WARNING_LEVEL, x)
#define NOTICE(x...)  init_klog_write(KLOG_NOTICE_LEVEL, x)
#define INFO(x...)    init_klog_write(KLOG_INFO_LEVEL, x)
#define DEBUG(x...)   init_klog_write(KLOG_DEBUG_LEVEL, x)
#define VERBOSE(x...) init_klog_write(KLOG_DEBUG_LEVEL, x)

      這一小節咱們先講解完init進程對目錄生成和掛載、日誌初始化和設置,接下來的一小節咱們將繼續講解SELinux的初始化過程。進程

 

 

 

[1] execv會中止執行當前的進程,而且以progname應用進程替換被中止執行的進程,進程ID保持不變。

[2] tmpfs是一種虛擬內存的文件系統,典型的tmpfs文件系統徹底駐留在RAM中,讀寫速度遠快於內存或硬盤系統。/dev目錄保存着硬件設備訪問所需的設備驅動程序。在Android中,將相關目錄用做tmpfs,能夠大幅提高設備訪問的速度。

[3] devpts是一種虛擬終端文件系統,用來支持外部網絡連接虛擬終端。

[4] proc是一種虛擬文件系統,只存在於內存中,而不佔用外存空間。藉助此文件系統,應用程序能夠與內核內部數據結構進行交互。

[5] sysfs是一種特殊的文件系統,在Linux Kernel 2.6中引入,用於將系統中的設備組織成層次結構,統一proc、devfs、devpts這些文件系統,並將內核數據結構信息輸出到用戶空間。

[6] open_devnull_stdio()函數在system/core/init/util.cpp中定義。

[7] klog_init()函數在system/core/libcutils/klog.cpp中定義。

相關文章
相關標籤/搜索