前言
init程式,它是一個由核心啟動的使用者級程式,當Linux核心啟動之後,執行的第一個程式是init,這個程式是一個守護程式,確切的說,它是Linux系統中使用者控制元件的第一個程式,所以它的程式號是1。它的生命週期貫穿整個linux 核心執行的始終, linux中所有其它的程式的共同始祖均為init程式。
開篇
核心原始碼
關鍵類 | 路徑 |
---|---|
init.rc | system/core/rootdir/init.rc |
init.cpp | system/core/init/init.cpp |
property_service.cpp | system/core/init/property_service.cpp |
init_parser.h | system/core/init/init_parser.h |
init_parser.cpp | system/core/init/init_parser.cpp |
log.cpp | system/core/init/log.cpp |
logging.cpp | system/core/base/logging.cpp |
property_service.cpp | system/core/init/property_service.cpp |
signal_handler.cpp | system/core/init/signal_handler.cpp |
service.cpp | system/core/init/service.cpp |
Action.cpp | system/core/init/Action.cpp |
builtins.cpp | system/core/init/builtins.cpp |
Android系統啟動過程
? 按下電源系統啟動
當電源按下時引導晶片程式碼開始從預定義的地方(固化在ROM)開始執行,載入載入程式Bootloader到RAM,然後執行。
? 載入程式Bootloader
載入程式是在Android作業系統開始執行前的一個小程式,它的主要作用是把系統OS拉起來並執行。
? linux核心啟動
核心啟動時,設定快取、被保護儲存器、計劃列表,載入驅動。當核心完成系統設定,它首先在系統檔案中尋找”init”檔案,然後啟動root程式或者系統的第一個程式。
? init程式啟動
✨ 這就是我們接下來要討論的內容 ✨
Read The Fucking Code
Android init程式的入口檔案在system/core/init/init.cpp中,由於init是命令列程式,所以分析init.cpp首先應從main函式開始:
第一階段(核心態)
判斷及增加環境變數
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
//根據引數,判斷是否需要啟動ueventd和watchdogd
if (!strcmp(basename(argv[0]), "ueventd")) { // 啟動ueventd
return ueventd_main(argc, argv);
}
if (!strcmp(basename(argv[0]), "watchdogd")) { // 啟動watchdogd
return watchdogd_main(argc, argv);
}
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers(); // 若緊急重啟,則安裝對應的訊息處理器
}
add_environment("PATH", _PATH_DEFPATH); // 新增環境變數
... ...
}
建立並掛載相關的檔案系統
int main(int argc, char** argv) {
/* 01. 判斷及增加環境變數 */
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) { // 判斷是否是系統啟動的第一階段(第一次進入:true)
// 用於記錄啟動時間
boot_clock::time_point start_time = boot_clock::now();
// Clear the umask.
umask(0); // 清除遮蔽字(file mode creation mask),保證新建的目錄的訪問許可權不受遮蔽字影響
// 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.
// 掛載tmpfs檔案系統
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
// 掛載devpts檔案系統
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
// 掛載proc檔案系統
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
// Don`t expose the raw commandline to unprivileged processes.
// 8.0新增, 收緊了cmdline目錄的許可權
chmod("/proc/cmdline", 0440);
// 8.0新增,增加了個使用者組
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
// 掛載sysfs檔案系統
mount("sysfs", "/sys", "sysfs", 0, NULL);
// 8.0新增
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
// 提前建立了kmsg裝置節點檔案,用於輸出log資訊
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));
... ...
}
如上所示,該部分主要用於建立和掛載啟動所需的檔案目錄。需要注意的是,在編譯Android系統原始碼時,在生成的根檔案系統中, 並不存在這些目錄,它們是系統執行時的目錄,即當系統終止時,就會消失。
四類檔案系統:
tmpfs:一種虛擬記憶體檔案系統,它會將所有的檔案儲存在虛擬記憶體中,如果你將tmpfs檔案系統解除安裝後,那麼其下的所有的內容將不復存在。tmpfs既可以使用RAM,也可以使用交換分割槽,會根據你的實際需要而改變大小。tmpfs的速度非常驚人,畢竟它是駐留在RAM中的,即使用了交換分割槽,效能仍然非常卓越。由於tmpfs是駐留在RAM的,因此它的內容是不持久的。斷電後,tmpfs的內容就消失了,這也是被稱作tmpfs的根本原因。
devpts:為偽終端提供了一個標準介面,它的標準掛接點是/dev/ pts。只要pty的主複合裝置/dev/ptmx被開啟,就會在/dev/pts下動態的建立一個新的pty裝置檔案。
proc:一個非常重要的虛擬檔案系統,它可以看作是核心內部資料結構的介面,通過它我們可以獲得系統的資訊,同時也能夠在執行時修改特定的核心引數。
sysfs:與proc檔案系統類似,也是一個不佔有任何磁碟空間的虛擬檔案系統。它通常被掛接在/sys目錄下。sysfs檔案系統是Linux2.6核心引入的,它把連線在系統上的裝置和匯流排組織成為一個分級的檔案,使得它們可以在使用者空間存取。
重定向輸入輸出/核心Log系統
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立並掛載相關的檔案系統 */
if (is_first_stage) {
... ...
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
InitKernelLogging(argv);
... ...
}
... ...
遮蔽標準的輸入輸出
跟蹤InitKernelLogging(): system/core/init/log.cpp
void InitKernelLogging(char* argv[]) {
// Make stdin/stdout/stderr all point to /dev/null.
int fd = open("/sys/fs/selinux/null", O_RDWR);
if (fd == -1) { // 若開啟失敗,則記錄log
int saved_errno = errno;
android::base::InitLogging(argv, &android::base::KernelLogger);
errno = saved_errno;
PLOG(FATAL) << "Couldn`t open /sys/fs/selinux/null";
}
dup2(fd, 0); // dup2函式的作用是用來複制一個檔案的描述符, 通常用來重定向程式的stdin、stdout和stderr
dup2(fd, 1); // 它的函式原形是:int dup2(int oldfd, int targetfd),該函式執行後,targetfd將變成oldfd的複製品
dup2(fd, 2); // 因此這邊的過程其實就是:建立出__null__裝置後,將0、1、2繫結到__null__裝置上
// 所以init程式呼叫InitKernelLogging函式後,通過標準的輸入輸出無法輸出資訊
if (fd > 2) close(fd);
android::base::InitLogging(argv, &android::base::KernelLogger);
}
設定kernel logger
跟蹤InitLogging():system/core/base/logging.cpp
// 設定KernelLogger
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {
//設定logger
SetLogger(std::forward<LogFunction>(logger));
SetAborter(std::forward<AbortFunction>(aborter));
if (gInitialized) {
return;
}
gInitialized = true;
// Stash the command line for later use. We can use /proc/self/cmdline on
// Linux to recover this, but we don`t have that luxury on the Mac/Windows,
// and there are a couple of argv[0] variants that are commonly used.
if (argv != nullptr) {
std::lock_guard<std::mutex> lock(LoggingLock());
ProgramInvocationName() = basename(argv[0]);
}
const char* tags = getenv("ANDROID_LOG_TAGS");
if (tags == nullptr) {
return;
}
// 根據TAG決定最小記錄等級
std::vector<std::string> specs = Split(tags, " ");
for (size_t i = 0; i < specs.size(); ++i) {
// "tag-pattern:[vdiwefs]"
std::string spec(specs[i]);
if (spec.size() == 3 && StartsWith(spec, "*:")) {
switch (spec[2]) {
case `v`:
gMinimumLogSeverity = VERBOSE;
continue;
... ...
}
}
LOG(FATAL) << "unsupported `" << spec << "` in ANDROID_LOG_TAGS (" << tags
<< ")";
}
}
當需要輸出日誌時,KernelLogger函式就會被呼叫:
#if defined(__linux__)
void KernelLogger(android::base::LogId, android::base::LogSeverity severity,
const char* tag, const char*, unsigned int, const char* msg) {
... ...
// 開啟log節點
static int klog_fd = TEMP_FAILURE_RETRY(open("/dev/kmsg", O_WRONLY | O_CLOEXEC));
if (klog_fd == -1) return;
// 決定log等級
int level = kLogSeverityToKernelLogLevel[severity];
// The kernel`s printk buffer is only 1024 bytes.
// TODO: should we automatically break up long lines into multiple lines?
// Or we could log but with something like "..." at the end?
char buf[1024];
size_t size = snprintf(buf, sizeof(buf), "<%d>%s: %s
", level, tag, msg);
if (size > sizeof(buf)) {
size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk
",
level, tag, size);
}
iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = size;
// 通過iovec將log傳送到dev/kmsg
TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
}
#endif
掛在一些分割槽裝置
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
if (is_first_stage) {
... ...
// 掛載特定的分割槽裝置
if (!DoFirstStageMount()) {
LOG(ERROR) << "Failed to mount required partitions early ...";
panic(); // panic會嘗試reboot
}
}
... ...
跟蹤DoFirstStageMount():system/core/init/init_first_stage.cpp
// Mounts partitions specified by fstab in device tree.
bool DoFirstStageMount() {
// Skips first stage mount if we`re in recovery mode.
if (IsRecoveryMode()) {
LOG(INFO) << "First stage mount skipped (recovery mode)";
return true;
}
// Firstly checks if device tree fstab entries are compatible.
if (!is_android_dt_value_expected("fstab/compatible", "android,fstab")) {
LOG(INFO) << "First stage mount skipped (missing/incompatible fstab in device tree)";
return true;
}
// 滿足上述條件時,就會呼叫FirstStageMount的DoFirstStageMount函式
std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
if (!handle) {
LOG(ERROR) << "Failed to create FirstStageMount";
return false;
}
// 主要是初始化特定裝置並掛載
return handle->DoFirstStageMount();
}
完成SELinux相關工作
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
if (is_first_stage) {
... ...
// 此處應該是初始化安全框架:Android Verified Boot
// AVB主要用於防止系統檔案本身被篡改,還包含了防止系統回滾的功能,
// 以免有人試圖回滾系統並利用以前的漏洞
SetInitAvbVersionInRecovery();
// Set up SELinux, loading the SELinux policy.
selinux_initialize(true); // 呼叫selinux_initialize啟動SELinux
... ...
}
... ...
跟蹤selinux_initialize():
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
cb.func_log = selinux_klog_callback; // 用於列印log的回撥函式
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback; // 用於檢查許可權的回撥函式
selinux_set_callback(SELINUX_CB_AUDIT, cb);
// init程式的執行是區分使用者態和核心態的,first_stage執行在核心態
if (in_kernel_domain) {
LOG(INFO) << "Loading SELinux policy";
// 用於載入sepolicy檔案
// 該函式最終將sepolicy檔案傳遞給kernel,這樣kernel就有了安全策略配置檔案,後續的MAC才能開展起來。
if (!selinux_load_policy()) {
panic();
}
bool kernel_enforcing = (security_getenforce() == 1); // 核心中讀取的資訊
bool is_enforcing = selinux_is_enforcing(); // 命令列中得到的資料
// 用於設定selinux的工作模式。selinux有兩種工作模式:
// 1、”permissive”,所有的操作都被允許(即沒有MAC),但是如果違反許可權的話,會記錄日誌
// 2、”enforcing”,所有操作都會進行許可權檢查。在一般的終端中,應該工作於enforing模式
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {
PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
security_failure(); // 將重啟進入recovery mode
}
}
std::string err;
if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
LOG(ERROR) << err;
security_failure();
}
// init`s first stage can`t set properties, so pass the time to the second stage.
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
} else {
selinux_init_all_handles(); // 在second stage呼叫時,初始化所有的handle
}
}
is_first_stage 收尾
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
if (is_first_stage) {
... ...
// We`re in the kernel domain, so re-exec init to transition to the init domain now
// that the SELinux policy has been loaded.
if (selinux_android_restorecon("/init", 0) == -1) { // 按selinux policy要求,重新設定init檔案屬性
PLOG(ERROR) << "restorecon failed";
security_failure(); // 失敗的話會reboot
}
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1); // 記錄初始化時的時間
char* path = argv[0];
char* args[] = { path, nullptr };
execv(path, args); // 再次呼叫init的main函式,啟動使用者態的init程式
// execv() only returns if an error happened, in which case we
// panic and never fall through this conditional.
PLOG(ERROR) << "execv("" << path << "") failed";
security_failure(); // 核心態的程式不應該退出,若退出則會重啟
}
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
... ...
}
上面所有的原始碼我們都是圍繞第一階段分析(is_first_stage),自此第一階段結束,會復位一些資訊,並設定一些環境變數,最後啟動使用者態的init程式,進入init第二階段。
第二階段(使用者態)
init程式的第二階段仍然從main函式開始入手(繼續分析main函式剩餘原始碼)
初始化屬性域
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
// 同樣進行一些判斷及環境變數設定的工作
... ...
// 現在 is_first_stage 為 false 了
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
// 這部分工作不再執行了
if (is_first_stage) {
...........
}
// At this point we`re in the second stage of init.
InitKernelLogging(argv); // 同樣遮蔽標準輸入輸出及定義Kernel logger
LOG(INFO) << "init second stage started!";
// Set up a session keyring that all processes will have access to. It
// will hold things like FBE encryption keys. No process should override
// its session keyring.
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); // 最後呼叫syscall,設定安全相關的值
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); // 這裡的功能類似於“鎖”
... ...
property_init(); // 初始化屬性域 --> 定義於system/core/init/property_service.cpp
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
process_kernel_dt();
process_kernel_cmdline(); // 處理核心命令
// Propagate the kernel variables to internal variables
// used by init as well as the current required properties.
export_kernel_boot_props();
// Make the time that init started available for bootstat to log.
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
// Set libavb version for Framework-only OTA match in Treble build.
const char* avb_version = getenv("INIT_AVB_VERSION");
if (avb_version) property_set("ro.boot.avb_version", avb_version);
... ...
}
這部分程式碼主要的工作應該就是呼叫 property_init 初始化屬性域,然後設定各種屬性。
在Android平臺中,為了讓執行中的所有程式共享系統執行時所需要的各種設定值,系統開闢了屬性儲存區域,並提供了訪問該區域的API。
跟蹤property_init():system/core/init/property_service.cpp
void property_init() {
if (__system_property_area_init()) { // 最終呼叫_system_property_area_init函式初始化屬性域
LOG(ERROR) << "Failed to initialize property area";
exit(1);
}
}
清空環境變數
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
// Clean up our environment.
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT"); // 清除掉之前使用過的環境變數
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
... ...
}
完成SELinux相關工作
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
// Now set up SELinux for second stage.
selinux_initialize(false);
selinux_restore_context(); // 再次完成selinux相關的工作
我們發現在init程式的第一階段,也呼叫了selinux_initialize函式,那麼兩者有什麼區別?
init程式第一階段主要載入selinux相關的策略,而第二階段呼叫selinux_initialize僅僅註冊一些處理器。
我們跟下selinux_initialize():
static void selinux_initialize(bool in_kernel_domain) {
Timer t;
selinux_callback cb;
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
if (in_kernel_domain) {
... ... // 這邊就是第一階段的工作
} else {
selinux_init_all_handles(); // 這邊就是第二階段的工作:註冊處理器
}
}
再來看一下selinux_restore_context():主要是按 selinux policy 要求,重新設定一些檔案的屬性。
// The files and directories that were created before initial sepolicy load or
// files on ramdisk need to have their security context restored to the proper
// value. This must happen before /dev is populated by ueventd.
// 如註釋所述,以下檔案在selinux被載入前就建立了
// 於是,在selinux啟動後,需要重新設定一些屬性
static void selinux_restore_context() {
LOG(INFO) << "Running restorecon...";
selinux_android_restorecon("/dev", 0);
selinux_android_restorecon("/dev/kmsg", 0);
selinux_android_restorecon("/dev/socket", 0);
selinux_android_restorecon("/dev/random", 0);
selinux_android_restorecon("/dev/urandom", 0);
selinux_android_restorecon("/dev/__properties__", 0);
selinux_android_restorecon("/plat_file_contexts", 0);
selinux_android_restorecon("/nonplat_file_contexts", 0);
selinux_android_restorecon("/plat_property_contexts", 0);
selinux_android_restorecon("/nonplat_property_contexts", 0);
selinux_android_restorecon("/plat_seapp_contexts", 0);
selinux_android_restorecon("/nonplat_seapp_contexts", 0);
selinux_android_restorecon("/plat_service_contexts", 0);
selinux_android_restorecon("/nonplat_service_contexts", 0);
selinux_android_restorecon("/plat_hwservice_contexts", 0);
selinux_android_restorecon("/nonplat_hwservice_contexts", 0);
selinux_android_restorecon("/sepolicy", 0);
selinux_android_restorecon("/vndservice_contexts", 0);
selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
selinux_android_restorecon("/dev/device-mapper", 0);
selinux_android_restorecon("/sbin/mke2fs_static", 0);
selinux_android_restorecon("/sbin/e2fsdroid_static", 0);
}
建立epoll控制程式碼
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
/* 03. 完成SELinux相關工作 */
epoll_fd = epoll_create1(EPOLL_CLOEXEC); // 呼叫epoll_create1建立epoll控制程式碼
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
... ...
}
在linux的網路程式設計中,很長的時間都在使用 select 來做事件觸發。在linux新的核心中,有了一種替換它的機制,就是 epoll。
相比於select,epoll最大的好處在於它不會隨著監聽fd數目的增長而降低效率。因為在核心中的 select 實現中,它是採用輪詢來處理的,輪詢的fd數目越多,自然耗時越多。
epoll機制一般使用epoll_create(int size)函式建立epoll控制程式碼,size用來告訴核心這個控制程式碼可監聽的fd的數目。
注意這個引數不同於select()中的第一個引數,在select中需給出最大監聽數加1的值。
此外,當建立好epoll控制程式碼後,它就會佔用一個fd值,在linux下如果檢視/proc/程式id/fd/,能夠看到建立出的fd,因此在使用完epoll後,必須呼叫close()關閉,否則可能導致fd被耗盡。
上述程式碼使用的epoll_create1(EPOLL_CLOEXEC)來建立epoll控制程式碼,該標誌位表示生成的epoll fd具有“執行後關閉”特性。
裝載子程式訊號處理器
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
/* 03. 完成SELinux相關工作 */
/* 04. 建立epoll控制程式碼 */
signal_handler_init(); // 裝載子程式訊號處理器
}
init是一個守護程式,為了防止init的子程式成為殭屍程式(zombie process),需要init在子程式在結束時獲取子程式的結束碼,通過結束碼將程式表中的子程式移除,防止成為殭屍程式的子程式佔用程式表的空間(程式表的空間達到上限時,系統就不能再啟動新的程式了,會引起嚴重的系統問題)。
在linux當中,父程式是通過捕捉 SIGCHLD 訊號來得知子程式執行結束的情況,此處init程式呼叫 signal_handler_init 的目的就是捕獲子程式結束的訊號。
我們跟蹤下signal_handler_init():
void signal_handler_init() {
// Create a signalling mechanism for SIGCHLD.
int s[2];
// 利用socketpair建立出已經連線的兩個socket,分別作為訊號的讀、寫端
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
PLOG(ERROR) << "socketpair failed";
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結構體中,負責處理SIGCHLD訊息
act.sa_handler = SIGCHLD_handler;
act.sa_flags = SA_NOCLDSTOP;
// 呼叫訊號安裝函式sigaction,將監聽的訊號及對應的訊號處理器註冊到核心中
sigaction(SIGCHLD, &act, 0);
// 用於終止出現問題的子程式
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
// 註冊訊號處理函式handle_signal
register_epoll_handler(signal_read_fd, handle_signal);
}
在深入分析程式碼前,我們需要了解一些基本概念:Linux程式通過互相傳送訊息來實現程式間的通訊,這些訊息被稱為“訊號”。每個程式在處理其它程式傳送的訊號時都要註冊處理者,處理者被稱為訊號處理器。
注意到sigaction結構體的sa_flags為SA_NOCLDSTOP。由於系統預設在子程式暫停時也會傳送訊號SIGCHLD,init需要忽略子程式在暫停時發出的SIGCHLD訊號,因此將act.sa_flags 置為SA_NOCLDSTOP,該標誌位表示僅當程式終止時才接受SIGCHLD訊號。
接下來,我們分步驟詳細瞭解一下signal_handler_init具體的工作流程。
SIGCHLD_handler
// system/core/init/signal_handler.cpp
static void SIGCHLD_handler(int) {
if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) {
PLOG(ERROR) << "write(signal_write_fd) failed";
}
}
從上面程式碼我們知道,init程式是所有程式的父程式,當其子程式終止產生SIGCHLD訊號時,SIGCHLD_handler將對signal_write_fd執行寫操作。由於socketpair的繫結關係,這將觸發訊號對應的signal_read_fd收到資料。
register_epoll_handler
根據前文的程式碼我們知道,在裝載訊號監聽器的最後,signal_handler_init呼叫了register_epoll_handler,其程式碼如下所示,注意傳入的引數分別為signal_read_fd和handle_signal:
// system/core/init/init.cpp
void register_epoll_handler(int fd, void (*fn)()) {
epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = reinterpret_cast<void*>(fn);
// epoll_fd增加一個監聽物件fd,fd上有資料到來時,呼叫fn處理
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
PLOG(ERROR) << "epoll_ctl failed";
}
}
根據程式碼不難看出:當epoll控制程式碼監聽到signal_read_fd中有資料可讀時,將呼叫handle_signal進行處理。
至此,結合上文我們知道:當init程式呼叫signal_handler_init後,一旦收到子程式終止帶來的SIGCHLD訊息後,將利用訊號處理者SIGCHLD_handler向signal_write_fd寫入資訊;由於繫結的關係,epoll控制程式碼將監聽到signal_read_fd收到訊息,於是將呼叫handle_signal進行處理。
handle_signal
// system/core/init/signal_handler.cpp
static void handle_signal() {
// Clear outstanding requests.
char buf[32];
read(signal_read_fd, buf, sizeof(buf));
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}
從程式碼中可以看出,handle_signal只是清空signal_read_fd中的資料,然後呼叫ServiceManager::GetInstance().ReapAnyOutstandingChildren()。
ServiceManager定義於 system/core/init/service.cpp 中,是一個單例物件:
... ...
ServiceManager::ServiceManager() {
}
ServiceManager& ServiceManager::GetInstance() {
static ServiceManager instance;
return instance;
}
... ...
void ServiceManager::ReapAnyOutstandingChildren() {
while (ReapOneProcess()) {
}
}
... ...
如上所示,ReapAnyOutstandingChildren函式實際上呼叫了ReapOneProcess。我們結合程式碼,看看ReapOneProcess的具體工作。
bool ServiceManager::ReapOneProcess() {
siginfo_t siginfo = {};
// This returns a zombie pid or informs us that there are no zombies left to be reaped.
// It does NOT reap the pid; that is done below.
//用waitid函式獲取狀態發生變化的子程式pid
//waitid的標記為WNOHANG,即非阻塞,返回為正值就說明有程式掛掉了
if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
PLOG(ERROR) << "waitid failed";
return false;
}
auto pid = siginfo.si_pid;
if (pid == 0) return false;
// At this point we know we have a zombie pid, so we use this scopeguard to reap the pid
// whenever the function returns from this point forward.
// We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we
// want the pid to remain valid throughout that (and potentially future) usages.
auto reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, nullptr, WNOHANG)); });
if (PropertyChildReap(pid)) {
return true;
}
// 利用FindServiceByPid函式,找到pid對應的服務
// FindServiceByPid主要通過輪詢解析init.rc生成的service_list,找到pid與引數一致的srvc
Service* svc = FindServiceByPid(pid);
... ... // 輸出服務結束的原因
if (!svc) { // 沒有找到,說明已經結束了
return true;
}
svc->Reap();
// 根據svc的型別,決定後續的處理方式
if (svc->flags() & SVC_EXEC) {
exec_waiter_.reset(); // 可執行服務則重置對應的waiter
}
if (svc->flags() & SVC_TEMPORARY) {
RemoveService(*svc); // 移除臨時服務
}
return true;
}
Reap
void Service::Reap() {
// 清理未攜帶SVC_ONESHOT 或 攜帶了SVC_RESTART標誌的srvc的程式組
if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
KillProcessGroup(SIGKILL);
}
// Remove any descriptor resources we may have created.
// 清除srvc中建立出的任意描述符
std::for_each(descriptors_.begin(), descriptors_.end(),
std::bind(&DescriptorInfo::Clean, std::placeholders::_1));
// 清理工作完畢後,後面決定是否重啟機器或重啟服務
// TEMP服務不用參與這種判斷
if (flags_ & SVC_TEMPORARY) {
return;
}
pid_ = 0;
flags_ &= (~SVC_RUNNING);
// Oneshot processes go into the disabled state on exit,
// except when manually restarted.
// 對於攜帶了SVC_ONESHOT並且未攜帶SVC_RESTART的srvc,將這類服務的標誌置為SVC_DISABLED,不再自啟動
if ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
flags_ |= SVC_DISABLED;
}
// Disabled and reset processes do not get restarted automatically.
if (flags_ & (SVC_DISABLED | SVC_RESET)) {
NotifyStateChange("stopped");
return;
}
// If we crash > 4 times in 4 minutes, reboot into recovery.
boot_clock::time_point now = boot_clock::now();
// 未攜帶SVC_RESTART的關鍵服務,在規定的間隔內,crash字數過多時,會導致整機重啟;
if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
if (now < time_crashed_ + 4min) {
if (++crash_count_ > 4) {
LOG(ERROR) << "critical process `" << name_ << "` exited 4 times in 4 minutes";
panic();
}
} else {
time_crashed_ = now;
crash_count_ = 1;
}
}
// 將待重啟srvc的標誌位置為SVC_RESTARTING(init程式將根據該標誌位,重啟服務)
flags_ &= (~SVC_RESTART);
flags_ |= SVC_RESTARTING;
// Execute all onrestart commands for this service.
// 重啟在init.rc檔案中帶有onrestart選項的服務
onrestart_.ExecuteAllCommands();
NotifyStateChange("restarting");
return;
}
不難看出,Reap函式的主要作用就是清除問題程式相關的資源,然後根據程式對應的型別,決定是否重啟機器或重啟程式。
ExecuteAllCommands
我們在這一部分的最後,看看定義於system/core/init/Action.cpp中的ExecuteAllCommands函式:
void Action::ExecuteAllCommands() const {
for (const auto& c : commands_) {
ExecuteCommand(c);
}
}
void Action::ExecuteCommand(const Command& command) const {
android::base::Timer t;
// 程式重啟時,將執行對應的函式
int result = command.InvokeFunc();
// 列印log
auto duration = t.duration();
// Any action longer than 50ms will be warned to user as slow operation
if (duration > 50ms || android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
std::string trigger_name = BuildTriggersString();
std::string cmd_str = command.BuildCommandString();
LOG(INFO) << "Command `" << cmd_str << "` action=" << trigger_name << " (" << filename_
<< ":" << command.line() << ") returned " << result << " took "
<< duration.count() << "ms.";
}
}
整個signal_handler_init的內容比較多,在此總結一下:signal_handler_init的本質就是監聽子程式死亡的資訊,然後進行對應的清理工作,並根據死亡程式的型別,決定是否需要重啟程式或機器。
啟動屬性服務
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
/* 03. 完成SELinux相關工作 */
/* 04. 建立epoll控制程式碼 */
/* 05. 裝載子程式訊號處理器 */
property_load_boot_defaults(); // 程式呼叫property_load_boot_defaults進行預設屬性配置相關的工作
export_oem_lock_status(); // 最終就是決定"ro.boot.flash.locked"的值
start_property_service(); // 啟動屬性服務
set_usb_controller();
... ...
}
老樣子,這邊我們跟蹤幾個重要的函式。
property_load_boot_defaults
void property_load_boot_defaults() {
// 就是從各種路徑讀取預設配置
// load_properties_from_file的基本操作就是read_file,然後解析並設定
if (!load_properties_from_file("/system/etc/prop.default", NULL)) {
// Try recovery path
if (!load_properties_from_file("/prop.default", NULL)) {
// Try legacy path
load_properties_from_file("/default.prop", NULL);
}
}
load_properties_from_file("/odm/default.prop", NULL);
load_properties_from_file("/vendor/default.prop", NULL);
update_sys_usb_config(); // 就是設定"persist.sys.usb.config"相關的配置
}
如程式碼所示,property_load_boot_defaults 實際上就是呼叫 load_properties_from_file 解析配置檔案,然後根據解析的結果,設定系統屬性。
start_property_service
void start_property_service() {
property_set("ro.property_service.version", "2");
// 建立了一個非阻塞socket
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr, sehandle);
if (property_set_fd == -1) {
PLOG(ERROR) << "start_property_service socket creation failed";
exit(1);
}
// 呼叫listen函式監聽property_set_fd, 於是該socket變成一個server
listen(property_set_fd, 8);
// 監聽server socket上是否有資料到來
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
init程式在共享記憶體區域中,建立並初始化屬性域。其它程式可以訪問屬性域中的值,但更改屬性值僅能在init程式中進行。這就是init程式呼叫start_property_service的原因。
其它程式修改屬性值時,要預先向init程式提交值變更申請,然後init程式處理該申請,並修改屬性值。在訪問和修改屬性時,init程式都可以進行許可權控制。
匹配命令和函式之間對應關係
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
/* 03. 完成SELinux相關工作 */
/* 04. 建立epoll控制程式碼 */
/* 05. 裝載子程式訊號處理器 */
/* 06. 啟動屬性服務*/
// system/core/init/builtins.cpp,定義Action中的function_map_為BuiltinFuntionMap
const BuiltinFunctionMap function_map;
// 在Action中儲存function_map物件,記錄了命令與函式之間的對應關係
Action::set_function_map(&function_map);
/* 07. 匹配命令和函式之間對應關係 */
/* ------------ 第二階段 ------------ END ------------ */
... ...
}
至此,init程式的準備工作執行完畢, 接下來就要開始解析init.rc檔案了。
第三階段(init.rc)
解析init.rc
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
/* 03. 完成SELinux相關工作 */
/* 04. 建立epoll控制程式碼 */
/* 05. 裝載子程式訊號處理器 */
/* 06. 啟動屬性服務*/
/* 07. 匹配命令和函式之間對應關係 */
/* ------------ 第二階段 ------------ END ------------ */
/* ------------ 第三階段 ----------- BEGIN------------ */
ActionManager& am = ActionManager::GetInstance();
ServiceManager& sm = ServiceManager::GetInstance();
Parser& parser = Parser::GetInstance(); // 構造解析檔案用的parser物件
// 為一些型別的關鍵字,建立特定的parser
// 增加ServiceParser為一個section,對應name為service
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));
// 增加ActionParser為一個section,對應name為action
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));
// 增加ActionParser為一個section,對應name為import
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
std::string bootscript = GetProperty("ro.boot.init_rc", ""); // 判斷是否存在bootscript
// 如果沒有bootscript,則解析init.rc檔案
if (bootscript.empty()) { // 8.0引入
parser.ParseConfig("/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 {
// 若存在bootscript, 則解析bootscript
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);
}
... ...
}
如果沒有定義bootScript,那麼init程式還是會解析init.rc檔案。init.rc檔案是在init程式啟動後執行的啟動指令碼,檔案中記錄著init程式需執行的操作。
此處解析函式傳入的引數為“/init.rc”,解析的是執行時與init程式同在根目錄下的init.rc檔案。該檔案在編譯前,定義於system/core/rootdir/init.rc中。
✨ 繼續往下分析main函式之前;
✨ 我們先了解一下init.rc是什麼,然後分析下parser解析init.rc過程;
✨ 最後我們再繼續跟原始碼!
init.rc配置檔案
init.rc是一個配置檔案,內部由Android初始化語言編寫(Android Init Language)編寫的指令碼,主要包含五種型別語句:<font color=#87CEFA>Action、Command、Service、Option 和 Import</font>,在分析程式碼的過程中我們會詳細介紹。
init.rc的配置程式碼在:system/core/rootdir/init.rc中。
init.rc檔案是在init程式啟動後執行的啟動指令碼,檔案中記錄著init程式需執行的操作。
init.rc檔案大致分為兩大部分:
一部分是以“on”關鍵字開頭的動作列表(action list):
on early-init // Action型別語句
# Set init and its forked children`s oom_adj. // #:註釋符號
write /proc/1/oom_score_adj -1000
... ...
# Shouldn`t be necessary, but sdcard won`t start without it. http://b/22568628.
mkdir /mnt 0775 root system
... ...
start ueventd
Action型別語句格式:
on <trigger> [&& <trigger>]* // 設定觸發器
<command>
<command> // 動作觸發之後要執行的命令
另一部分是以“service”關鍵字開頭的服務列表(service list): 如 Zygote
service ueventd /sbin/ueventd // Service型別語句
class core
critical
seclabel u:r:ueventd:s0
Service型別語句格式:
service <name> <pathname> [ <argument> ]* // <service的名字><執行程式路徑><傳遞引數>
<option> // option是service的修飾詞,影響什麼時候、如何啟動services
<option>
...
藉助系統環境變數或Linux命令,
? 動作列表用於建立所需目錄,以及為某些特定檔案指定許可權;
? 服務列表用來記錄init程式需要啟動的一些子程式,如上面程式碼所示,service關鍵字後的第一個字串表示服務(子程式)的名稱,第二個字串表示服務的執行路徑。
值得一提的是從Android 7.0後的原始碼,對init.rc檔案進行了拆分,每個服務一個rc檔案。我們要分析的zygote服務的啟動指令碼則在init.zygoteXX.rc中定義。
在init.rc的import段我們看到如下程式碼:
import /init.${ro.zygote}.rc // 可以看出init.rc不再直接引入一個固定的檔案,而是根據屬性ro.zygote的內容來引入不同的檔案
從android5.0開始,android開始支援64位的編譯,zygote本身也就有了32位和64位的區別,所以在這裡用ro.zygote屬性來控制啟動不同版本的zygote程式。
init.rc位於/system/core/rootdir下。在這個路徑下還包括四個關於zygote的rc檔案。分別是init.zygote32.rc,init.zygote32_64.rc,init.zygote64.rc,init.zygote64_32.rc,由硬體決定呼叫哪個檔案。
這裡拿32位處理器為例,init.zygote32.rc的程式碼如下所示:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main # class是一個option,指定zygote服務的型別為main
priority -20
user root
group root readproc
# socket關鍵字表示一個option,建立一個名為dev/socket/zygote,型別為stream,許可權為660的socket
socket zygote stream 660 root system
# onrestart是一個option,說明在zygote重啟時需要執行的command
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 -Xzygote /system/bin –zygote –start-system-server語句解讀:
在Init.zygote32.rc中,定義了一個zygote服務:zygote,由關鍵字service告訴init程式建立一個名為zygote的程式,這個程式要執行的程式是:/system/bin/app_process,給這個程式四個引數:
? -Xzygote:該引數將作為虛擬機器啟動時所需的引數
? /system/bin:代表虛擬機器程式所在目錄
? –zygote:指明以ZygoteInit.java類中的main函式作為虛擬機器執行入口
? –start-system-server:告訴Zygote程式啟動systemServer程式
init.rc解析過程
回顧解析init.rc的程式碼:
int main(int argc, char** argv) {
... ...
ActionManager& am = ActionManager::GetInstance();
ServiceManager& sm = ServiceManager::GetInstance();
Parser& parser = Parser::GetInstance(); // 構造解析檔案用的parser物件
// 增加ServiceParser為一個section,對應name為service
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));
// 增加ActionParser為一個section,對應name為action
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));
// 增加ActionParser為一個section,對應name為import
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/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);
}
... ...
}
Parse
我們發現在解析前,使用了Parser類(在init目錄下的 <font color=#00FFFF>init_parser.h</font> 中定義),構造了parser物件:
Parser& parser = Parser::GetInstance(); // 構造解析檔案用的parser物件
初始化ServiceParser用來解析 “service”塊,ActionParser用來解析”on”塊,ImportParser用來解析“import”塊,“import”是用來引入一個init配置檔案,來擴充套件當前配置的。
// 增加ServiceParser為一個section,對應name為service
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));
// 增加ActionParser為一個section,對應name為action
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am));
// 增加ActionParser為一個section,對應name為import
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
/system/core/init/readme.txt中對init檔案中的所有關鍵字做了介紹,主要包含了Actions, Commands, Services, Options, and Imports等,可自行學習解讀。
ParseConfig
下面就是分析解析過程了:parser.ParseConfig(“/init.rc”) (函式定義於system/core/init/init_parser.cpp中)
bool Parser::ParseConfig(const std::string& path) {
if (is_dir(path.c_str())) { // 判斷傳入引數是否為目錄地址
return ParseConfigDir(path); // 遞迴目錄,最終還是靠ParseConfigFile來解析實際的檔案
}
return ParseConfigFile(path); // 傳入引數為檔案地址
}
先來看看ParseConfigDir函式:
bool Parser::ParseConfigDir(const std::string& path) {
LOG(INFO) << "Parsing directory " << path << "...";
std::unique_ptr<DIR, int(*)(DIR*)> config_dir(opendir(path.c_str()), closedir);
if (!config_dir) {
PLOG(ERROR) << "Could not import directory `" << path << "`";
return false;
}
// 遞迴目錄,得到需要處理的檔案
dirent* current_file;
std::vector<std::string> files;
while ((current_file = readdir(config_dir.get()))) {
// Ignore directories and only process regular files.
if (current_file->d_type == DT_REG) {
std::string current_path =
android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name);
files.emplace_back(current_path);
}
}
// Sort first so we load files in a consistent order (bug 31996208)
std::sort(files.begin(), files.end());
for (const auto& file : files) {
// 容易看出,最終仍是呼叫ParseConfigFile
if (!ParseConfigFile(file)) {
LOG(ERROR) << "could not import file `" << file << "`";
}
}
return true;
}
接下來就重點分析ParseConfigFile():
bool Parser::ParseConfigFile(const std::string& path) {
... ...
android::base::Timer t;
std::string data;
std::string err;
if (!ReadFile(path, &data, &err)) { // 讀取路徑指定檔案中的內容,儲存為字串形式
LOG(ERROR) << err;
return false;
}
... ...
ParseData(path, data); // 解析獲取的字串
... ...
return true;
}
ParseConfigFile只是讀取檔案的內容並轉換為字串,實際的解析工作被交付給ParseData。
ParseData
ParseData函式定義於system/core/init/init_parser.cpp中,負責根據關鍵字解析出服務和動作。動作與服務會以連結串列節點的形式註冊到service_list與action_list中,service_list與action_list是init程式中宣告的全域性結構體。
跟蹤ParseData():
void Parser::ParseData(const std::string& filename, const std::string& data) {
//TODO: Use a parser with const input and remove this copy
// copy一波資料
std::vector<char> data_copy(data.begin(), data.end());
data_copy.push_back(` `);
// 解析用的結構體
parse_state state;
state.line = 0;
state.ptr = &data_copy[0];
state.nexttoken = 0;
SectionParser* section_parser = nullptr;
std::vector<std::string> args;
for (;;) {
// next_token以行為單位分割引數傳遞過來的字串,初始沒有分割符時,最先走到T_TEXT分支
switch (next_token(&state)) {
case T_EOF:
if (section_parser) {
section_parser->EndSection(); // 解析結束
}
return;
case T_NEWLINE:
state.line++;
if (args.empty()) {
break;
}
... ...
// 在前文建立parser時,我們為service,on,import定義了對應的parser
// 這裡就是根據第一個引數,判斷是否有對應的parser
if (section_parsers_.count(args[0])) {
if (section_parser) {
// 結束上一個parser的工作,將構造出的物件加入到對應的service_list與action_list中
section_parser->EndSection();
}
// 獲取引數對應的parser
section_parser = section_parsers_[args[0]].get();
std::string ret_err;
// 呼叫實際parser的ParseSection函式
if (!section_parser->ParseSection(std::move(args), filename, state.line, &ret_err)) {
LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
section_parser = nullptr;
}
} else if (section_parser) {
std::string ret_err;
/*
* 如果第一個引數不是service,on,import
* 則呼叫前一個parser的ParseLineSection函式
* 這裡相當於解析一個引數塊的子項
*/
if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) {
LOG(ERROR) << filename << ": " << state.line << ": " << ret_err;
}
}
// 清空本次解析的資料
args.clear();
break;
case T_TEXT:
// 將本次解析的內容寫入到args中
args.emplace_back(state.text);
break;
}
}
}
上面的程式碼看起來比較複雜,但實際上就是物件導向,根據不同的關鍵字,使用不同的parser物件進行解析。
至此,init.rc解析完!Ok,別忘了,main函式還沒有分析完,繼續往下看。
第四階段
向執行佇列中新增其他action
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
/* 03. 完成SELinux相關工作 */
/* 04. 建立epoll控制程式碼 */
/* 05. 裝載子程式訊號處理器 */
/* 06. 啟動屬性服務*/
/* 07. 匹配命令和函式之間對應關係 */
/* ------------ 第二階段 ------------ END ------------ */
/* ------------ 第三階段 ----------- BEGIN------------ */
/* init解析 */
/* ------------ 第三階段 ----------- END ------------ */
/* ------------ 第四階段 ----------- BEGIN------------ */
// 通過am對命令執行順序進行控制
// ActionManager& am = ActionManager::GetInstance();
// init執行命令觸發器主要分為early-init,init,late-init,boot等
am.QueueEventTrigger("early-init"); // 新增觸發器early-init,執行on early-init內容
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
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");
// Trigger all the boot actions to get us started.
// 新增觸發器init,執行on init內容,主要包括建立/掛在一些目錄,以及symlink等
am.QueueEventTrigger("init");
// 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(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
// Don`t mount filesystems or start core system services in charger mode.
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger"); // on charger階段
} else {
am.QueueEventTrigger("late-init"); // 非充電模式新增觸發器last-init
}
// Run all property triggers based on current state of the properties.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
}
其餘工作
繼續分析main函式:
int main(int argc, char** argv) {
/* ------------ 第一階段 ------------ BEGIN------------ */
/* 01. 判斷及增加環境變數 */
/* 02. 建立檔案系統目錄並掛載相關的檔案系統 */
/* 03. 重定向輸入輸出/核心Log系統 */
/* 04. 掛在一些分割槽裝置 */
/* 05. 完成SELinux相關工作 */
/* 06. is_first_stage 收尾 */
/* ------------ 第一階段 ------------- END ------------ */
/* ------------ 第二階段 ------------ BEGIN------------ */
/* 01. 初始化屬性域 */
/* 02. 清空環境變數 */
/* 03. 完成SELinux相關工作 */
/* 04. 建立epoll控制程式碼 */
/* 05. 裝載子程式訊號處理器 */
/* 06. 啟動屬性服務*/
/* 07. 匹配命令和函式之間對應關係 */
/* ------------ 第二階段 ------------ END ------------ */
/* ------------ 第三階段 ----------- BEGIN------------ */
/* init解析 */
/* ------------ 第三階段 ----------- END ------------ */
/* ------------ 第四階段 ----------- BEGIN------------ */
/* 01. 向執行佇列中新增其他action */
/* 02. 其餘工作 */
while (true) {
// 判斷是否有事件需要處理
// By default, sleep until something happens.
int epoll_timeout_ms = -1;
if (do_shutdown && !shutting_down) {
do_shutdown = false;
if (HandlePowerctlMessage(shutdown_command)) {
shutting_down = true;
}
}
if (!(waiting_for_prop || sm.IsWaitingForExec())) {
am.ExecuteOneCommand(); // 依次執行每個action中攜帶command對應的執行函式
}
if (!(waiting_for_prop || sm.IsWaitingForExec())) {
if (!shutting_down) restart_processes(); // 重啟一些掛掉的程式
// If there`s a process that needs restarting, wake up in time for that.
if (process_needs_restart_at != 0) { // 程式重啟相關邏輯
epoll_timeout_ms = (process_needs_restart_at - time(nullptr)) * 1000;
if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
}
// If there`s more work to do, wake up again immediately.
if (am.HasMoreCommands()) epoll_timeout_ms = 0; // 有action待處理,不等待
}
epoll_event ev;
// 沒有事件到來的話,最多阻塞timeout時間
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
if (nr == -1) {
PLOG(ERROR) << "epoll_wait failed";
} else if (nr == 1) {
//有事件到來,執行對應處理函式
//根據上文知道,epoll控制程式碼(即epoll_fd)主要監聽子程式結束,及其它程式設定系統屬性的請求
((void (*)()) ev.data.ptr)();
}
}
} // end main
至此,Init.cpp的main函式分析完畢!init程式已經啟動完成,一些重要的服務如core服務和main服務也都啟動起來,並啟動了zygote(/system/bin/app_process64)程式,zygote初始化時會建立虛擬機器,啟動systemserver等。