目录
一、前言
上一篇中讲了init进程的第一阶段,我们接着讲第二阶段,主要有以下内容
- 创建进程会话密钥并初始化属性系统
- 进行SELinux第二阶段并恢复一些文件安全上下文
- 新建epoll并初始化子进程终止信号处理函数
- 设置其他系统属性并开启系统属性服务
本文涉及到的文件
platform/system/core/init/init.cpp
platform/system/core/init/keyutils.h
platform/system/core/init/property_service.cpp
platform/external/selinux/libselinux/src/label.c
platform/system/core/init/signal_handler.cpp
platform/system/core/init/service.cpp
platform/system/core/init/property_service.cpp
二、源码分析
2.1 创建进程会话密钥并初始化属性系统
第二阶段先有一个is_first_stage的判断,由于之前第一阶段最后有设置INIT_SECOND_STAGE,
因此直接跳过一大段代码。
int main(int argc, char** argv) {
//同样进行ueventd/watchdogd跳转及环境变量设置
...
//之前准备工作时将INIT_SECOND_STAGE设置为true,已经不为nullptr,所以is_first_stage为false
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
//is_first_stage为false,直接跳过
if (is_first_stage) {
...
}
// At this point we're in the second stage of init.
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.
keyctl(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,中文意思是设备树,这里面记录自己的硬件配置和系统运行参数,参考http://www.wowotech.net/linux_kenrel/why-dt.html
*/
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();//处理其他的一些属性
// 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);
// Clean up our environment.
unsetenv("INIT_SECOND_STAGE"); //清空这些环境变量,因为之前都已经存入到系统属性中去了
unsetenv("INIT_STARTED_AT");
unsetenv("INIT_SELINUX_TOOK");
unsetenv("INIT_AVB_VERSION");
...
2.2 新建epoll并初始化子进程终止信号处理函数
epoll_fd = epoll_create1(EPOLL_CLOEXEC);//创建epoll实例,并返回epoll的文件描述符
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
signal_handler_init();//主要是创建handler处理子进程终止信号,创建一个匿名socket并注册到epoll进行监听
2.2.1 epoll_create1
EPOLL类似于POLL,是Linux中用来做事件触发的,跟EventBus功能差不多
linux很长的时间都在使用select来做事件触发,它是通过轮询来处理的,轮询的fd数目越多,自然耗时越多,对于大量的描述符处理,EPOLL更有优势
epoll_create1是epoll_create的升级版,可以动态调整epoll实例中文件描述符的个数
EPOLL_CLOEXEC这个参数是为文件描述符添加O_CLOEXEC属性,参考http://blog.youkuaiyun.com/gqtcgq/article/details/48767691
2.2.2 signal_handler_init
定义在platform/system/core/init/signal_handler.cpp
void signal_handler_init() {
// Create a signalling mechanism for SIGCHLD.
int s[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) { //创建socket并返回文件描述符
PLOG(ERROR) << "socketpair failed";
exit(1);
}
signal_write_fd = s[0];
signal_read_fd = s[1];
// Write to signal_write_fd if we catch SIGCHLD.
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = SIGCHLD_handler; //act处理函数
act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, 0);
ServiceManager::GetInstance().ReapAnyOutstandingChildren();//具体处理子进程终止信号
register_epoll_handler(signal_read_fd, handle_signal);//注册signal_read_fd到epoll中
}
void register_epoll_handler(int fd, void (*fn)()) {
epoll_event ev;
ev.events = EPOLLIN; //监听事件类型,EPOLLIN表示fd中有数据可读
ev.data.ptr = reinterpret_cast<void*>(fn); //回调函数赋值给ptr
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { //注册事件
PLOG(ERROR) << "epoll_ctl failed";
}
}
这个函数主要的作用是注册SIGCHLD信号的处理函数
init是一个守护进程,为了防止init的子进程成为僵尸进程(zombie process),
需要init在子进程在结束时获取子进程的结束码,通过结束码将程序表中的子进程移除,
防止成为僵尸进程的子进程占用程序表的空间(程序表的空间达到上限时,系统就不能再启动新的进程了,会引起严重的系统问题)
在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,SIGCHLD信号会在子进程终止的时候发出,了解这些背景后,我们来看看init进程如何处理这个信号
首先,调用socketpair,这个方法会返回一对文件描述符,这样当一端写入时,另一端就能被通知到,
socketpair两端既可以写也可以读,这里只是单向的让s[0]写,s[1]读
然后,新建一个sigaction结构体,sa_handler是信号处理函数,指向SIGCHLD_handler,
SIGCHLD_handler做的事情就是往s[0]里写个"1",这样s1就会收到通知,SA_NOCLDSTOP表示只在子进程终止时处理,
因为子进程在暂停时也会发出SIGCHLD信号
sigaction(SIGCHLD, &act, 0) 这个是建立信号绑定关系,也就是说当监听到SIGCHLD信号时,由act这个sigaction结构体处理
ReapAnyOutstandingChildren 这个后文讲
最后,register_epoll_handler的作用就是注册一个监听,当signal_read_fd(之前的s[1])收到信号,触发handle_signal
终上所述,signal_handler_init函数的作用就是,接收到SIGCHLD信号时触发handle_signal
2.2.3 handle_signal
定义在platform/system/core/init/signal_handler.cpp
static void handle_signal() {
// Clear outstanding requests.
char buf[32];
read(signal_read_fd, buf, sizeof(buf));
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
}
首先清空signal_read_fd中的数据,然后调用ReapAnyOutstandingChildren,之前在signal_handler_init中调用过一次,
它其实是调用ReapOneProcess