源码详解Android 9.0(P) 系统启动流程目录:
源码详解Android 9.0(P)系统启动流程之init进程(第一阶段)
源码详解Android 9.0(P)系统启动流程之init进程(第二阶段)
源码详解Android 9.0(P)系统启动流程之init.rc语法规则
源码详解Android 9.0(P)系统启动流程之init进程(第三阶段)
源码详解Android 9.0(P)系统启动流程之核心服务和关键进程启动
源码详解Android 9.0(P)系统启动流程之Zygote进程
源码详解Android 9.0(P)系统启动流程之SystemServer
Android系统启动流程 init进程
1. 概述
在前一篇博客源码详解Android 9.0 系统启动流程之init进程(第一阶段)中,
我们分析了init进程第一阶段(内核态)的流程。
在本篇博客中,我们来看看init进程第二阶段(用户态)的工作,主要有以下内容:
- 初始化属性域、创建进程会话密钥
- 清空环境变量,完成selinux相关的工作
- 新建epoll并初始化子进程终止信号处理函数
- 设置其他系统属性并开启系统属性服务
2. 初始化属性域、创建进程会话密钥
int main(int argc, char** argv) {
//同样进行一些判断及环境变量设置的工作
..........
//之前准备工作时将INIT_SECOND_STAGE设置为true,已经不为nullptr,所以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.
// 同样屏蔽标准输入输出及定义Kernel logger
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
// Set up a session keyring that all processes will have access to. It
// will hold things like FBE encryption keys. No process should override
// its session keyring.
// 最后调用syscall,设置安全相关的值
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);//初始化进程会话密钥
// Indicate that booting is in progress to background fw loaders, etc.
// 这里的功能类似于“锁”
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));//创建 /dev/.booting 文件,就是个标记,表示booting进行中
property_init();//初始化属性域
//接下来的一系列操作都是从各个文件读取一些属性,然后通过property_set设置系统属性
// If arguments are passed both on the command line and in DT,
// properties set in DT always have priority over the command-line ones.
/*
* 1.这句英文的大概意思是,如果参数同时从命令行和DT传过来,DT的优先级总是大于命令行的
* 2.DT即device-tree,中文意思是设备树,这里面记录自己的硬件配置和系统运行参数
*/
process_kernel_dt();//处理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();//处理其他的一些属性,设置一些ro类型的系统属性
system_info_initialize();
aml_firstbootinit();
// 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。
2.1 keyctl_get_keyring_ID(keyctl)
定义在system/core/libkeyutils/Keyutils.cpp
keyctl将主要的工作交给__NR_keyctl这个系统调用,keyctl是Linux系统操纵内核的通讯密钥管理工具
static long keyctl(int cmd, ...) {
va_list va;
//va_start,va_arg,va_end是配合使用的,用于将可变参数从堆栈中读取出来
va_start(va, cmd);//va_start是获取第一个参数地址
unsigned long arg2 = va_arg(va, unsigned long);//va_arg 遍历参数
unsigned long arg3 = va_arg(va, unsigned long);
unsigned long arg4 = va_arg(va, unsigned long);
unsigned long arg5 = va_arg(va, unsigned long);
va_end(va);
return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);//系统调用
}
key_serial_t keyctl_get_keyring_ID(key_serial_t id, int create) {
return keyctl(KEYCTL_GET_KEYRING_ID, id, create);
}
2.2 property_init
property_init函数定义于system/core/init/property_service.cpp中,如下面代码所示,最终调用_system_property_area_init函数初始化属性域。
void property_init() {
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
if (!property_info_area.LoadDefaultPath()) {
LOG(FATAL) << "Failed to load serialized property info file";
}
}
_system_property_area_init 定义在/bionic/libc/bionic/system_properties.cpp看名字大概知道是用来初始化属性系统区域的,应该是分门别类更准确些,首先清除缓存,这里主要是清除几个链表以及在内存中的映射,新建property_filename目录,这个目录的值为 /dev/_properties;然后就是调用initialize_properties加载一些系统属性的类别信息,最后将加载的链表写入文件并映射到内存
2.3 process_kernel_dt
定义在system/core/init/init.cpp
读取DT(设备树)的属性信息,然后通过 property_set 设置系统属性
static void process_kernel_dt() {
if (!is_android_dt_value_expected("compatible", "android,firmware")) {
//判断 /proc/device-tree/firmware/android/compatible 文件中的值是否为 android,firmware
return;
}
// get_android_dt_dir()的值为/proc/device-tree/firmware/android
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(get_android_dt_dir().c_str()), closedir);
if (!dir) return;
std::string dt_file;
struct dirent *dp;
while ((dp = readdir(dir.get())) != NULL) {//遍历dir中的文件
if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
//跳过 compatible和name文件
continue;
}
std::string file_name = get_android_dt_dir() + dp->d_name;
android::base::ReadFileToString(file_name, &dt_file);//读取文件内容
std::replace(dt_file.begin(), dt_file.end(), ',', '.');//替换 , 为 .
property_set("ro.boot."s + dp->d_name, dt_file);// 将 ro.boot.文件名 作为key,文件内容为value,设置进属性
}
}
2.4 process_kernel_cmdline
static void process_kernel_cmdline() {
// The first pass does the common stuff, and finds if we are in qemu.
// The second pass is only necessary for qemu to export all kernel params
// as properties.
import_kernel_cmdline(false, import_kernel_nv);
if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
}
其内部调用了import_kernel_cmdline()
并传入了import_kernel_nv()
回调函数。import_kernel_cmdline()
代码的功能是将传入的cmdline进行分割,并将其传入import_kernel_nv()
中进行转换。import_kernel_nv()
的代码实现为:
static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
if (key.empty()) return;
if (for_emulator) {
// In the emulator, export any kernel option with the "ro.kernel." prefix.
property_set("ro.kernel." + key, value);
return;
}
if (key == "qemu") {
strlcpy(qemu, value.c_str(), sizeof(qemu));
} else if (android::base::StartsWith(key, "androidboot.")) {
property_set("ro.boot." + key.substr(12), value);
} else if (key == "mem_size") {
property_set("ro.mem_size", value);
}
}
import_kernel_nv()
的功能就是将kernel cmdline中的“androidboot.*
”参数转化为"ro.boot.*
"property。
2.5 export_kernel_boot_props
static void export_kernel_boot_props() {
struct {
const char *src_prop;
const char *dst_prop;
const char *default_value;
} prop_map[] = {
{ "ro.boot.serialno", "ro.serialno", "", },
{ "ro.boot.mode", "ro.bootmode", "unknown", },
{ "ro.boot.baseband", "ro.baseband", "unknown", },
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
{ "ro.boot.hardware", "ro.hardware", "unknown", },
{ "ro.boot.revision", "ro.revision", "0", },
{ "ro.boot.mac", "ro.mac", "00:22:7E:0B:53:26", },
{ "ro.boot.deviceid", "ro.deviceid", "unknown", },
{ "ro.boot.firstboot", "ro.firstboot", "0", },
};
for (size_t i = 0; i < arraysize(prop_map); i++) {
std::string value = GetProperty(prop_map[i].src_prop, "");
property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value);
}
}
在这里面有一个比较常见的系统属性ro.serialno,我们常见的序列号。它是由ro.boot.serialno这个值决定的,而这个值的赋值比较曲折,有机会再研究吧
3. 清空环境变量,完成selinux相关的工作
我们回到main函数,看看接下来的工作:
......
// Clean up our environment.
//清空这些环境变量,因为之前都已经存入到系统属性中去了
unsetenv("INIT_SECOND_STAGE");
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
// Now set up SELinux for second stage.
// 再次完成selinux相关的工作
SelinuxSetupKernelLogging();
SelabelInitialize();
SelinuxRestoreContext();
......
3.1 SelabelInitialize
定位在system/core/init/selinux.cpp
// selinux_android_file_context_handle() takes on the order of 10+ms to run, so we want to cache
// its value. selinux_android_restorecon() also needs an sehandle for file context look up. It
// will create and store its own copy, but selinux_android_set_sehandle() can be used to provide
// one, thus eliminating an extra call to selinux_android_file_context_handle().
void SelabelInitialize() {
sehandle = selinux_android_file_context_handle();//创建context的处理函数
selinux_android_set_sehandle(sehandle);//将刚刚新建的处理赋值给fc_sehandle
}
涉及函数
定位于external/selinux/libselinux/src/android/android_platform.c
static char const * const seapp_contexts_plat[] = {
"/system/etc/selinux/plat_seapp_contexts",
"/plat_seapp_contexts"
};
static char const * const seapp_contexts_vendor[] = {
"/vendor/etc/selinux/vendor_seapp_contexts",
"/vendor_seapp_contexts",
// TODO: remove nonplat* when no need to retain backward compatibility.
"/vendor/etc/selinux/nonplat_seapp_contexts",
"/nonplat_seapp_contexts"
};
static char const * const seapp_contexts_odm[] = {
"/odm/etc/selinux/odm_seapp_contexts",
"/odm_seapp_contexts"
};
struct selabel_handle* selinux_android_file_context_handle(void)
{
struct selinux_opt seopts_file[MAX_FILE_CONTEXT_SIZE];
int size = 0;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(seopts_file_plat); i++) {
if (access(seopts_file_plat[i].value, R_OK) != -1) {
seopts_file[size++] = seopts_file_plat[i];
break;
}
}
for (i = 0; i < ARRAY_SIZE(seopts_file_vendor); i++) {
if (access(seopts_file_vendor[i].value, R_OK) != -1) {
seopts_file[size++] = seopts_file_vendor[i];
break;
}
}
for (i = 0; i < ARRAY_SIZE(seopts_file_odm); i++) {
if (access(seopts_file_odm[i].value, R_OK) != -1) {
seopts_file[size++] = seopts_file_odm[i];
break;
}
}
return selinux_android_file_context(seopts_file, size);
}
void selinux_android_set_sehandle(const struct selabel_handle *hndl)
{
fc_sehandle = (struct selabel_handle *) hndl;
}
在init启动启动第一阶段内核态我们调用SelinuxInitialize函数初始化了SELinux,而在这一阶段主要调用selinux_android_file_context_handle初始化context_handle。
init 进程给一些文件或者系统属性进行安全上下文检查时会使用 libselinux 的 API,查询 file_contexts & property_contexts 文件中的安全上下文。
以系统属性为例,当其他进程通过 socket 与系统属性通信时请求访问某一项系统属性的值时,属性服务系统会通过 libselinux 提供的 selabel_lookup 函数到 property_contexts 文件中查找要访问属性的安全上下文,有了该进程的安全上下文和要访问属性的安全上下文之后,属性系统就能决定是否允许一个进程访问它所指定的服务了。
所以需要先得到这些文件的 handler,以便可以用来查询系统文件和系统属性的安全上下文。
3.2 selinux_restore_context
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启动后,需要重新设置一些属性
void SelinuxRestoreContext() {
LOG(INFO) << "Running restorecon...";
selinux_android_restorecon("/dev", 0);
selinux_android_restorecon("/dev/kmsg", 0);
if constexpr (WORLD_WRITABLE_KMSG) {
selinux_android_restorecon("/dev/kmsg_debug", 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("/vendor_file_contexts", 0);
selinux_android_restorecon("/plat_property_contexts", 0);
selinux_android_restorecon("/nonplat_property_contexts", 0);
selinux_android_restorecon("/vendor_property_contexts", 0);
selinux_android_restorecon("/plat_seapp_contexts", 0);
selinux_android_restorecon("/nonplat_seapp_contexts", 0);
selinux_android_restorecon("/vendor_seapp_contexts", 0);
selinux_android_restorecon("/plat_service_contexts", 0);
selinux_android_restorecon("/nonplat_service_contexts", 0);
selinux_android_restorecon("/vendor_service_contexts", 0);
selinux_android_restorecon("/plat_hwservice_contexts", 0);
selinux_android_restorecon("/nonplat_hwservice_contexts", 0);
selinux_android_restorecon("/vendor_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);
selinux_android_restorecon("/sbin/mkfs.f2fs", 0);
selinux_android_restorecon("/sbin/sload.f2fs", 0);
}
4. 新建epoll并初始化子进程终止信号处理函数
int main(){
......
epoll_fd = epoll_create1(EPOLL_CLOEXEC);//创建epoll实例,并返回epoll的文件描述符
if (epoll_fd == -1) {
PLOG(FATAL) << "epoll_create1 failed";
}
sigchld_handler_init();//注册子进程死亡监听socket
if (!IsRebootCapable()) {
// If init does not have the CAP_SYS_BOOT capability, it is running in a container.
// In that case, receiving SIGTERM will cause the system to shut down.
InstallSigtermHandler();
}
......
}
4.1 epoll_create1
定义在system/core/init/init.cpp
EPOLL类似于POLL,是Linux中用来做事件触发的,linux很长的时间都在使用select来做事件触发,它是通过轮询来处理的,轮询的fd数目越多,自然耗时越多,对于大量的描述符处理,EPOLL更有优势。epoll_create1是epoll_create的升级版,可以动态调整epoll实例中文件描述符的个数。
EPOLL_CLOEXEC这个参数是为文件描述符添加O_CLOEXEC属性,表示生成的epoll fd具有“执行后关闭”特性。
4.2 signal_handler_init
紧接着,init进程调用signal_handler_init装载子进程信号处理器,
该函数定义于system/core/init/signal_handler.cpp中。
init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),
需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)。
在这里我们有必要简单介绍一下Linux进程的状态,我们知道Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态,其状态主要分为如下几种:
- Linux进程状态:R (TASK_RUNNING),可执行状态&运行状态(在run_queue队列里的状态)
- Linux进程状态:S (TASK_INTERRUPTIBLE),可中断的睡眠状态, 可处理signal
- Linux进程状态:D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态, 可处理signal, 有延迟
- Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态, 不可处理signal, 因为根本没有时间片运行代码
- Linux进程状态:Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。不可被kill, 即不响应任务信号, 无法用SIGKILL杀死
对于上面的进程状态我们可以通过ps -A在运行的相关Android终端进行查看
在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,
此处init进程调用signal_handler_init的目的就是捕获子进程结束的信号。
我们来看看signal_handler_init
相关的代码:
void sigchld_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(FATAL) << "socketpair failed in sigchld_handler_init";
}
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处理函数
act.sa_flags = SA_NOCLDSTOP;
//调用信号安装函数sigaction,将监听的信号及对应的信号处理器注册到内核中
sigaction(SIGCHLD, &act, 0);
//用于终止出现问题的子进程,详细代码于后文分析。
ReapAnyOutstandingChildren();
//注册signal_read_fd到epoll中
register_epoll_handler(signal_read_fd, handle_signal);
}
Linux 信号机制
在深入分析代码前,我们需要了解一些基本概念:
- Linux进程通过互相发送消息来实现进程间的通信,这些消息被称为“信号”。
- 每个进程在处理其它进程发送的信号时都要注册处理者,处理者被称为信号处理器。
信号机制是Linux进程间通信的一种重要方式,Linux 信号一方面用于正常的进程间通信和同步,如任务控制(SIGINT, SIGTSTP,SIGKILL, SIGCONT,…);另一方面,它还负责监控系统异常及中断。 当应用程序运行异常时, Linux 内核将产生错误信号并通知当前进程。 当前进程在接收到相关信号后,可以有三种不同的处理方式。
- 忽略该信号。
- 捕捉该信号并执行对应的信号处理函数(signal handler),这里采用的就是这种方法
- 执行该信号的缺省操作(如 SIGTERM, 其缺省操作是终止进程)。
下面我们来分析一下代码
- 首先,利用sockerpair创建一对已经连接的socket文件描述符,分别作为信号的读/写端,这样当一端写入时,另一端就能被通知到,socketpair 两端既可以写也可以读,这里只是单向的让 s[0] 写,s[1] 读。
- 接着创建sigaction 结构体,这里注意到sigaction结构体的sa_flags为SA_NOCLDSTOP。由于系统默认在子进程暂停时也会发送信号SIGCHLD,init需要忽略子进程在暂停时发出的SIGCHLD信号,因此将act.sa_flags 置为SA_NOCLDSTOP,该标志位表示仅当进程终止时才接受SIGCHLD信号。
- 接着调用sigaction(SIGCHLD, &act, 0)是信号绑定关系,也就是当监听到SIGCHLD信号时,由 act 这个 sigaction 结构体进行处理,最终交由SIGCHLD_handler函数处理。
上述文字描述还是显得很单薄,空洞不是,那必须拿出必杀技了,有图才有真相不是,让我们通过示意图对整个逻辑把控一番,这样会更加得清晰明了,如果还是不明了,那只能说臣妾做不到了。
让我们对上面的示意图捋一捋,顺一顺,总结总结:
- init进程收到收到子进程 SIGCHLD 信号然后通过 sigaction 函数将信号处理过程转移到 sigaction 结构体
- sigaction 成员变量 sa_flags 另外指定所关心的具体信号为 SA_NOCLDSTOP,也就是子进程终止信号成员变量 sa_handler 表明当子进程终止时,通过 SIGCHLD_handler 函数处理
- SIGCHLD_handler 信号处理函数中通过 s[0] 文件描述符写了一个"1",由于 socketpair 的特性,s[1] 能接读到该字符串(后序讲解)
- 通过 register_epoll_handler 将 s[1] 注册到 epoll 内核事件表中,handle_signal 是 s[1] 有数据到来时的处理函数(后续讲解)
signal_handler_init需要关注的内容还是比较多的,我们分步骤来看看。
4.2.1 SIGCHLD_handler
我们先来看看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将对socketpair对中signal_write_fd执行写操作。由于socketpair的绑定关系,这将触发信号对应的signal_read_fd收到数据。
4.2.2 register_epoll_handler
通过前面的代码分析我么可以知道,在初始化信号监听函数的最后sigchld_handler_init函数调用了register_epoll_handler,该函数定义在system/core/init/init.cpp中,注意这里传入的两个参数分别为signal_read_fd和handle_signal。
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处理即调用handle_signal
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) {
PLOG(ERROR) << "epoll_ctl failed";
}
}
根据代码不难看出:
当epoll句柄监听到signal_read_fd中有数据可读时,将调用handle_signal进行处理。
4.2.3 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));
ReapAnyOutstandingChildren();
}
其逻辑非常简单就是读取(清空)signal_read_fd的数据,然后调用ReapAnyOutstandingChildren函数进行相关处理。
ReapAnyOutstandingChildren之前在signal_handler_init中调用过一次,它其实是调用ReapOneProcess
void ReapAnyOutstandingChildren() {
while (ReapOneProcess()) {
}
}
4.2.4 ReapOneProcess
定义在system\core\init\sigchld_handler.cpp
这是最终的处理函数了,这个函数先用waitpid找出挂掉进程的pid,然后根据pid找到对应Service,最后调用Service的Reap方法清除资源,根据进程对应的类型,决定是否重启机器或重启进程
static bool 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.
//waitpid会暂时停止目前进程的执行,直到有信号来到或子进程结束
//这里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)); });
std::string name;
std::string wait_string;
Service* service = nullptr;
if (PropertyChildReap(pid)) {
name = "Async property child";
} else if (SubcontextChildReap(pid)) {
name = "Subcontext";
} else {
//利用FindService找到pid对应的服务
//FindService主要是通过轮询init.rc解析生成的services_列表,找到pid与参数一致的service
service = ServiceList::GetInstance().FindService(pid, &Service::pid);
if (service) {//输出找到服务信息
name = StringPrintf("Service '%s' (pid %d)", service->name().c_str(), pid);
if (service->flags() & SVC_EXEC) {
auto exec_duration = boot_clock::now() - service->time_started();
auto exec_duration_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(exec_duration).count();
wait_string = StringPrintf(" waiting took %f seconds", exec_duration_ms / 1000.0f);
}
} else {
name = StringPrintf("Untracked pid %d", pid);
}
}
// 根据svc的类型,决定后续的处理方式
if (siginfo.si_code == CLD_EXITED) {
LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;
} else {
LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
}
if (!service) return true;//没有找到,说明已经结束了
//清除子进程相关资源
service->Reap(siginfo);
if (service->flags() & SVC_TEMPORARY) {
ServiceList::GetInstance().RemoveService(*service);//移除临时服务
}
return true;
}
上文中,waitpid的函数原型为:
pid_t waitpid(pid_t pid, int *status, int options)
其中:
第一个参数pid为预等待的子进程的识别码,pid=-1表示等待任何子进程是否发出SIGCHLD。
第二个参数status,用于返回子进程的结束状态。
第三个参数决定waitpid函数是否处于阻塞处理方式;
WNOHANG表示若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待;
若子进程结束,则返回子进程的pid。
waitpid如果出错,则返回-1。
容易看出handle_signal的主要作用就是找出出现问题的进程,
然后调用对应的Reap函数处理。
Reap
该代码定义在system/core/init/service.cpp中
void Service::Reap(const siginfo_t& siginfo) {
//清除未携带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));
for (const auto& f : reap_callbacks_) {
f(siginfo);
}
if (flags_ & SVC_EXEC) UnSetExec();
//清理工作完毕后,后面决定是否重启机器或重启服务
//TEMP服务不用参与这种判断
if (flags_ & SVC_TEMPORARY) return;
pid_ = 0;
flags_ &= (~SVC_RUNNING);
start_order_ = 0;
// 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.
//未携带SVC_RESTART的关键服务,在规定的间隔内,crash字数过多时,会导致整机重启;
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();
if ((flags_ & SVC_CRITICAL) && !(flags_ & SVC_RESTART)) {
if (now < time_crashed_ + 4min) {
if (++crash_count_ > 4) {
LOG(FATAL) << "critical process '" << name_ << "' exited 4 times in 4 minutes";
}
} 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;
//进程重启时,将执行对应的函数
auto result = command.InvokeFunc(subcontext_);
//打印log
auto duration = t.duration();
// There are many legacy paths in rootdir/init.rc that will virtually never exist on a new
// device, such as '/sys/class/leds/jogball-backlight/brightness'. As of this writing, there
// are 198 such failures on bullhead. Instead of spamming the log reporting them, we do not
// report such failures unless we're running at the DEBUG log level.
bool report_failure = !result.has_value();
if (report_failure && android::base::GetMinimumLogSeverity() > android::base::DEBUG &&
result.error_errno() == ENOENT) {
report_failure = false;
}
// Any action longer than 50ms will be warned to user as slow operation
if (report_failure || 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() << ") took " << duration.count() << "ms and "
<< (result ? "succeeded" : "failed: " + result.error_string());
}
}
整个signal_handler_init的内容比较多,在此总结一下:
signal_handler_init的本质就是监听子进程死亡的信息,
然后进行对应的清理工作,并根据死亡进程的类型,
决定是否需要重启进程或机器。
上述过程其实最终可以简化为下图:
5. 设置其他系统属性并开启系统属性服务
我们重新将视角拉回到init的main函数,看看接下来的工作:
...
property_load_boot_defaults();//从文件中加载一些属性,读取usb配置
export_oem_lock_status();//设置ro.boot.flash.locked 属性
start_property_service();//开启一个socket监听系统属性的设置
set_usb_controller();//设置sys.usb.controller 属性
...
5.1 设置其他系统属性
property_load_boot_defaults,export_oem_lock_status,set_usb_controller这三个函数都是调用property_set设置一些系统属性
5.1.1 property_load_boot_defaults
我们先来看看property_load_boot_defaults函数的内容:
定义在system/core//init/property_service.cpp
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("/product/build.prop", NULL);
load_properties_from_file("/odm/default.prop", NULL);
load_properties_from_file("/vendor/default.prop", NULL);
//就是设置"persist.sys.usb.config"相关的配置
update_sys_usb_config();
}
如代码所示,property_load_boot_defaults实际上就是调用load_properties_from_file解析配置文件;
然后根据解析的结果,设置系统属性。
该部分功能较为单一,不再深入分析。
5.1.2 export_oem_lock_status
static void export_oem_lock_status() {
if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
return;
}
std::string value = GetProperty("ro.boot.verifiedbootstate", "");
if (!value.empty()) {
property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
}
}
5.1.3 set_usb_controller
static void set_usb_controller() {
std::unique_ptr<DIR, decltype(&closedir)>dir(opendir("/sys/class/udc"), closedir);
if (!dir) return;
dirent* dp;
while ((dp = readdir(dir.get())) != nullptr) {
if (dp->d_name[0] == '.') continue;
property_set("sys.usb.controller", dp->d_name);
break;
}
}
5.2 start_property_service
定义在platform/system/core/init/property_service.cpp
通过前面的代码分析我们知道在init进程在共享内存区域中,创建并初始化属性域,然后通过property_set 可以轻松设置系统属性,那干嘛这里还要启动一个属性服务呢?这里其实涉及到一些权限的问题,不是所有进程都可以随意修改任何的系统属性。
Android将属性的设置统一交由init进程管理,其他进程不能直接修改属性,而只能通知init进程来修改,而在这过程中,init进程可以进行权限控制,我们来看看这些是如何实现的
- 首先创建一个socket并返回文件描述符,然后设置最大并发数为8,其他进程可以通过这个socket通知init进程修改系统属性
- 最后注册epoll事件,也就是当监听到property_set_fd改变时调用handle_property_set_fd
void start_property_service() {
selinux_callback cb;
cb.func_audit = SelinuxAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
property_set("ro.property_service.version", "2");
//创建了一个非阻塞socket
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr);
if (property_set_fd == -1) {
PLOG(FATAL) << "start_property_service socket creation failed";
}
//调用listen函数监听property_set_fd, 于是该socket变成一个server
listen(property_set_fd, 8);
//监听server socket上是否有数据到来
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
在正式开始源码细节分析前,先奉上其它进程修改系统属性时大致的流程,以供大家心里有个整体概括:
5.2.1 相关解释
我们知道,在create_socket函数返回套接字property_set_fd时,property_set_fd是一个主动连接的套接字。
此时,系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接。
由于在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接,
于是需要调用listen函数使用主动连接套接字变为被连接套接字,
使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。
因此,上述代码调用listen后,init进程成为一个服务进程,
其它进程可以通过property_set_fd连接init进程,提交设置系统属性的申请。
listen函数的第二个参数,涉及到一些网络的细节。
在进程处理一个连接请求的时候,可能还存在其它的连接请求。
因为TCP连接是一个过程,所以可能存在一种半连接的状态。
有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。
因此,内核会在自己的进程空间里维护一个队列,以跟踪那些已完成连接但服务器进程还没有接手处理的用户,
或正在进行的连接的用户。
这样的一个队列不可能任意大,所以必须有一个上限。
listen的第二个参数就是告诉内核使用这个数值作为上限。
因此,init进程作为系统属性设置的服务器,最多可以同时为8个试图设置属性的用户提供服务。
5.2.2 handle_property_set_fd
这个函数的作用主要是调用accept4处理property_set_fd的scoket描述符中的数据信息,然后从 socket 中读取操作信息,根据不同的操作类型,调用 HandlePropertySet做具体的操作:
static void handle_property_set_fd() {
static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */
//等待客户端连接
int s = accept4(property_set_fd, nullptr, nullptr, SOCK_CLOEXEC);
if (s == -1) {
return;
}
ucred cr;
socklen_t cr_size = sizeof(cr);
if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {//获取连接到此socket的进程的凭据
close(s);
PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
return;
}
SocketConnection socket(s, cr);
uint32_t timeout_ms = kDefaultSocketTimeout;
uint32_t cmd = 0;
if (!socket.RecvUint32(&cmd, &timeout_ms)) {// 读取 socket 中的操作类型信息
PLOG(ERROR) << "sys_prop: error while reading command from the socket";
socket.SendUint32(PROP_ERROR_READ_CMD);
return;
}
switch (cmd) {// 根据操作类型信息,执行对应处理,两者区别一个是以 char 形式读取,一个以 String 形式读取
case PROP_MSG_SETPROP: {
char prop_name[PROP_NAME_MAX];
char prop_value[PROP_VALUE_MAX];
if (!socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) ||
!socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
return;
}
prop_name[PROP_NAME_MAX-1] = 0;
prop_value[PROP_VALUE_MAX-1] = 0;
const auto& cr = socket.cred();
std::string error;
uint32_t result =
HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << prop_name << "' to '" << prop_value
<< "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
<< error;
}
break;
}
case PROP_MSG_SETPROP2: {
std::string name;
std::string value;
if (!socket.RecvString(&name, &timeout_ms) ||
!socket.RecvString(&value, &timeout_ms)) {
PLOG(ERROR) << "sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
socket.SendUint32(PROP_ERROR_READ_DATA);
return;
}
const auto& cr = socket.cred();
std::string error;
uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
if (result != PROP_SUCCESS) {
LOG(ERROR) << "Unable to set property '" << name << "' to '" << value
<< "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ": "
<< error;
}
socket.SendUint32(result);
break;
}
default:
LOG(ERROR) << "sys_prop: invalid command " << cmd;
socket.SendUint32(PROP_ERROR_INVALID_CMD);
break;
}
}
5.2.3 HandlePropertySet
这个是最终的调用处理函数,set property msg 分为两类处理,msg name 以“ctl.”为起始的 msg 通过HandleControlMessage处理,主要是启动、停止、重启服务。修改其它 prop时会调用 property_get,然后通过 bionic 的__system_property_set 函数来实现,而这个函数会通过 socket 与 init 的 property service 取得联系。但是不管是前者还是后者,都要进行 SELinux 安全性检查,只有该进程有操作权限才能执行相应操作。
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
const std::string& source_context, const ucred& cr, std::string* error) {
if (!IsLegalPropertyName(name)) {//检查可以的合法性
*error = "Illegal property name";
return PROP_ERROR_INVALID_NAME;
}
if (StartsWith(name, "ctl.")) {//如果一ctl开头,就执行Service的一些控制操作
if (!CheckControlPropertyPerms(name, value, source_context, cr)) {//SELinux安全检查,有权限才进行操作
*error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
value.c_str());
return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
}
HandleControlMessage(name.c_str() + 4, value, cr.pid);
return PROP_SUCCESS;
}
const char* target_context = nullptr;
const char* type = nullptr;
property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);
if (!CheckMacPerms(name, target_context, source_context.c_str(), cr)) {//检查SElinux规则
*error = "SELinux permission check failed";
return PROP_ERROR_PERMISSION_DENIED;
}
if (type == nullptr || !CheckType(type, value)) {
*error = StringPrintf("Property type check failed, value doesn't match expected type '%s'",
(type ?: "(null)"));
return PROP_ERROR_INVALID_VALUE;
}
// sys.powerctl is a special property that is used to make the device reboot. We want to log
// any process that sets this property to be able to accurately blame the cause of a shutdown.
if (name == "sys.powerctl") {
std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
std::string process_cmdline;
std::string process_log_string;
if (ReadFileToString(cmdline_path, &process_cmdline)) {
// Since cmdline is null deliminated, .c_str() conveniently gives us just the process
// path.
process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
}
LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
<< process_log_string;
}
if (name == "selinux.restorecon_recursive") {
return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
}
return PropertySet(name, value, error);//其它属性调用PropertySet
}
这段代码比较简单,整体的大概流程如下:
- 通过IsLegalPropertyName检测要设置的是否是合法的属性名
- 如果是以 “ctl.” 打头的属性名表明是控制命令(譬如ctl.start,ctl.stop,ctl.restart),如果能经过权限检测则调用HandleControlMessage进行处理
- 其它种情况调用PropertySet处理
在这一部分的最后,我们简单举例介绍一下,系统属性改变的一些用途。
在init.rc中定义了一些与属性相关的触发器。
当某个条件相关的属性被改变时,与该条件相关的触发器就会被触发。
举例来说,如下面代码所示,debuggable属性变为1时,将执行启动console进程等操作。
on property:ro.debuggable=1
# Give writes to anyone for the trace folder on debug builds.
# The folder is used to store method traces.
chmod 0773 /data/misc/trace
start console
6. 总结
随着Android版本越高,init的工作量也是越来越大了,分析起来不得不使出吃奶的力气了,在init进程的第二阶段主要工作是主要工作是初始化属性系统,解析SELinux的匹配规则,处理子进程终止信号,启动系统属性服务等等,可以说每一项都很关键,如果说第一阶段是为属性系统,SELinux做准备,那么第二阶段就是真正去把这些落实的。
至此,init进程的准备工作执行完毕,
接下来就要开始解析init.rc文件了。
解析init.rc代码的流程,我们放到下一篇博客介绍。