前言

init进程,它是一个由内核启动的用户级进程,当Linux内核启动之后,运行的第一个进程是init,这个进程是一个守护进程,确切的说,它是Linux系统中用户控件的第一个进程,所以它的进程号是1。它的生命周期贯穿整个linux 内核运行的始终, linux中所有其它的进程的共同始祖均为init进程。

开篇

核心源码

Android 版本 关键类 路径
8.1 init.rc system/core/rootdir/init.rc
8.1 init.cpp system/core/init/init.cpp
8.1 property_service.cpp system/core/init/property_service.cpp
8.1 init_parser.h system/core/init/init_parser.h
8.1 init_parser.cpp system/core/init/init_parser.cpp
8.1 log.cpp system/core/init/log.cpp
8.1 logging.cpp system/core/base/logging.cpp
8.1 property_service.cpp system/core/init/property_service.cpp
8.1 signal_handler.cpp system/core/init/signal_handler.cpp
8.1 service.cpp system/core/init/service.cpp
8.1 Action.cpp system/core/init/Action.cpp
8.1 builtins.cpp system/core/init/builtins.cpp

Android系统启动过程

1. 按下电源系统启动
    当电源按下时引导芯片代码开始从预定义的地方(固化在ROM)开始执行,加载引导程序Bootloader到RAM,然后执行。
2. 引导程序Bootloader
    引导程序是在Android操作系统开始运行前的一个小程序,它的主要作用是把系统OS拉起来并运行。
3. linux内核启动
    内核启动时,设置缓存、被保护存储器、计划列表,加载驱动。当内核完成系统设置,它首先在系统文件中寻找”init”文件,然后启动root进程或者系统的第一个进程。
4. init进程启动
    ✨ 这就是我们接下来要讨论的内容 ✨

Read The Fucking Code

Android init进程的入口文件在system/core/init/init.cpp中,由于init是命令行程序,所以分析init.cpp首先应从main函数开始:

第一阶段(内核态)

判断及增加环境变量

int main(int argc, char** argv) {/* ------------ 第一阶段 ------------ BEGIN------------ *///根据参数,判断是否需要启动ueventd和watchdogdif (!strcmp(basename(argv[0]), "ueventd")) {                                         // 启动ueventdreturn ueventd_main(argc, argv);}if (!strcmp(basename(argv[0]), "watchdogd")) {                                       // 启动watchdogdreturn 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.mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");                         // 挂载tmpfs文件系统 mkdir("/dev/pts", 0755);mkdir("/dev/socket", 0755);mount("devpts", "/dev/pts", "devpts", 0, NULL);                                  // 挂载devpts文件系统 #define MAKE_STR(x) __STRING(x)mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));      // 挂载proc文件系统// Don't expose the raw commandline to unprivileged processes.chmod("/proc/cmdline", 0440);                                                    // 8.0新增, 收紧了cmdline目录的权限gid_t groups[] = { AID_READPROC };                                               // 8.0新增,增加了个用户组setgroups(arraysize(groups), groups);       mount("sysfs", "/sys", "sysfs", 0, NULL);                                        // 挂载sysfs文件系统 mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);                     // 8.0新增mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));                              // 提前创建了kmsg设备节点文件,用于输出log信息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) {                                                  // 若开启失败,则记录logint 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和stderrdup2(fd, 1);                                                     // 它的函数原形是:int dup2(int oldfd, int targetfd),该函数执行后,targetfd将变成oldfd的复制品dup2(fd, 2);                                                     // 因此这边的过程其实就是:创建出__null__设备后,将0、1、2绑定到__null__设备上if (fd > 2) close(fd);                                           // 所以init进程调用InitKernelLogging函数后,通过标准的输入输出无法输出信息android::base::InitLogging(argv, &android::base::KernelLogger);
}

设置kernel logger

跟踪InitLogging():system/core/base/logging.cpp

// 设置KernelLogger
void InitLogging(char* argv[], LogFunction&& logger, AbortFunction&& aborter) {//设置loggerSetLogger(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\n", level, tag, msg);if (size > sizeof(buf)) {size = snprintf(buf, sizeof(buf), "<%d>%s: %zu-byte message too long for printk\n",level, tag, size);}iovec iov[1];iov[0].iov_base = buf;iov[0].iov_len = size;// 通过iovec将log发送到dev/kmsgTEMP_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 loggerLOG(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_signalregister_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与参数一致的srvcService* 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();// 打印logauto duration = t.duration();// Any action longer than 50ms will be warned to user as slow operationif (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 pathif (!load_properties_from_file("/prop.default", NULL)) {// Try legacy pathload_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");// 创建了一个非阻塞socketproperty_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变成一个serverlisten(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. 启动属性服务*/const BuiltinFunctionMap function_map;         // system/core/init/builtins.cpp,定义Action中的function_map_为BuiltinFuntionMapAction::set_function_map(&function_map);       // 在Action中保存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对象// 为一些类型的关键字,创建特定的parserparser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));        // 增加ServiceParser为一个section,对应name为serviceparser.AddSectionParser("on", std::make_unique<ActionParser>(&am));              // 增加ActionParser为一个section,对应name为actionparser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));      // 增加ActionParser为一个section,对应name为importstd::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, 则解析bootscriptparser.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)编写的脚本,主要包含五种类型语句:Action、Command、Service、Option 和 Import,在分析代码的过程中我们会详细介绍。

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 corecriticalseclabel 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-serverclass main                                              # class是一个option,指定zygote服务的类型为mainpriority -20user rootgroup root readprocsocket zygote stream 660 root system                    # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socketonrestart write /sys/android_power/request_state wake   # onrestart是一个option,说明在zygote重启时需要执行的commandonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /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对象parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));        // 增加ServiceParser为一个section,对应name为serviceparser.AddSectionParser("on", std::make_unique<ActionParser>(&am));              // 增加ActionParser为一个section,对应name为actionparser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));      // 增加ActionParser为一个section,对应name为importstd::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目录下的 init_parser.h 中定义),构造了parser对象:

    Parser& parser = Parser::GetInstance();                // 构造解析文件用的parser对象

初始化ServiceParser用来解析 “service”块,ActionParser用来解析"on"块,ImportParser用来解析“import”块,“import”是用来引入一个init配置文件,来扩展当前配置的。

    parser.AddSectionParser("service", std::make_unique<ServiceParser>(&sm));        // 增加ServiceParser为一个section,对应name为serviceparser.AddSectionParser("on", std::make_unique<ActionParser>(&am));              // 增加ActionParser为一个section,对应name为actionparser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));      // 增加ActionParser为一个section,对应name为import

/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) {// 容易看出,最终仍是调用ParseConfigFileif (!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('\0');// 解析用的结构体parse_state state;state.line = 0;state.ptr = &data_copy[0];state.nexttoken = 0;SectionParser* section_parser = nullptr;std::vector<std::string> args;for (;;) {switch (next_token(&state)) {                // next_token以行为单位分割参数传递过来的字符串,初始没有分割符时,最先走到T_TEXT分支case T_EOF:if (section_parser) {section_parser->EndSection();        // 解析结束}return;case T_NEWLINE:state.line++;if (args.empty()) {break;}... ...// 在前文创建parser时,我们为service,on,import定义了对应的parser// 这里就是根据第一个参数,判断是否有对应的parserif (section_parsers_.count(args[0])) {           if (section_parser) {// 结束上一个parser的工作,将构造出的对象加入到对应的service_list与action_list中section_parser->EndSection();            }// 获取参数对应的parsersection_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.am.QueueEventTrigger("init");                // 添加触发器init,执行on init内容,主要包括创建/挂在一些目录,以及symlink等// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random// wasn't ready immediately after wait_for_coldboot_doneam.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等。


参考Blog

01. https://www.cnblogs.com/pepsimaxin/p/6702945.html
                  02. https://blog.csdn.net/gaugamela/article/details/79280385
                  03. http://qiangbo.space/2016-10-10/AndroidAnatomy_Process_Creation/

转载于:https://www.cnblogs.com/pepsimaxin/p/9443002.html

Android 8.1 源码_启动篇(一) -- 深入研究 init(转 Android 9.0 分析)相关推荐

  1. APISIX源码解析-启动篇【ops.lua - start】

    APISIX源码解析-启动篇[ops.lua - start] 命令开始入口 ops.execute(env, arg) 命令参数校验 目前仅支持:help.version.init.init_etc ...

  2. 开源中国源码学习UI篇(二)之NavigationDrawer+Fragment的使用分析

    前文链接:开源中国源码学习UI篇(一)之FragmentTabHost的使用分析 开源中国2.2版,完整源码地址为:http://git.oschina.net/oschina/android-app ...

  3. android gif快手 源码,Android-JNI开发系列《十一》实践-利用Android C源码实现GIF图片的播放...

    人间观察 人往往都是多面性的,一个人的时候是一个样子,一群人的时候是另一个样子. 声明 此篇文章只为记录和学习JNI以及了解GIF的解码原理.借鉴了网上的有关gif文章介绍和代码.如果是自己学习,建议 ...

  4. android studio 项目源码_这个标星 2.3k+ 的项目带你 Android 源码分析从入门到放弃...

    上次写了一篇 Android 开发者的福利,介绍几款看源码的工具 ,这篇文章主要介绍了三款看 Android 源码的工具,后台有很多同学留言问,有没有分析源码的. 分析源码的文章也很多,但大多数文章不 ...

  5. 和菜鸟一起学linux内核源码之启动篇

    又是一个周末,日子过得比较散,虽然期间也有不断地看书学习,总觉得有点小盲目.想想毕业也快要1年了,从事嵌入式linux的研发工作也1年多了.这1年多的从实习到正式工作到现在的自己,进步有,也很大,但是 ...

  6. android 快传 源码_最新安卓仿茄子快传APP源码包括服务端源码Android开发快传类项目源码全套...

    适使用范围:  安卓APP茄子快传源码 Android项目源码相似茄子快传的快传项目包括服务端 运行环境:  Android app 源码详细: 本项目是一个基于安卓的相似茄子快传的项目源码,包括安卓 ...

  7. android 汽车 源码_汽车级Linux,无需Google即可运行Android等

    android 汽车 源码 开源新闻让您阅读愉快. 2014年6月28日至7月4日 在本周的开源新闻摘要中,我们庆祝我们的数字独立性,并使用Automotive Grade Linux进行巡游. 再加 ...

  8. Android 12系统源码_系统壁纸(一)系统壁纸的原理分析

    前言 在Android中,壁纸分为静态与动态两种.静态壁纸是一张图片,而动态壁纸则以动画为表现形式,或者可以对用户的操作作出反应.这两种形式看似差异很大,其实二者的本质是统一的.它们都以一个Servi ...

  9. android 快传 源码_安卓APP仿茄子快传源码,Android项目源码类似茄子快传的快传项目包括服务端...

    适用范围:安卓APP仿茄子快传源码,Android项目源码类似茄子快传的快传项目包括服务端 演示地址:(以截图为准) 运行环境:Android+PC+web 其他说明: 本项目是一个基于安卓的类似茄子 ...

最新文章

  1. Oracle隐含参数:_allow_resetlogs_corruption
  2. Python-面向对象进阶
  3. 欧拉定理和C语言实现 - win32版
  4. ul弹性怎么一行显示_css3系列之弹性盒子 flex
  5. 用计算机弹生僻字乐谱,生僻字 E调 (拇指琴卡林巴琴弹奏谱)_谱友园地_中国曲谱网...
  6. flink java生成流式数据
  7. mate30 e pro和mate30 pro的区别_新出的华为mate40跟华为mate40pro手机有何区别呢?
  8. STM32CUBEF4 实现USB 虚拟串口
  9. desktop docker 无法卸载_docker 安装教程和常见问题
  10. php 抽象类 接口 区别,php中接口、抽象类以及接口和抽象类区别详解
  11. how to get keyboard key with non blocking in terminal
  12. android frameworks下面config.xml属性
  13. [字符串题-java实现]20. 有效的括号
  14. C语言怎样判断乘法越界,c语言算术运算符越界问题解决方案
  15. Topcoder 2016 TCO Algorithm Algo Semifinal 2 Hard
  16. 纯函数及函数的副作用详解
  17. 华人民共和国道路交通安全法
  18. struts2.x自定义类型转换
  19. OSPF高级特性 —— 修改参考带宽值 + 修改优先级
  20. 尚学堂(java)1到13章所有练习题答案详解

热门文章

  1. clinical significance临床显著性
  2. linux iptables规则组成及用法
  3. centos7安装redis3.2.5
  4. ubuntu set ntpdate
  5. POJ 3734 Blocks
  6. 30个 Web 设计者 必备的免费 PSD UI 工具包
  7. java开发环境配置环境变量_3. Java开发环境的搭建:安装JDK,配置环境变量
  8. linux find 删除文件夹,linux中find与rm实现查找并删除目录或文件linux操作系统 -电脑资料...
  9. python跳出双层for循环的解决方法
  10. vivado----fpga硬件调试 (三)----mark_debug