前言
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函数开始:
第一阶段(内核态)
判断及增加环境变量
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">//根据参数,判断是否需要启动ueventd和watchdogd</span>
<span style="color:#0000ff">if</span> (!<span style="color:#0000ff">strcmp</span>(basename(argv[0]), <span style="color:#a31515">"ueventd"</span>)) { <span style="color:#008000">// 启动ueventd</span>
<span style="color:#0000ff">return</span> ueventd_main(argc, argv);
}
<span style="color:#0000ff">if</span> (!<span style="color:#0000ff">strcmp</span>(basename(argv[0]), <span style="color:#a31515">"watchdogd"</span>)) { <span style="color:#008000">// 启动watchdogd</span>
<span style="color:#0000ff">return</span> watchdogd_main(argc, argv);
}
<span style="color:#0000ff">if</span> (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers(); <span style="color:#008000">// 若紧急重启,则安装对应的消息处理器</span>
}
add_environment(<span style="color:#a31515">"PATH"</span>, _PATH_DEFPATH); <span style="color:#008000">// 添加环境变量</span>
... ...
}</code></span></span>
创建并挂载相关的文件系统
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#0000ff">bool</span> is_first_stage = (getenv(<span style="color:#a31515">"INIT_SECOND_STAGE"</span>) == <span style="color:#a31515">nullptr</span>);
<span style="color:#0000ff">if</span> (is_first_stage) { <span style="color:#008000">// 判断是否是系统启动的第一阶段(第一次进入:true) </span>
boot_clock::time_point start_time = boot_clock::now(); <span style="color:#008000">// 用于记录启动时间</span>
<span style="color:#008000">// Clear the umask.</span>
umask(0); <span style="color:#008000">// 清除屏蔽字(file mode creation mask),保证新建的目录的访问权限不受屏蔽字影响</span>
<span style="color:#008000">// Get the basic filesystem setup we need put together in the initramdisk</span>
<span style="color:#008000">// on / and then we'll let the rc file figure out the rest.</span>
mount(<span style="color:#a31515">"tmpfs"</span>, <span style="color:#a31515">"/dev"</span>, <span style="color:#a31515">"tmpfs"</span>, MS_NOSUID, <span style="color:#a31515">"mode=0755"</span>); <span style="color:#008000">// 挂载tmpfs文件系统 </span>
mkdir(<span style="color:#a31515">"/dev/pts"</span>, 0755);
mkdir(<span style="color:#a31515">"/dev/socket"</span>, 0755);
mount(<span style="color:#a31515">"devpts"</span>, <span style="color:#a31515">"/dev/pts"</span>, <span style="color:#a31515">"devpts"</span>, 0, <span style="color:#a31515">NULL</span>); <span style="color:#008000">// 挂载devpts文件系统 </span>
<span style="color:#2b91af">#define MAKE_STR(x) __STRING(x)</span>
mount(<span style="color:#a31515">"proc"</span>, <span style="color:#a31515">"/proc"</span>, <span style="color:#a31515">"proc"</span>, 0, <span style="color:#a31515">"hidepid=2,gid="</span> MAKE_STR(AID_READPROC)); <span style="color:#008000">// 挂载proc文件系统</span>
<span style="color:#008000">// Don't expose the raw commandline to unprivileged processes.</span>
chmod(<span style="color:#a31515">"/proc/cmdline"</span>, 0440); <span style="color:#008000">// 8.0新增, 收紧了cmdline目录的权限</span>
<span style="color:#0000ff">gid_t</span> groups[] = { AID_READPROC }; <span style="color:#008000">// 8.0新增,增加了个用户组</span>
setgroups(arraysize(groups), groups);
mount(<span style="color:#a31515">"sysfs"</span>, <span style="color:#a31515">"/sys"</span>, <span style="color:#a31515">"sysfs"</span>, 0, <span style="color:#a31515">NULL</span>); <span style="color:#008000">// 挂载sysfs文件系统 </span>
mount(<span style="color:#a31515">"selinuxfs"</span>, <span style="color:#a31515">"/sys/fs/selinux"</span>, <span style="color:#a31515">"selinuxfs"</span>, 0, <span style="color:#a31515">NULL</span>); <span style="color:#008000">// 8.0新增</span>
mknod(<span style="color:#a31515">"/dev/kmsg"</span>, S_IFCHR | 0600, makedev(1, 11)); <span style="color:#008000">// 提前创建了kmsg设备节点文件,用于输出log信息</span>
mknod(<span style="color:#a31515">"/dev/random"</span>, S_IFCHR | 0666, makedev(1, 8));
mknod(<span style="color:#a31515">"/dev/urandom"</span>, S_IFCHR | 0666, makedev(1, 9));
... ...
}</code></span></span>
如上所示,该部分主要用于创建和挂载启动所需的文件目录。需要注意的是,在编译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系统
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建并挂载相关的文件系统 */</span>
<span style="color:#0000ff">if</span> (is_first_stage) {
... ...
<span style="color:#008000">// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually</span>
<span style="color:#008000">// talk to the outside world...</span>
InitKernelLogging(argv);
... ...
}
... ...</code></span></span>
屏蔽标准的输入输出
跟踪InitKernelLogging(): system/core/init/log.cpp
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">void</span> <span style="color:#a31515">InitKernelLogging</span>(<span style="color:#0000ff">char</span>* argv[]) {
<span style="color:#008000">// Make stdin/stdout/stderr all point to /dev/null.</span>
<span style="color:#0000ff">int</span> fd = open(<span style="color:#a31515">"/sys/fs/selinux/null"</span>, O_RDWR);
<span style="color:#0000ff">if</span> (fd == -1) { <span style="color:#008000">// 若开启失败,则记录log</span>
<span style="color:#0000ff">int</span> saved_errno = errno;
android::base::InitLogging(argv, &android::base::KernelLogger);
errno = saved_errno;
PLOG(FATAL) << <span style="color:#a31515">"Couldn't open /sys/fs/selinux/null"</span>;
}
dup2(fd, 0); <span style="color:#008000">// dup2函数的作用是用来复制一个文件的描述符, 通常用来重定向进程的stdin、stdout和stderr</span>
dup2(fd, 1); <span style="color:#008000">// 它的函数原形是:int dup2(int oldfd, int targetfd),该函数执行后,targetfd将变成oldfd的复制品</span>
dup2(fd, 2); <span style="color:#008000">// 因此这边的过程其实就是:创建出__null__设备后,将0、1、2绑定到__null__设备上</span>
<span style="color:#0000ff">if</span> (fd > 2) close(fd); <span style="color:#008000">// 所以init进程调用InitKernelLogging函数后,通过标准的输入输出无法输出信息</span>
android::base::InitLogging(argv, &android::base::KernelLogger);
}</code></span></span>
设置kernel logger
跟踪InitLogging():system/core/base/logging.cpp
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#008000">// 设置KernelLogger</span>
<span style="color:#0000ff">void</span> <span style="color:#a31515">InitLogging</span>(<span style="color:#0000ff">char</span>* argv[], LogFunction&& logger, AbortFunction&& aborter) {
<span style="color:#008000">//设置logger</span>
SetLogger(<span style="color:#0000ff">std</span>::forward<LogFunction>(logger));
SetAborter(<span style="color:#0000ff">std</span>::forward<AbortFunction>(aborter));
<span style="color:#0000ff">if</span> (gInitialized) {
<span style="color:#0000ff">return</span>;
}
gInitialized = <span style="color:#a31515">true</span>;
<span style="color:#008000">// Stash the command line for later use. We can use /proc/self/cmdline on</span>
<span style="color:#008000">// Linux to recover this, but we don't have that luxury on the Mac/Windows,</span>
<span style="color:#008000">// and there are a couple of argv[0] variants that are commonly used.</span>
<span style="color:#0000ff">if</span> (argv != <span style="color:#a31515">nullptr</span>) {
<span style="color:#0000ff">std</span>::lock_guard<<span style="color:#0000ff">std</span>::mutex> lock(LoggingLock());
ProgramInvocationName() = basename(argv[0]);
}
<span style="color:#0000ff">const</span> <span style="color:#0000ff">char</span>* tags = getenv(<span style="color:#a31515">"ANDROID_LOG_TAGS"</span>);
<span style="color:#0000ff">if</span> (tags == <span style="color:#a31515">nullptr</span>) {
<span style="color:#0000ff">return</span>;
}
<span style="color:#008000">// 根据TAG决定最小记录等级</span>
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">vector</span><<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>> specs = Split(tags, <span style="color:#a31515">" "</span>);
<span style="color:#0000ff">for</span> (<span style="color:#0000ff">size_t</span> i = 0; i < specs.size(); ++i) {
<span style="color:#008000">// "tag-pattern:[vdiwefs]"</span>
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> <span style="color:#a31515">spec</span>(specs[i]);
<span style="color:#0000ff">if</span> (spec.size() == 3 && StartsWith(spec, <span style="color:#a31515">"*:"</span>)) {
<span style="color:#0000ff">switch</span> (spec[2]) {
<span style="color:#0000ff">case</span> <span style="color:#a31515">'v'</span>:
gMinimumLogSeverity = VERBOSE;
<span style="color:#0000ff">continue</span>;
... ...
}
}
LOG(FATAL) << <span style="color:#a31515">"unsupported '"</span> << spec << <span style="color:#a31515">"' in ANDROID_LOG_TAGS ("</span> << tags
<< <span style="color:#a31515">")"</span>;
}
}</code></span></span>
当需要输出日志时,KernelLogger函数就会被调用:
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#2b91af">#if defined(__linux__)</span>
<span style="color:#0000ff">void</span> <span style="color:#a31515">KernelLogger</span>(android::base::LogId, android::base::LogSeverity severity,
<span style="color:#0000ff">const</span> <span style="color:#0000ff">char</span>* tag, <span style="color:#0000ff">const</span> <span style="color:#0000ff">char</span>*, <span style="color:#0000ff">unsigned</span> <span style="color:#0000ff">int</span>, <span style="color:#0000ff">const</span> <span style="color:#0000ff">char</span>* msg) {
... ...
<span style="color:#008000">// 打开log节点</span>
<span style="color:#0000ff">static</span> <span style="color:#0000ff">int</span> klog_fd = TEMP_FAILURE_RETRY(open(<span style="color:#a31515">"/dev/kmsg"</span>, O_WRONLY | O_CLOEXEC));
<span style="color:#0000ff">if</span> (klog_fd == -1) <span style="color:#0000ff">return</span>;
<span style="color:#008000">// 决定log等级</span>
<span style="color:#0000ff">int</span> level = kLogSeverityToKernelLogLevel[severity];
<span style="color:#008000">// The kernel's printk buffer is only 1024 bytes.</span>
<span style="color:#008000">// <span style="color:#808080">TODO:</span> should we automatically break up long lines into multiple lines?</span>
<span style="color:#008000">// Or we could log but with something like "..." at the end?</span>
<span style="color:#0000ff">char</span> buf[1024];
<span style="color:#0000ff">size_t</span> size = <span style="color:#0000ff">snprintf</span>(buf, <span style="color:#0000ff">sizeof</span>(buf), <span style="color:#a31515">"<%d>%s: %s\n"</span>, level, tag, msg);
<span style="color:#0000ff">if</span> (size > <span style="color:#0000ff">sizeof</span>(buf)) {
size = <span style="color:#0000ff">snprintf</span>(buf, <span style="color:#0000ff">sizeof</span>(buf), <span style="color:#a31515">"<%d>%s: %zu-byte message too long for printk\n"</span>,
level, tag, size);
}
iovec iov[1];
iov[0].iov_base = buf;
iov[0].iov_len = size;
<span style="color:#008000">// 通过iovec将log发送到dev/kmsg</span>
TEMP_FAILURE_RETRY(writev(klog_fd, iov, 1));
}
<span style="color:#2b91af">#endif</span></code></span></span>
挂在一些分区设备
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#0000ff">if</span> (is_first_stage) {
... ...
<span style="color:#008000">// 挂载特定的分区设备</span>
<span style="color:#0000ff">if</span> (!DoFirstStageMount()) {
LOG(ERROR) << <span style="color:#a31515">"Failed to mount required partitions early ..."</span>;
panic(); <span style="color:#008000">// panic会尝试reboot</span>
}
}
... ...</code></span></span>
跟踪DoFirstStageMount():system/core/init/init_first_stage.cpp
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#008000">// Mounts partitions specified by fstab in device tree.</span>
<span style="color:#0000ff">bool</span> <span style="color:#a31515">DoFirstStageMount</span>() {
<span style="color:#008000">// Skips first stage mount if we're in recovery mode.</span>
<span style="color:#0000ff">if</span> (IsRecoveryMode()) {
LOG(INFO) << <span style="color:#a31515">"First stage mount skipped (recovery mode)"</span>;
<span style="color:#0000ff">return</span> <span style="color:#a31515">true</span>;
}
<span style="color:#008000">// Firstly checks if device tree fstab entries are compatible.</span>
<span style="color:#0000ff">if</span> (!is_android_dt_value_expected(<span style="color:#a31515">"fstab/compatible"</span>, <span style="color:#a31515">"android,fstab"</span>)) {
LOG(INFO) << <span style="color:#a31515">"First stage mount skipped (missing/incompatible fstab in device tree)"</span>;
<span style="color:#0000ff">return</span> <span style="color:#a31515">true</span>;
}
<span style="color:#008000">// 满足上述条件时,就会调用FirstStageMount的DoFirstStageMount函数</span>
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">unique_ptr</span><FirstStageMount> handle = FirstStageMount::Create();
<span style="color:#0000ff">if</span> (!handle) {
LOG(ERROR) << <span style="color:#a31515">"Failed to create FirstStageMount"</span>;
<span style="color:#0000ff">return</span> <span style="color:#a31515">false</span>;
}
<span style="color:#008000">// 主要是初始化特定设备并挂载</span>
<span style="color:#0000ff">return</span> handle->DoFirstStageMount();
}</code></span></span>
完成SELinux相关工作
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#0000ff">if</span> (is_first_stage) {
... ...
<span style="color:#008000">// 此处应该是初始化安全框架:Android Verified Boot</span>
<span style="color:#008000">// AVB主要用于防止系统文件本身被篡改,还包含了防止系统回滚的功能,</span>
<span style="color:#008000">// 以免有人试图回滚系统并利用以前的漏洞</span>
SetInitAvbVersionInRecovery();
<span style="color:#008000">// Set up SELinux, loading the SELinux policy. </span>
selinux_initialize(<span style="color:#a31515">true</span>); <span style="color:#008000">// 调用selinux_initialize启动SELinux</span>
... ...
}
... ...</code></span></span>
跟踪selinux_initialize():
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">selinux_initialize</span>(<span style="color:#0000ff">bool</span> in_kernel_domain) {
Timer t;
selinux_callback cb;
cb.func_log = selinux_klog_callback; <span style="color:#008000">// 用于打印log的回调函数</span>
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback; <span style="color:#008000">// 用于检查权限的回调函数</span>
selinux_set_callback(SELINUX_CB_AUDIT, cb);
<span style="color:#008000">// init进程的运行是区分用户态和内核态的,first_stage运行在内核态</span>
<span style="color:#0000ff">if</span> (in_kernel_domain) {
LOG(INFO) << <span style="color:#a31515">"Loading SELinux policy"</span>;
<span style="color:#008000">// 用于加载sepolicy文件</span>
<span style="color:#008000">// 该函数最终将sepolicy文件传递给kernel,这样kernel就有了安全策略配置文件,后续的MAC才能开展起来。</span>
<span style="color:#0000ff">if</span> (!selinux_load_policy()) {
panic();
}
<span style="color:#0000ff">bool</span> kernel_enforcing = (security_getenforce() == 1); <span style="color:#008000">// 内核中读取的信息</span>
<span style="color:#0000ff">bool</span> is_enforcing = selinux_is_enforcing(); <span style="color:#008000">// 命令行中得到的数据</span>
<span style="color:#008000">// 用于设置selinux的工作模式。selinux有两种工作模式:</span>
<span style="color:#008000">// 1、”permissive”,所有的操作都被允许(即没有MAC),但是如果违反权限的话,会记录日志</span>
<span style="color:#008000">// 2、”enforcing”,所有操作都会进行权限检查。在一般的终端中,应该工作于enforing模式</span>
<span style="color:#0000ff">if</span> (kernel_enforcing != is_enforcing) {
<span style="color:#0000ff">if</span> (security_setenforce(is_enforcing)) {
PLOG(ERROR) << <span style="color:#a31515">"security_setenforce(%s) failed"</span> << (is_enforcing ? <span style="color:#a31515">"true"</span> : <span style="color:#a31515">"false"</span>);
security_failure(); <span style="color:#008000">// 将重启进入recovery mode</span>
}
}
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> err;
<span style="color:#0000ff">if</span> (!WriteFile(<span style="color:#a31515">"/sys/fs/selinux/checkreqprot"</span>, <span style="color:#a31515">"0"</span>, &err)) {
LOG(ERROR) << err;
security_failure();
}
<span style="color:#008000">// init's first stage can't set properties, so pass the time to the second stage.</span>
setenv(<span style="color:#a31515">"INIT_SELINUX_TOOK"</span>, <span style="color:#0000ff">std</span>::to_string(t.duration().count()).c_str(), 1);
} <span style="color:#0000ff">else</span> {
selinux_init_all_handles(); <span style="color:#008000">// 在second stage调用时,初始化所有的handle</span>
}
}</code></span></span>
is_first_stage 收尾
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#0000ff">if</span> (is_first_stage) {
... ...
<span style="color:#008000">// We're in the kernel domain, so re-exec init to transition to the init domain now</span>
<span style="color:#008000">// that the SELinux policy has been loaded.</span>
<span style="color:#0000ff">if</span> (selinux_android_restorecon(<span style="color:#a31515">"/init"</span>, 0) == -1) { <span style="color:#008000">// 按selinux policy要求,重新设置init文件属性</span>
PLOG(ERROR) << <span style="color:#a31515">"restorecon failed"</span>;
security_failure(); <span style="color:#008000">// 失败的话会reboot</span>
}
<span style="color:#0000ff">static</span> <span style="color:#0000ff">constexpr</span> <span style="color:#0000ff">uint32_t</span> kNanosecondsPerMillisecond = 1e6;
<span style="color:#0000ff">uint64_t</span> start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
setenv(<span style="color:#a31515">"INIT_STARTED_AT"</span>, <span style="color:#0000ff">std</span>::to_string(start_ms).c_str(), 1); <span style="color:#008000">// 记录初始化时的时间</span>
<span style="color:#0000ff">char</span>* path = argv[0];
<span style="color:#0000ff">char</span>* args[] = { path, <span style="color:#a31515">nullptr</span> };
execv(path, args); <span style="color:#008000">// 再次调用init的main函数,启动用户态的init进程</span>
<span style="color:#008000">// execv() only returns if an error happened, in which case we</span>
<span style="color:#008000">// panic and never fall through this conditional.</span>
PLOG(ERROR) << <span style="color:#a31515">"execv(\""</span> << path << <span style="color:#a31515">"\") failed"</span>;
security_failure(); <span style="color:#008000">// 内核态的进程不应该退出,若退出则会重启</span>
}
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
... ...
}</code></span></span>
上面所有的源码我们都是围绕第一阶段分析(is_first_stage),自此第一阶段结束,会复位一些信息,并设置一些环境变量,最后启动用户态的init进程,进入init第二阶段。
第二阶段(用户态)
init进程的第二阶段仍然从main函数开始入手(继续分析main函数剩余源码)
初始化属性域
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">// 同样进行一些判断及环境变量设置的工作</span>
... ...
<span style="color:#008000">// 现在 is_first_stage 为 false 了</span>
<span style="color:#0000ff">bool</span> is_first_stage = (getenv(<span style="color:#a31515">"INIT_SECOND_STAGE"</span>) == <span style="color:#a31515">nullptr</span>);
<span style="color:#008000">// 这部分工作不再执行了</span>
<span style="color:#0000ff">if</span> (is_first_stage) {
...........
}
<span style="color:#008000">// At this point we're in the second stage of init.</span>
InitKernelLogging(argv); <span style="color:#008000">// 同样屏蔽标准输入输出及定义Kernel logger</span>
LOG(INFO) << <span style="color:#a31515">"init second stage started!"</span>;
<span style="color:#008000">// Set up a session keyring that all processes will have access to. It</span>
<span style="color:#008000">// will hold things like FBE encryption keys. No process should override</span>
<span style="color:#008000">// its session keyring.</span>
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1); <span style="color:#008000">// 最后调用syscall,设置安全相关的值</span>
<span style="color:#008000">// Indicate that booting is in progress to background fw loaders, etc.</span>
close(open(<span style="color:#a31515">"/dev/.booting"</span>, O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); <span style="color:#008000">// 这里的功能类似于“锁”</span>
... ...
property_init(); <span style="color:#008000">// 初始化属性域 --> 定义于system/core/init/property_service.cpp</span>
<span style="color:#008000">// If arguments are passed both on the command line and in DT,</span>
<span style="color:#008000">// properties set in DT always have priority over the command-line ones.</span>
process_kernel_dt();
process_kernel_cmdline(); <span style="color:#008000">// 处理内核命令</span>
<span style="color:#008000">// Propagate the kernel variables to internal variables</span>
<span style="color:#008000">// used by init as well as the current required properties.</span>
export_kernel_boot_props();
<span style="color:#008000">// Make the time that init started available for bootstat to log.</span>
property_set(<span style="color:#a31515">"ro.boottime.init"</span>, getenv(<span style="color:#a31515">"INIT_STARTED_AT"</span>));
property_set(<span style="color:#a31515">"ro.boottime.init.selinux"</span>, getenv(<span style="color:#a31515">"INIT_SELINUX_TOOK"</span>));
<span style="color:#008000">// Set libavb version for Framework-only OTA match in Treble build.</span>
<span style="color:#0000ff">const</span> <span style="color:#0000ff">char</span>* avb_version = getenv(<span style="color:#a31515">"INIT_AVB_VERSION"</span>);
<span style="color:#0000ff">if</span> (avb_version) property_set(<span style="color:#a31515">"ro.boot.avb_version"</span>, avb_version);
... ...
}</code></span></span>
这部分代码主要的工作应该就是调用 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);
}
}
清空环境变量
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">// Clean up our environment.</span>
unsetenv(<span style="color:#a31515">"INIT_SECOND_STAGE"</span>);
unsetenv(<span style="color:#a31515">"INIT_STARTED_AT"</span>); <span style="color:#008000">// 清除掉之前使用过的环境变量</span>
unsetenv(<span style="color:#a31515">"INIT_SELINUX_TOOK"</span>);
unsetenv(<span style="color:#a31515">"INIT_AVB_VERSION"</span>);
... ...
}</code></span></span>
完成SELinux相关工作
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">// Now set up SELinux for second stage.</span>
selinux_initialize(<span style="color:#a31515">false</span>);
selinux_restore_context(); <span style="color:#008000">// 再次完成selinux相关的工作</span></code></span></span>
我们发现在init进程的第一阶段,也调用了selinux_initialize函数,那么两者有什么区别?
init进程第一阶段主要加载selinux相关的策略,而第二阶段调用selinux_initialize仅仅注册一些处理器。
我们跟下selinux_initialize():
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">selinux_initialize</span>(<span style="color:#0000ff">bool</span> 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);
<span style="color:#0000ff">if</span> (in_kernel_domain) {
... ... <span style="color:#008000">// 这边就是第一阶段的工作</span>
} <span style="color:#0000ff">else</span> {
selinux_init_all_handles(); <span style="color:#008000">// 这边就是第二阶段的工作:注册处理器</span>
}
}</code></span></span>
再来看一下selinux_restore_context():主要是按 selinux policy 要求,重新设置一些文件的属性。
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#008000">// The files and directories that were created before initial sepolicy load or</span>
<span style="color:#008000">// files on ramdisk need to have their security context restored to the proper</span>
<span style="color:#008000">// value. This must happen before /dev is populated by ueventd.</span>
<span style="color:#008000">// 如注释所述,以下文件在selinux被加载前就创建了</span>
<span style="color:#008000">// 于是,在selinux启动后,需要重新设置一些属性</span>
<span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">selinux_restore_context</span>() {
LOG(INFO) << <span style="color:#a31515">"Running restorecon..."</span>;
selinux_android_restorecon(<span style="color:#a31515">"/dev"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/dev/kmsg"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/dev/socket"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/dev/random"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/dev/urandom"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/dev/__properties__"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/plat_file_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/nonplat_file_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/plat_property_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/nonplat_property_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/plat_seapp_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/nonplat_seapp_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/plat_service_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/nonplat_service_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/plat_hwservice_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/nonplat_hwservice_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/sepolicy"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/vndservice_contexts"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/dev/block"</span>, SELINUX_ANDROID_RESTORECON_RECURSE);
selinux_android_restorecon(<span style="color:#a31515">"/dev/device-mapper"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/sbin/mke2fs_static"</span>, 0);
selinux_android_restorecon(<span style="color:#a31515">"/sbin/e2fsdroid_static"</span>, 0);
}</code></span></span>
创建epoll句柄
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">/* 03. 完成SELinux相关工作 */</span>
epoll_fd = epoll_create1(EPOLL_CLOEXEC); <span style="color:#008000">// 调用epoll_create1创建epoll句柄</span>
<span style="color:#0000ff">if</span> (epoll_fd == -1) {
PLOG(ERROR) << <span style="color:#a31515">"epoll_create1 failed"</span>;
<span style="color:#0000ff">exit</span>(1);
}
... ...
}</code></span></span>
在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具有“执行后关闭”特性。
装载子进程信号处理器
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">/* 03. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 04. 创建epoll句柄 */</span>
signal_handler_init(); <span style="color:#008000">// 装载子进程信号处理器</span>
}</code></span></span>
init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。
在linux当中,父进程是通过捕捉 SIGCHLD 信号来得知子进程运行结束的情况,此处init进程调用 signal_handler_init 的目的就是捕获子进程结束的信号。
我们跟踪下signal_handler_init():
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">void</span> <span style="color:#a31515">signal_handler_init</span>() {
<span style="color:#008000">// Create a signalling mechanism for SIGCHLD.</span>
<span style="color:#0000ff">int</span> s[2];
<span style="color:#008000">// 利用socketpair创建出已经连接的两个socket,分别作为信号的读、写端</span>
<span style="color:#0000ff">if</span> (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) {
PLOG(ERROR) << <span style="color:#a31515">"socketpair failed"</span>;
<span style="color:#0000ff">exit</span>(1);
}
signal_write_fd = s[0];
signal_read_fd = s[1];
<span style="color:#008000">// Write to signal_write_fd if we catch SIGCHLD.</span>
<span style="color:#0000ff">struct</span> <span style="color:#a31515">sigaction</span> <span style="color:#a31515">act</span>;
<span style="color:#0000ff">memset</span>(&act, 0, <span style="color:#0000ff">sizeof</span>(act));
<span style="color:#008000">// 信号处理器对应的执行函数为SIGCHLD_handler</span>
<span style="color:#008000">// 被存在sigaction结构体中,负责处理SIGCHLD消息</span>
act.sa_handler = SIGCHLD_handler;
act.sa_flags = SA_NOCLDSTOP;
<span style="color:#008000">// 调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中</span>
sigaction(SIGCHLD, &act, 0);
<span style="color:#008000">// 用于终止出现问题的子进程</span>
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
<span style="color:#008000">// 注册信号处理函数handle_signal</span>
register_epoll_handler(signal_read_fd, handle_signal);
}</code></span></span>
在深入分析代码前,我们需要了解一些基本概念:Linux进程通过互相发送消息来实现进程间的通信,这些消息被称为“信号”。每个进程在处理其它进程发送的信号时都要注册处理者,处理者被称为信号处理器。
注意到sigaction结构体的sa_flags为SA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位表示仅当进程终止时才接受SIGCHLD信号。
接下来,我们分步骤详细了解一下signal_handler_init具体的工作流程。
SIGCHLD_handler
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#008000">// system/core/init/signal_handler.cpp</span>
<span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">SIGCHLD_handler</span>(<span style="color:#0000ff">int</span>) {
<span style="color:#0000ff">if</span> (TEMP_FAILURE_RETRY(write(signal_write_fd, <span style="color:#a31515">"1"</span>, 1)) == -1) {
PLOG(ERROR) << <span style="color:#a31515">"write(signal_write_fd) failed"</span>;
}
}</code></span></span>
从上面代码我们知道,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:
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#008000">// system/core/init/init.cpp</span>
<span style="color:#0000ff">void</span> <span style="color:#a31515">register_epoll_handler</span>(<span style="color:#0000ff">int</span> fd, <span style="color:#0000ff">void</span> (*fn)()) {
epoll_event ev;
ev.events = EPOLLIN;
ev.data.ptr = <span style="color:#0000ff">reinterpret_cast</span><<span style="color:#0000ff">void</span>*>(fn);
<span style="color:#008000">// epoll_fd增加一个监听对象fd,fd上有数据到来时,调用fn处理</span>
<span style="color:#0000ff">if</span> (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
PLOG(ERROR) << <span style="color:#a31515">"epoll_ctl failed"</span>;
}
}</code></span></span>
根据代码不难看出:当epoll句柄监听到signal_read_fd中有数据可读时,将调用handle_signal进行处理。
至此,结合上文我们知道:当init进程调用signal_handler_init后,一旦收到子进程终止带来的SIGCHLD消息后,将利用信号处理者SIGCHLD_handler向signal_write_fd写入信息;由于绑定的关系,epoll句柄将监听到signal_read_fd收到消息,于是将调用handle_signal进行处理。
handle_signal
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#008000">// system/core/init/signal_handler.cpp</span>
<span style="color:#0000ff">static</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">handle_signal</span>() {
<span style="color:#008000">// Clear outstanding requests.</span>
<span style="color:#0000ff">char</span> buf[32];
read(signal_read_fd, buf, <span style="color:#0000ff">sizeof</span>(buf));
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}</code></span></span>
从代码中可以看出,handle_signal只是清空signal_read_fd中的数据,然后调用ServiceManager::GetInstance().ReapAnyOutstandingChildren()。
ServiceManager定义于 system/core/init/service.cpp 中,是一个单例对象:
<span style="color:#7d8b8d"><span style="color:#333333"><code>... ...
ServiceManager::ServiceManager() {
}
ServiceManager& ServiceManager::GetInstance() {
<span style="color:#0000ff">static</span> ServiceManager instance;
<span style="color:#0000ff">return</span> instance;
}
... ...
<span style="color:#0000ff">void</span> ServiceManager::ReapAnyOutstandingChildren() {
<span style="color:#0000ff">while</span> (ReapOneProcess()) {
}
}
... ...
</code></span></span>
如上所示,ReapAnyOutstandingChildren函数实际上调用了ReapOneProcess。我们结合代码,看看ReapOneProcess的具体工作。
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">bool</span> ServiceManager::ReapOneProcess() {
<span style="color:#0000ff">siginfo_t</span> siginfo = {};
<span style="color:#008000">// This returns a zombie pid or informs us that there are no zombies left to be reaped.</span>
<span style="color:#008000">// It does NOT reap the pid; that is done below.</span>
<span style="color:#008000">//用waitid函数获取状态发生变化的子进程pid</span>
<span style="color:#008000">//waitid的标记为WNOHANG,即非阻塞,返回为正值就说明有进程挂掉了</span>
<span style="color:#0000ff">if</span> (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
PLOG(ERROR) << <span style="color:#a31515">"waitid failed"</span>;
<span style="color:#0000ff">return</span> <span style="color:#a31515">false</span>;
}
<span style="color:#0000ff">auto</span> pid = siginfo.si_pid;
<span style="color:#0000ff">if</span> (pid == 0) <span style="color:#0000ff">return</span> <span style="color:#a31515">false</span>;
<span style="color:#008000">// At this point we know we have a zombie pid, so we use this scopeguard to reap the pid</span>
<span style="color:#008000">// whenever the function returns from this point forward.</span>
<span style="color:#008000">// We do NOT want to reap the zombie earlier as in Service::Reap(), we kill(-pid, ...) and we</span>
<span style="color:#008000">// want the pid to remain valid throughout that (and potentially future) usages.</span>
<span style="color:#0000ff">auto</span> reaper = make_scope_guard([pid] { TEMP_FAILURE_RETRY(waitpid(pid, <span style="color:#a31515">nullptr</span>, WNOHANG)); });
<span style="color:#0000ff">if</span> (PropertyChildReap(pid)) {
<span style="color:#0000ff">return</span> <span style="color:#a31515">true</span>;
}
<span style="color:#008000">// 利用FindServiceByPid函数,找到pid对应的服务</span>
<span style="color:#008000">// FindServiceByPid主要通过轮询解析init.rc生成的service_list,找到pid与参数一致的srvc</span>
Service* svc = FindServiceByPid(pid);
... ... <span style="color:#008000">// 输出服务结束的原因</span>
<span style="color:#0000ff">if</span> (!svc) { <span style="color:#008000">// 没有找到,说明已经结束了</span>
<span style="color:#0000ff">return</span> <span style="color:#a31515">true</span>;
}
svc->Reap();
<span style="color:#008000">// 根据svc的类型,决定后续的处理方式</span>
<span style="color:#0000ff">if</span> (svc->flags() & SVC_EXEC) {
exec_waiter_.reset(); <span style="color:#008000">// 可执行服务则重置对应的waiter</span>
}
<span style="color:#0000ff">if</span> (svc->flags() & SVC_TEMPORARY) {
RemoveService(*svc); <span style="color:#008000">// 移除临时服务</span>
}
<span style="color:#0000ff">return</span> <span style="color:#a31515">true</span>;
}</code></span></span>
Reap
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">void</span> Service::Reap() {
<span style="color:#008000">// 清理未携带SVC_ONESHOT 或 携带了SVC_RESTART标志的srvc的进程组</span>
<span style="color:#0000ff">if</span> (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
KillProcessGroup(SIGKILL);
}
<span style="color:#008000">// Remove any descriptor resources we may have created.</span>
<span style="color:#008000">// 清除srvc中创建出的任意描述符</span>
<span style="color:#0000ff">std</span>::for_each(descriptors_.begin(), descriptors_.end(),
<span style="color:#0000ff">std</span>::bind(&DescriptorInfo::Clean, <span style="color:#0000ff">std</span>::placeholders::_1));
<span style="color:#008000">// 清理工作完毕后,后面决定是否重启机器或重启服务</span>
<span style="color:#008000">// TEMP服务不用参与这种判断</span>
<span style="color:#0000ff">if</span> (flags_ & SVC_TEMPORARY) {
<span style="color:#0000ff">return</span>;
}
pid_ = 0;
flags_ &= (~SVC_RUNNING);
<span style="color:#008000">// Oneshot processes go into the disabled state on exit,</span>
<span style="color:#008000">// except when manually restarted.</span>
<span style="color:#008000">// 对于携带了SVC_ONESHOT并且未携带SVC_RESTART的srvc,将这类服务的标志置为SVC_DISABLED,不再自启动</span>
<span style="color:#0000ff">if</span> ((flags_ & SVC_ONESHOT) && !(flags_ & SVC_RESTART)) {
flags_ |= SVC_DISABLED;
}
<span style="color:#008000">// Disabled and reset processes do not get restarted automatically.</span>
<span style="color:#0000ff">if</span> (flags_ & (SVC_DISABLED | SVC_RESET)) {
NotifyStateChange(<span style="color:#a31515">"stopped"</span>);
<span style="color:#0000ff">return</span>;
}
<span style="color:#008000">// If we crash > 4 times in 4 minutes, reboot into recovery.</span>
boot_clock::time_point now = boot_clock::now();
<span style="color:#008000">// 未携带SVC_RESTART的关键服务,在规定的间隔内,crash字数过多时,会导致整机重启;</span>
<span style="color:#0000ff">if</span> ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
<span style="color:#0000ff">if</span> (now < time_crashed_ + 4min) {
<span style="color:#0000ff">if</span> (++crash_count_ > 4) {
LOG(ERROR) << <span style="color:#a31515">"critical process '"</span> << name_ << <span style="color:#a31515">"' exited 4 times in 4 minutes"</span>;
panic();
}
} <span style="color:#0000ff">else</span> {
time_crashed_ = now;
crash_count_ = 1;
}
}
<span style="color:#008000">// 将待重启srvc的标志位置为SVC_RESTARTING(init进程将根据该标志位,重启服务)</span>
flags_ &= (~SVC_RESTART);
flags_ |= SVC_RESTARTING;
<span style="color:#008000">// Execute all onrestart commands for this service.</span>
<span style="color:#008000">// 重启在init.rc文件中带有onrestart选项的服务</span>
onrestart_.ExecuteAllCommands();
NotifyStateChange(<span style="color:#a31515">"restarting"</span>);
<span style="color:#0000ff">return</span>;
}</code></span></span>
不难看出,Reap函数的主要作用就是清除问题进程相关的资源,然后根据进程对应的类型,决定是否重启机器或重启进程。
ExecuteAllCommands
我们在这一部分的最后,看看定义于system/core/init/Action.cpp中的ExecuteAllCommands函数:
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">void</span> Action::ExecuteAllCommands() <span style="color:#0000ff">const</span> {
<span style="color:#0000ff">for</span> (<span style="color:#0000ff">const</span> <span style="color:#0000ff">auto</span>& c : commands_) {
ExecuteCommand(c);
}
}
<span style="color:#0000ff">void</span> Action::ExecuteCommand(<span style="color:#0000ff">const</span> Command& command) <span style="color:#0000ff">const</span> {
android::base::Timer t;
<span style="color:#008000">// 进程重启时,将执行对应的函数</span>
<span style="color:#0000ff">int</span> result = command.InvokeFunc();
<span style="color:#008000">// 打印log</span>
<span style="color:#0000ff">auto</span> duration = t.duration();
<span style="color:#008000">// Any action longer than 50ms will be warned to user as slow operation</span>
<span style="color:#0000ff">if</span> (duration > 50ms || android::base::GetMinimumLogSeverity() <= android::base::DEBUG) {
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> trigger_name = BuildTriggersString();
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> cmd_str = command.BuildCommandString();
LOG(INFO) << <span style="color:#a31515">"Command '"</span> << cmd_str << <span style="color:#a31515">"' action="</span> << trigger_name << <span style="color:#a31515">" ("</span> << filename_
<< <span style="color:#a31515">":"</span> << command.line() << <span style="color:#a31515">") returned "</span> << result << <span style="color:#a31515">" took "</span>
<< duration.count() << <span style="color:#a31515">"ms."</span>;
}
}</code></span></span>
整个signal_handler_init的内容比较多,在此总结一下:signal_handler_init的本质就是监听子进程死亡的信息,然后进行对应的清理工作,并根据死亡进程的类型,决定是否需要重启进程或机器。
启动属性服务
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">/* 03. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 04. 创建epoll句柄 */</span>
<span style="color:#008000">/* 05. 装载子进程信号处理器 */</span>
property_load_boot_defaults(); <span style="color:#008000">// 进程调用property_load_boot_defaults进行默认属性配置相关的工作</span>
export_oem_lock_status(); <span style="color:#008000">// 最终就是决定"ro.boot.flash.locked"的值</span>
start_property_service(); <span style="color:#008000">// 启动属性服务</span>
set_usb_controller();
... ...
}</code></span></span>
老样子,这边我们跟踪几个重要的函数。
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
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">void</span> <span style="color:#a31515">start_property_service</span>() {
property_set(<span style="color:#a31515">"ro.property_service.version"</span>, <span style="color:#a31515">"2"</span>);
<span style="color:#008000">// 创建了一个非阻塞socket</span>
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
<span style="color:#a31515">false</span>, 0666, 0, 0, <span style="color:#a31515">nullptr</span>, sehandle);
<span style="color:#0000ff">if</span> (property_set_fd == -1) {
PLOG(ERROR) << <span style="color:#a31515">"start_property_service socket creation failed"</span>;
<span style="color:#0000ff">exit</span>(1);
}
<span style="color:#008000">// 调用listen函数监听property_set_fd, 于是该socket变成一个server</span>
listen(property_set_fd, 8);
<span style="color:#008000">// 监听server socket上是否有数据到来</span>
register_epoll_handler(property_set_fd, handle_property_set_fd);
}</code></span></span>
init进程在共享内存区域中,创建并初始化属性域。其它进程可以访问属性域中的值,但更改属性值仅能在init进程中进行。这就是init进程调用start_property_service的原因。
其它进程修改属性值时,要预先向init进程提交值变更申请,然后init进程处理该申请,并修改属性值。在访问和修改属性时,init进程都可以进行权限控制。
匹配命令和函数之间对应关系
<span style="color:#7d8b8d"><span style="color:#333333"><code> <span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">/* 03. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 04. 创建epoll句柄 */</span>
<span style="color:#008000">/* 05. 装载子进程信号处理器 */</span>
<span style="color:#008000">/* 06. 启动属性服务*/</span>
<span style="color:#0000ff">const</span> BuiltinFunctionMap function_map; <span style="color:#008000">// system/core/init/builtins.cpp,定义Action中的function_map_为BuiltinFuntionMap</span>
Action::set_function_map(&function_map); <span style="color:#008000">// 在Action中保存function_map对象,记录了命令与函数之间的对应关系</span>
<span style="color:#008000">/* 07. 匹配命令和函数之间对应关系 */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ END ------------ */</span>
... ...
}</code></span></span>
至此,init进程的准备工作执行完毕, 接下来就要开始解析init.rc文件了。
第三阶段(init.rc)
解析init.rc
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">/* 03. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 04. 创建epoll句柄 */</span>
<span style="color:#008000">/* 05. 装载子进程信号处理器 */</span>
<span style="color:#008000">/* 06. 启动属性服务*/</span>
<span style="color:#008000">/* 07. 匹配命令和函数之间对应关系 */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ END ------------ */</span>
<span style="color:#008000">/* ------------ 第三阶段 ----------- BEGIN------------ */</span>
ActionManager& am = ActionManager::GetInstance();
ServiceManager& sm = ServiceManager::GetInstance();
Parser& parser = Parser::GetInstance(); <span style="color:#008000">// 构造解析文件用的parser对象</span>
<span style="color:#008000">// 为一些类型的关键字,创建特定的parser</span>
parser.AddSectionParser(<span style="color:#a31515">"service"</span>, <span style="color:#0000ff">std</span>::make_unique<ServiceParser>(&sm)); <span style="color:#008000">// 增加ServiceParser为一个section,对应name为service</span>
parser.AddSectionParser(<span style="color:#a31515">"on"</span>, <span style="color:#0000ff">std</span>::make_unique<ActionParser>(&am)); <span style="color:#008000">// 增加ActionParser为一个section,对应name为action</span>
parser.AddSectionParser(<span style="color:#a31515">"import"</span>, <span style="color:#0000ff">std</span>::make_unique<ImportParser>(&parser)); <span style="color:#008000">// 增加ActionParser为一个section,对应name为import</span>
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> bootscript = GetProperty(<span style="color:#a31515">"ro.boot.init_rc"</span>, <span style="color:#a31515">""</span>); <span style="color:#008000">// 判断是否存在bootscript</span>
<span style="color:#008000">// 如果没有bootscript,则解析init.rc文件</span>
<span style="color:#0000ff">if</span> (bootscript.empty()) { <span style="color:#008000">// 8.0引入</span>
parser.ParseConfig(<span style="color:#a31515">"/init.rc"</span>); <span style="color:#008000">// 开始实际的解析过程</span>
parser.set_is_system_etc_init_loaded(
parser.ParseConfig(<span style="color:#a31515">"/system/etc/init"</span>));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig(<span style="color:#a31515">"/vendor/etc/init"</span>));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig(<span style="color:#a31515">"/odm/etc/init"</span>));
} <span style="color:#0000ff">else</span> {
<span style="color:#008000">// 若存在bootscript, 则解析bootscript</span>
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(<span style="color:#a31515">true</span>);
parser.set_is_vendor_etc_init_loaded(<span style="color:#a31515">true</span>);
parser.set_is_odm_etc_init_loaded(<span style="color:#a31515">true</span>);
}
... ...
}</code></span></span>
如果没有定义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 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 zygote stream 660 root system # socket关键字表示一个option,创建一个名为dev/socket/zygote,类型为stream,权限为660的socket
onrestart write /sys/android_power/request_state wake # onrestart是一个option,说明在zygote重启时需要执行的command
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的代码:
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
... ...
ActionManager& am = ActionManager::GetInstance();
ServiceManager& sm = ServiceManager::GetInstance();
Parser& parser = Parser::GetInstance(); <span style="color:#008000">// 构造解析文件用的parser对象</span>
parser.AddSectionParser(<span style="color:#a31515">"service"</span>, <span style="color:#0000ff">std</span>::make_unique<ServiceParser>(&sm)); <span style="color:#008000">// 增加ServiceParser为一个section,对应name为service</span>
parser.AddSectionParser(<span style="color:#a31515">"on"</span>, <span style="color:#0000ff">std</span>::make_unique<ActionParser>(&am)); <span style="color:#008000">// 增加ActionParser为一个section,对应name为action</span>
parser.AddSectionParser(<span style="color:#a31515">"import"</span>, <span style="color:#0000ff">std</span>::make_unique<ImportParser>(&parser)); <span style="color:#008000">// 增加ActionParser为一个section,对应name为import</span>
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> bootscript = GetProperty(<span style="color:#a31515">"ro.boot.init_rc"</span>, <span style="color:#a31515">""</span>);
<span style="color:#0000ff">if</span> (bootscript.empty()) {
parser.ParseConfig(<span style="color:#a31515">"/init.rc"</span>); <span style="color:#008000">// 开始实际的解析过程</span>
parser.set_is_system_etc_init_loaded(
parser.ParseConfig(<span style="color:#a31515">"/system/etc/init"</span>));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig(<span style="color:#a31515">"/vendor/etc/init"</span>));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig(<span style="color:#a31515">"/odm/etc/init"</span>));
} <span style="color:#0000ff">else</span> {
parser.ParseConfig(bootscript);
parser.set_is_system_etc_init_loaded(<span style="color:#a31515">true</span>);
parser.set_is_vendor_etc_init_loaded(<span style="color:#a31515">true</span>);
parser.set_is_odm_etc_init_loaded(<span style="color:#a31515">true</span>);
}
... ...
}</code></span></span>
Parse
我们发现在解析前,使用了Parser类(在init目录下的 init_parser.h 中定义),构造了parser对象:
<span style="color:#7d8b8d"><span style="color:#333333"><code> Parser& parser = Parser::GetInstance(); <span style="color:#008000">// 构造解析文件用的parser对象</span></code></span></span>
初始化ServiceParser用来解析 “service”块,ActionParser用来解析"on"块,ImportParser用来解析“import”块,“import”是用来引入一个init配置文件,来扩展当前配置的。
<span style="color:#7d8b8d"><span style="color:#333333"><code> parser.AddSectionParser(<span style="color:#a31515">"service"</span>, <span style="color:#0000ff">std</span>::make_unique<ServiceParser>(&sm)); <span style="color:#008000">// 增加ServiceParser为一个section,对应name为service</span>
parser.AddSectionParser(<span style="color:#a31515">"on"</span>, <span style="color:#0000ff">std</span>::make_unique<ActionParser>(&am)); <span style="color:#008000">// 增加ActionParser为一个section,对应name为action</span>
parser.AddSectionParser(<span style="color:#a31515">"import"</span>, <span style="color:#0000ff">std</span>::make_unique<ImportParser>(&parser)); <span style="color:#008000">// 增加ActionParser为一个section,对应name为import</span></code></span></span>
/system/core/init/readme.txt中对init文件中的所有关键字做了介绍,主要包含了Actions, Commands, Services, Options, and Imports等,可自行学习解读。
ParseConfig
下面就是分析解析过程了:parser.ParseConfig("/init.rc") (函数定义于 system/core/init/init_parser.cpp 中)
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">bool</span> Parser::ParseConfig(<span style="color:#0000ff">const</span> <span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>& path) {
<span style="color:#0000ff">if</span> (is_dir(path.c_str())) { <span style="color:#008000">// 判断传入参数是否为目录地址</span>
<span style="color:#0000ff">return</span> ParseConfigDir(path); <span style="color:#008000">// 递归目录,最终还是靠ParseConfigFile来解析实际的文件</span>
}
<span style="color:#0000ff">return</span> ParseConfigFile(path); <span style="color:#008000">// 传入参数为文件地址</span>
}
</code></span></span>
先来看看ParseConfigDir函数:
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">bool</span> Parser::ParseConfigDir(<span style="color:#0000ff">const</span> <span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>& path) {
LOG(INFO) << <span style="color:#a31515">"Parsing directory "</span> << path << <span style="color:#a31515">"..."</span>;
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">unique_ptr</span><DIR, <span style="color:#0000ff">int</span>(*)(DIR*)> config_dir(opendir(path.c_str()), closedir);
<span style="color:#0000ff">if</span> (!config_dir) {
PLOG(ERROR) << <span style="color:#a31515">"Could not import directory '"</span> << path << <span style="color:#a31515">"'"</span>;
<span style="color:#0000ff">return</span> <span style="color:#a31515">false</span>;
}
<span style="color:#008000">// 递归目录,得到需要处理的文件</span>
dirent* current_file;
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">vector</span><<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>> files;
<span style="color:#0000ff">while</span> ((current_file = readdir(config_dir.get()))) {
<span style="color:#008000">// Ignore directories and only process regular files.</span>
<span style="color:#0000ff">if</span> (current_file->d_type == DT_REG) {
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> current_path =
android::base::StringPrintf(<span style="color:#a31515">"%s/%s"</span>, path.c_str(), current_file->d_name);
files.emplace_back(current_path);
}
}
<span style="color:#008000">// Sort first so we load files in a consistent order (bug 31996208)</span>
<span style="color:#0000ff">std</span>::sort(files.begin(), files.end());
<span style="color:#0000ff">for</span> (<span style="color:#0000ff">const</span> <span style="color:#0000ff">auto</span>& file : files) {
<span style="color:#008000">// 容易看出,最终仍是调用ParseConfigFile</span>
<span style="color:#0000ff">if</span> (!ParseConfigFile(file)) {
LOG(ERROR) << <span style="color:#a31515">"could not import file '"</span> << file << <span style="color:#a31515">"'"</span>;
}
}
<span style="color:#0000ff">return</span> <span style="color:#a31515">true</span>;
}</code></span></span>
接下来就重点分析ParseConfigFile():
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">bool</span> Parser::ParseConfigFile(<span style="color:#0000ff">const</span> <span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>& path) {
... ...
android::base::Timer t;
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> data;
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> err;
<span style="color:#0000ff">if</span> (!ReadFile(path, &data, &err)) { <span style="color:#008000">// 读取路径指定文件中的内容,保存为字符串形式</span>
LOG(ERROR) << err;
<span style="color:#0000ff">return</span> <span style="color:#a31515">false</span>;
}
... ...
ParseData(path, data); <span style="color:#008000">// 解析获取的字符串</span>
... ...
<span style="color:#0000ff">return</span> <span style="color:#a31515">true</span>;
}</code></span></span>
ParseConfigFile只是读取文件的内容并转换为字符串,实际的解析工作被交付给ParseData。
ParseData
ParseData函数定义于system/core/init/init_parser.cpp中,负责根据关键字解析出服务和动作。动作与服务会以链表节点的形式注册到service_list与action_list中,service_list与action_list是init进程中声明的全局结构体。
跟踪ParseData():
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">void</span> Parser::ParseData(<span style="color:#0000ff">const</span> <span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>& filename, <span style="color:#0000ff">const</span> <span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>& data) {
<span style="color:#008000">//<span style="color:#808080">TODO:</span> Use a parser with const input and remove this copy</span>
<span style="color:#008000">// copy一波数据</span>
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">vector</span><<span style="color:#0000ff">char</span>> data_copy(data.begin(), data.end());
data_copy.push_back(<span style="color:#a31515">'\0'</span>);
<span style="color:#008000">// 解析用的结构体</span>
parse_state state;
state.line = 0;
state.ptr = &data_copy[0];
state.nexttoken = 0;
SectionParser* section_parser = <span style="color:#a31515">nullptr</span>;
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">vector</span><<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span>> args;
<span style="color:#0000ff">for</span> (;;) {
<span style="color:#0000ff">switch</span> (next_token(&state)) { <span style="color:#008000">// next_token以行为单位分割参数传递过来的字符串,初始没有分割符时,最先走到T_TEXT分支</span>
<span style="color:#0000ff">case</span> T_EOF:
<span style="color:#0000ff">if</span> (section_parser) {
section_parser->EndSection(); <span style="color:#008000">// 解析结束</span>
}
<span style="color:#0000ff">return</span>;
<span style="color:#0000ff">case</span> T_NEWLINE:
state.line++;
<span style="color:#0000ff">if</span> (args.empty()) {
<span style="color:#0000ff">break</span>;
}
... ...
<span style="color:#008000">// 在前文创建parser时,我们为service,on,import定义了对应的parser</span>
<span style="color:#008000">// 这里就是根据第一个参数,判断是否有对应的parser</span>
<span style="color:#0000ff">if</span> (section_parsers_.count(args[0])) {
<span style="color:#0000ff">if</span> (section_parser) {
<span style="color:#008000">// 结束上一个parser的工作,将构造出的对象加入到对应的service_list与action_list中</span>
section_parser->EndSection();
}
<span style="color:#008000">// 获取参数对应的parser</span>
section_parser = section_parsers_[args[0]].get();
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> ret_err;
<span style="color:#008000">// 调用实际parser的ParseSection函数</span>
<span style="color:#0000ff">if</span> (!section_parser->ParseSection(<span style="color:#0000ff">std</span>::move(args), filename, state.line, &ret_err)) {
LOG(ERROR) << filename << <span style="color:#a31515">": "</span> << state.line << <span style="color:#a31515">": "</span> << ret_err;
section_parser = <span style="color:#a31515">nullptr</span>;
}
} <span style="color:#0000ff">else</span> <span style="color:#0000ff">if</span> (section_parser) {
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> ret_err;
<span style="color:#008000">/*
* 如果第一个参数不是service,on,import
* 则调用前一个parser的ParseLineSection函数
* 这里相当于解析一个参数块的子项
*/</span>
<span style="color:#0000ff">if</span> (!section_parser->ParseLineSection(<span style="color:#0000ff">std</span>::move(args), state.line, &ret_err)) {
LOG(ERROR) << filename << <span style="color:#a31515">": "</span> << state.line << <span style="color:#a31515">": "</span> << ret_err;
}
}
<span style="color:#008000">// 清空本次解析的数据</span>
args.clear();
<span style="color:#0000ff">break</span>;
<span style="color:#0000ff">case</span> T_TEXT:
<span style="color:#008000">// 将本次解析的内容写入到args中</span>
args.emplace_back(state.text);
<span style="color:#0000ff">break</span>;
}
}
}</code></span></span>
上面的代码看起来比较复杂,但实际上就是面向对象,根据不同的关键字,使用不同的parser对象进行解析。
至此,init.rc解析完!Ok,别忘了,main函数还没有分析完,继续往下看。
第四阶段
向执行队列中添加其他action
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">/* 03. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 04. 创建epoll句柄 */</span>
<span style="color:#008000">/* 05. 装载子进程信号处理器 */</span>
<span style="color:#008000">/* 06. 启动属性服务*/</span>
<span style="color:#008000">/* 07. 匹配命令和函数之间对应关系 */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ END ------------ */</span>
<span style="color:#008000">/* ------------ 第三阶段 ----------- BEGIN------------ */</span>
<span style="color:#008000">/* init解析 */</span>
<span style="color:#008000">/* ------------ 第三阶段 ----------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第四阶段 ----------- BEGIN------------ */</span>
<span style="color:#008000">// 通过am对命令执行顺序进行控制</span>
<span style="color:#008000">// ActionManager& am = ActionManager::GetInstance();</span>
<span style="color:#008000">// init执行命令触发器主要分为early-init,init,late-init,boot等</span>
am.QueueEventTrigger(<span style="color:#a31515">"early-init"</span>); <span style="color:#008000">// 添加触发器early-init,执行on early-init内容</span>
<span style="color:#008000">// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...</span>
am.QueueBuiltinAction(wait_for_coldboot_done_action, <span style="color:#a31515">"wait_for_coldboot_done"</span>);
<span style="color:#008000">// ... so that we can start queuing up actions that require stuff from /dev.</span>
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, <span style="color:#a31515">"mix_hwrng_into_linux_rng"</span>);
am.QueueBuiltinAction(set_mmap_rnd_bits_action, <span style="color:#a31515">"set_mmap_rnd_bits"</span>);
am.QueueBuiltinAction(set_kptr_restrict_action, <span style="color:#a31515">"set_kptr_restrict"</span>);
am.QueueBuiltinAction(keychord_init_action, <span style="color:#a31515">"keychord_init"</span>);
am.QueueBuiltinAction(console_init_action, <span style="color:#a31515">"console_init"</span>);
<span style="color:#008000">// Trigger all the boot actions to get us started.</span>
am.QueueEventTrigger(<span style="color:#a31515">"init"</span>); <span style="color:#008000">// 添加触发器init,执行on init内容,主要包括创建/挂在一些目录,以及symlink等</span>
<span style="color:#008000">// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random</span>
<span style="color:#008000">// wasn't ready immediately after wait_for_coldboot_done</span>
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, <span style="color:#a31515">"mix_hwrng_into_linux_rng"</span>);
<span style="color:#008000">// Don't mount filesystems or start core system services in charger mode.</span>
<span style="color:#0000ff">std</span>::<span style="color:#0000ff">string</span> bootmode = GetProperty(<span style="color:#a31515">"ro.bootmode"</span>, <span style="color:#a31515">""</span>);
<span style="color:#0000ff">if</span> (bootmode == <span style="color:#a31515">"charger"</span>) {
am.QueueEventTrigger(<span style="color:#a31515">"charger"</span>); <span style="color:#008000">// on charger阶段</span>
} <span style="color:#0000ff">else</span> {
am.QueueEventTrigger(<span style="color:#a31515">"late-init"</span>); <span style="color:#008000">// 非充电模式添加触发器last-init</span>
}
<span style="color:#008000">// Run all property triggers based on current state of the properties.</span>
am.QueueBuiltinAction(queue_property_triggers_action, <span style="color:#a31515">"queue_property_triggers"</span>);
}</code></span></span>
其余工作
继续分析main函数:
<span style="color:#7d8b8d"><span style="color:#333333"><code><span style="color:#0000ff">int</span> <span style="color:#a31515">main</span>(<span style="color:#0000ff">int</span> argc, <span style="color:#0000ff">char</span>** argv) {
<span style="color:#008000">/* ------------ 第一阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 判断及增加环境变量 */</span>
<span style="color:#008000">/* 02. 创建文件系统目录并挂载相关的文件系统 */</span>
<span style="color:#008000">/* 03. 重定向输入输出/内核Log系统 */</span>
<span style="color:#008000">/* 04. 挂在一些分区设备 */</span>
<span style="color:#008000">/* 05. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 06. is_first_stage 收尾 */</span>
<span style="color:#008000">/* ------------ 第一阶段 ------------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ BEGIN------------ */</span>
<span style="color:#008000">/* 01. 初始化属性域 */</span>
<span style="color:#008000">/* 02. 清空环境变量 */</span>
<span style="color:#008000">/* 03. 完成SELinux相关工作 */</span>
<span style="color:#008000">/* 04. 创建epoll句柄 */</span>
<span style="color:#008000">/* 05. 装载子进程信号处理器 */</span>
<span style="color:#008000">/* 06. 启动属性服务*/</span>
<span style="color:#008000">/* 07. 匹配命令和函数之间对应关系 */</span>
<span style="color:#008000">/* ------------ 第二阶段 ------------ END ------------ */</span>
<span style="color:#008000">/* ------------ 第三阶段 ----------- BEGIN------------ */</span>
<span style="color:#008000">/* init解析 */</span>
<span style="color:#008000">/* ------------ 第三阶段 ----------- END ------------ */</span>
<span style="color:#008000">/* ------------ 第四阶段 ----------- BEGIN------------ */</span>
<span style="color:#008000">/* 01. 向执行队列中添加其他action */</span>
<span style="color:#008000">/* 02. 其余工作 */</span>
<span style="color:#0000ff">while</span> (<span style="color:#a31515">true</span>) {
<span style="color:#008000">// 判断是否有事件需要处理</span>
<span style="color:#008000">// By default, sleep until something happens.</span>
<span style="color:#0000ff">int</span> epoll_timeout_ms = -1;
<span style="color:#0000ff">if</span> (do_shutdown && !shutting_down) {
do_shutdown = <span style="color:#a31515">false</span>;
<span style="color:#0000ff">if</span> (HandlePowerctlMessage(shutdown_command)) {
shutting_down = <span style="color:#a31515">true</span>;
}
}
<span style="color:#0000ff">if</span> (!(waiting_for_prop || sm.IsWaitingForExec())) {
am.ExecuteOneCommand(); <span style="color:#008000">// 依次执行每个action中携带command对应的执行函数</span>
}
<span style="color:#0000ff">if</span> (!(waiting_for_prop || sm.IsWaitingForExec())) {
<span style="color:#0000ff">if</span> (!shutting_down) restart_processes(); <span style="color:#008000">// 重启一些挂掉的进程</span>
<span style="color:#008000">// If there's a process that needs restarting, wake up in time for that.</span>
<span style="color:#0000ff">if</span> (process_needs_restart_at != 0) { <span style="color:#008000">// 进程重启相关逻辑</span>
epoll_timeout_ms = (process_needs_restart_at - time(<span style="color:#a31515">nullptr</span>)) * 1000;
<span style="color:#0000ff">if</span> (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
}
<span style="color:#008000">// If there's more work to do, wake up again immediately.</span>
<span style="color:#0000ff">if</span> (am.HasMoreCommands()) epoll_timeout_ms = 0; <span style="color:#008000">// 有action待处理,不等待</span>
}
epoll_event ev;
<span style="color:#008000">// 没有事件到来的话,最多阻塞timeout时间</span>
<span style="color:#0000ff">int</span> nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
<span style="color:#0000ff">if</span> (nr == -1) {
PLOG(ERROR) << <span style="color:#a31515">"epoll_wait failed"</span>;
} <span style="color:#0000ff">else</span> <span style="color:#0000ff">if</span> (nr == 1) {
<span style="color:#008000">//有事件到来,执行对应处理函数</span>
<span style="color:#008000">//根据上文知道,epoll句柄(即epoll_fd)主要监听子进程结束,及其它进程设置系统属性的请求</span>
((<span style="color:#0000ff">void</span> (*)()) ev.data.ptr)();
}
}
} <span style="color:#008000">// end main</span></code></span></span>
至此,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/