本節開始依次分析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
下列代碼用於生成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__設備中,以下圖所示。學習
由上圖可見,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中定義。