1. 概览
在进入 second stage init 讲解之前,先来看看它事件监听及处理的机制 – Epoll 类,它实际上是对 epoll 的封装,使他变得更加适合再 init 中来跟踪事件以及分发触发方法等。整个类只有 4 个方法,在如此小巧的条件下实现了:事件监听的注册、卸载、跟踪以及收集事件的处理方法。这也意味着用户在注册后,在等待到事件后可以直接调用返回的处理方法列表。
int SecondStageMain(int argc, char** argv) {
if (REBOOT_BOOTLOADER_ON_PANIC) {
InstallRebootSignalHandlers();
}
// No threads should be spin up until signalfd
// is registered. If the threads are indeed required,
// each of these threads _should_ make sure SIGCHLD signal
// is blocked. See b/223076262
boot_clock::time_point start_time = boot_clock::now();
trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
SetStdioToDevNull(argv);
InitKernelLogging(argv);
LOG(INFO) << "init second stage started!";
SelinuxSetupKernelLogging();
// Update $PATH in the case the second stage init is newer than first stage init, where it is
// first set.
if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
PLOG(FATAL) << "Could not set $PATH to '" << _PATH_DEFPATH << "' in second stage";
}
// Init should not crash because of a dependence on any other process, therefore we ignore
// SIGPIPE and handle EPIPE at the call site directly. Note that setting a signal to SIG_IGN
// is inherited across exec, but custom signal handlers are not. Since we do not want to
// ignore SIGPIPE for child processes, we set a no-op function for the signal handler instead.
{
struct sigaction action = {.sa_flags = SA_RESTART};
action.sa_handler = [](int) {};
sigaction(SIGPIPE, &action, nullptr);
}
// Set init and its forked children's oom_adj.
if (auto result =
WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));
!result.ok()) {
LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST
<< " to /proc/1/oom_score_adj: " << result.error();
}
// Set up a session keyring that all processes will have access to. It
// will hold things like FBE encryption keys. No process should override
// its session keyring.
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
// See if need to load debug props to allow adb root, when the device is unlocked.
const char* force_debuggable_env = getenv("INIT_FORCE_DEBUGGABLE");
bool load_debug_prop = false;
if (force_debuggable_env && AvbHandle::IsDeviceUnlocked()) {
load_debug_prop = "true"s == force_debuggable_env;
}
unsetenv("INIT_FORCE_DEBUGGABLE");
// Umount the debug ramdisk so property service doesn't read .prop files from there, when it
// is not meant to.
if (!load_debug_prop) {
UmountDebugRamdisk();
}
PropertyInit();
// Umount second stage resources after property service has read the .prop files.
UmountSecondStageRes();
// Umount the debug ramdisk after property service has read the .prop files when it means to.
if (load_debug_prop) {
UmountDebugRamdisk();
}
// Mount extra filesystems required during second stage init
MountExtraFilesystems();
// Now set up SELinux for second stage.
SelabelInitialize();
SelinuxRestoreContext();
Epoll epoll;
if (auto result = epoll.Open(); !result.ok()) {
PLOG(FATAL) << result.error();
}
// We always reap children before responding to the other pending functions. This is to
// prevent a race where other daemons see that a service has exited and ask init to
// start it again via ctl.start before init has reaped it.
epoll.SetFirstCallback(ReapAnyOutstandingChildren);
InstallSignalFdHandler(&epoll);
InstallInitNotifier(&epoll);
StartPropertyService(&property_fd);
// Make the time that init stages started available for bootstat to log.
RecordStageBoottimes(start_time);
// Set libavb version for Framework-only OTA match in Treble build.
if (const char* avb_version = getenv("INIT_AVB_VERSION"); avb_version != nullptr) {
SetProperty("ro.boot.avb_version", avb_version);
}
unsetenv("INIT_AVB_VERSION");
fs_mgr_vendor_overlay_mount_all();
export_oem_lock_status();
MountHandler mount_handler(&epoll);
SetUsbController();
SetKernelVersion();
const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
Action::set_function_map(&function_map);
if (!SetupMountNamespaces()) {
PLOG(FATAL) << "SetupMountNamespaces failed";
}
InitializeSubcontext();
ActionManager& am = ActionManager::GetInstance();
ServiceList& sm = ServiceList::GetInstance();
LoadBootScripts(am, sm);
// Turning this on and letting the INFO logging be discarded adds 0.2s to
// Nexus 9 boot time, so it's disabled by default.
if (false) DumpState();
// Make the GSI status available before scripts start running.
auto is_running = android::gsi::IsGsiRunning() ? "1" : "0";
SetProperty(gsi::kGsiBootedProp, is_running);
auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
SetProperty(gsi::kGsiInstalledProp, is_installed);
if (android::gsi::IsGsiRunning()) {
std::string dsu_slot;
if (android::gsi::GetActiveDsu(&dsu_slot)) {
SetProperty(gsi::kDsuSlotProp, dsu_slot);
}
}
am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
am.QueueBuiltinAction(ConnectEarlyStageSnapuserdAction, "ConnectEarlyStageSnapuserd");
am.QueueEventTrigger("early-init");
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
// ... so that we can start queuing up actions that require stuff from /dev.
am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
Keychords keychords;
am.QueueBuiltinAction(
[&epoll, &keychords](const BuiltinArguments& args) -> Result<void> {
for (const auto& svc : ServiceList::GetInstance()) {
keychords.Register(svc->keycodes());
}
keychords.Start(&epoll, HandleKeychord);
return {};
},
"KeychordInit");
// Trigger all the boot actions to get us started.
am.QueueEventTrigger("init");
// Don't mount filesystems or start core system services in charger mode.
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
// Run all property triggers based on current state of the properties.
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
// Restore prio before main loop
setpriority(PRIO_PROCESS, 0, 0);
while (true) {
// By default, sleep until something happens. Do not convert far_future into
// std::chrono::milliseconds because that would trigger an overflow. The unit of boot_clock
// is 1ns.
const boot_clock::time_point far_future = boot_clock::time_point::max();
boot_clock::time_point next_action_time = far_future;
auto shutdown_command = shutdown_state.CheckShutdown();
if (shutdown_command) {
LOG(INFO) << "Got shutdown_command '" << *shutdown_command
<< "' Calling HandlePowerctlMessage()";
HandlePowerctlMessage(*shutdown_command);
}
if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
am.ExecuteOneCommand();
// If there's more work to do, wake up again immediately.
if (am.HasMoreCommands()) {
next_action_time = boot_clock::now();
}
}
// Since the above code examined pending actions, no new actions must be
// queued by the code between this line and the Epoll::Wait() call below
// without calling WakeMainInitThread().
if (!IsShuttingDown()) {
auto next_process_action_time = HandleProcessActions();
// If there's a process that needs restarting, wake up in time for that.
if (next_process_action_time) {
next_action_time = std::min(next_action_time, *next_process_action_time);
}
}
std::optional<std::chrono::milliseconds> epoll_timeout;
if (next_action_time != far_future) {
epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
std::max(next_action_time - boot_clock::now(), 0ns));
}
auto epoll_result = epoll.Wait(epoll_timeout);
if (!epoll_result.ok()) {
LOG(ERROR) << epoll_result.error();
}
if (!IsShuttingDown()) {
HandleControlMessages();
SetUsbController();
}
}
return 0;
}
**函数概述:**
`SecondStageMain` 函数是 Android 系统的第二阶段初始化程序,它负责完成系统的启动过程并加载其他服务和应用程序。
**函数参数:**
* `argc`: 命令行参数的数量。
* `argv`: 命令行参数的数组。
**函数流程:**
1. **初始化第二阶段环境:**
* 设置信号处理程序,以便在收到某些信号时采取相应的操作。
* 设置进程的优先级。
* 设置环境变量。
* 安装文件系统。
2. **加载属性服务:**
* 加载属性服务,用于管理系统属性。
3. **挂载额外文件系统:**
* 挂载必要的额外文件系统。
4. **设置 SELinux 上下文:**
* 初始化 SELinux 并设置 SELinux 上下文。
5. **创建 Epoll 实例:**
* 创建一个 Epoll 实例,用于监听文件描述符事件。
6. **安装信号处理程序和初始化程序通知程序:**
* 安装信号处理程序,以便在收到某些信号时采取相应的操作。
* 安装初始化程序通知程序,以便在收到某些通知时采取相应的操作。
7. **启动属性服务:**
* 启动属性服务。
8. **记录引导时间:**
* 记录引导阶段的开始时间。
9. **设置 libavb 版本:**
* 设置 libavb 版本,用于在 Treble 构建中进行 Framework-only OTA 匹配。
10. **挂载供应商覆盖文件系统:**
* 挂载供应商覆盖文件系统。
11. **导出 OEM 锁定状态:**
* 导出 OEM 锁定状态。
12. **设置 USB 控制器:**
* 设置 USB 控制器。
13. **设置内核版本:**
* 设置内核版本。
14. **设置内置函数映射:**
* 设置内置函数映射,用于将内置函数名映射到对应的函数指针。
15. **初始化子上下文:**
* 初始化子上下文。
16. **初始化操作管理器和服务列表:**
* 初始化操作管理器和服务列表。
17. **加载引导脚本:**
* 加载引导脚本。
18. **队列内置操作:**
* 将一些内置操作加入到队列中,这些操作将在稍后执行。
19. **触发事件:**
* 触发 "init" 事件,以便执行与该事件关联的操作。
20. **根据引导模式采取不同的操作:**
* 根据引导模式的不同,采取不同的操作。
21. **运行属性触发器:**
* 运行属性触发器,以便执行与当前属性状态关联的操作。
22. **进入主循环:**
* 进入主循环,监听文件描述符事件并执行相应的操作。
**函数返回值:**
返回 0 表示程序执行成功。
2.整体使用骨架
second init 中就是使用 Epoll 来跟踪事件并处理的,下面看下 Epoll 的使用流程。
//system\core\init\init.cpp
SecondStageMain
//code 1
Epoll epoll;
epoll.Open();
//code 2
InstallInitNotifier(&epoll);
auto clear_eventfd = [] {
uint64_t counter;
TEMP_FAILURE_RETRY(read(wake_main_thread_fd, &counter, sizeof(counter)));
};
epoll->RegisterHandler(wake_main_thread_fd, clear_eventfd)
//code 3
while (true) {
auto pending_functions = epoll.Wait(epoll_timeout);
for (const auto& function : *pending_functions) {
(*function)();
}
}
2.1 Epoll 的实例化 – code1
Epoll 构造方法使用的是空的并没做什么,再 Open 中也是很简单的申请了一个 epoll 的句柄,并记录在 epoll_fd_类变量中。
Result<void> Epoll::Open() {
if (epoll_fd_ >= 0) return {};
epoll_fd_.reset(epoll_create1(EPOLL_CLOEXEC));
if (epoll_fd_ == -1) {
return ErrnoError() << "epoll_create1 failed";
}
return {};
}
2.2 注册监听对象及处理方法 – code2
Epoll 的监听对象只能是 fd 也就是文件句柄,它的回调方法原型如下
//system\core\init\epoll.h
typedef std::function<void()> Handler;
准备好监听对象和它的处理方法后就可以调用 RegisterHandler 注册到 Epoll 中了,来看看它的定义
Result<void> Epoll::RegisterHandler(int fd, Handler handler, uint32_t events) {
if (!events) {
return Error() << "Must specify events";
}
auto [it, inserted] = epoll_handlers_.emplace(
fd, Info{
.events = events,
.handler = std::move(handler),
});
if (!inserted) {
return Error() << "Cannot specify two epoll handlers for a given FD";
}
epoll_event ev = {
.events = events,
.data.fd = fd,
};
if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_ADD, fd, &ev) == -1) {
Result<void> result = ErrnoError() << "epoll_ctl failed to add fd";
epoll_handlers_.erase(fd);
return result;
}
return {};
}
1. **参数检查:**
* 首先,代码检查传入的events参数是否为0。如果为0,则返回一个错误信息,指出必须指定事件。这是因为epoll事件循环需要知道对文件描述符感兴趣的事件类型,以便在该事件发生时通知处理程序。
2. **插入处理程序:**
* 接下来,代码使用std::unordered_map::emplace方法将文件描述符和处理程序信息插入到epoll_handlers_映射中。std::unordered_map是一种哈希表,它可以快速查找和插入元素。
* Info结构体包含了事件类型和处理程序信息。
* emplace方法可以确保只有当文件描述符不存在于映射中时才进行插入操作。如果文件描述符已经存在,则返回一个std::pair对象,其中first是映射中的元素,second是插入是否成功的标志。
* 如果插入操作成功,则返回true,否则返回false。
3. **创建epoll事件:**
* 在插入处理程序之后,代码创建一个epoll事件结构体ev。
* ev结构体包含了事件类型和文件描述符信息。
* 事件类型与Info结构体中存储的事件类型相同。
* 文件描述符是传入的fd参数。
4. **添加epoll事件:**
* 接下来,代码使用epoll_ctl系统调用将epoll事件添加到epoll事件循环中。
* epoll_ctl系统调用有三个参数:epoll文件描述符、操作类型和epoll事件结构体。
* 操作类型是EPOLL_CTL_ADD,表示要将epoll事件添加到epoll事件循环中。
* epoll事件结构体是ev。
* 如果epoll_ctl系统调用成功,则返回0,否则返回-1。
5. **错误处理:**
* 如果epoll_ctl系统调用失败,则代码返回一个ErrnoError对象。
* ErrnoError对象包含了错误信息和错误代码。
* 然后,代码从epoll_handlers_映射中删除该文件描述符,以确保映射中没有不正确的条目。
6. **返回结果:**
* 最后,代码返回一个Result对象。
* Result对象可以是Ok或Err。
* 如果epoll_ctl系统调用成功,则返回Ok()。
* 如果epoll_ctl系统调用失败,则返回Err(error)。
**总而言之,这段代码用于在epoll事件循环中注册一个新的文件描述符和对应的处理程序。如果注册成功,则返回Ok(),否则返回Err(error)。**
可见注册分为如下几个步骤
a) 构建 Info 信息,其中包括它对 fd 中关心的事件类型,比如默认值 EPOLLIN,如果来这个事件就意味着 fd 句柄有数据可以读取了。还有用来处理 fd 数据的回调方法 handler。最后将 Info 和 fd 传入到 map epoll_handlers_中去,供后续使用。
b) 设置 epoll_event 事件,该数据结构则是使用 epoll 的的标准构造方法,其中记录的 events 则是供 kernel 使用的和上面提到的 info.events值一致。并将 Info 实例作为它的私有数据。
c) 最后调用 epoll_ctl 将 fd 添加到 epoll_fd_中去,如此系统才会对该 fd 实现监听。
2.3 等待事件并返回处理方法 – code3
可见 Wait 方法会被循环调用不做退出的,这部分的概念和 Looper 还是很相似的,一个线程中只存在一个 Epoll中,并且 Epoll 的回调方法也是该线程执行的。下面就来看看它的实现
Result<int> Epoll::Wait(std::optional<std::chrono::milliseconds> timeout) {
int timeout_ms = -1;
if (timeout && timeout->count() < INT_MAX) {
timeout_ms = timeout->count();
}
const auto max_events = epoll_handlers_.size();
epoll_event ev[max_events];
auto num_events = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd_.get(), ev, max_events, timeout_ms));
if (num_events == -1) {
return ErrnoError() << "epoll_wait failed";
}
if (num_events > 0 && first_callback_) {
first_callback_();
}
for (int i = 0; i < num_events; ++i) {
const auto it = epoll_handlers_.find(ev[i].data.fd);
if (it == epoll_handlers_.end()) {
continue;
}
const Info& info = it->second;
if ((info.events & (EPOLLIN | EPOLLPRI)) == (EPOLLIN | EPOLLPRI) &&
(ev[i].events & EPOLLIN) != ev[i].events) {
// This handler wants to know about exception events, and just got one.
// Log something informational.
LOG(ERROR) << "Received unexpected epoll event set: " << ev[i].events;
}
info.handler();
for (auto fd : to_remove_) {
epoll_handlers_.erase(fd);
}
to_remove_.clear();
}
return num_events;
}
1. **初始化变量:**
* 首先,代码初始化一些变量:
* timeout_ms:epoll_wait系统调用的超时时间,单位是毫秒。如果timeout参数没有提供,或者timeout参数大于INT_MAX,则timeout_ms设置为-1,表示没有超时。
* max_events:epoll事件的最大数量。这是epoll_handlers_映射的大小。
* ev:一个epoll_event结构体数组,用于存储epoll事件。
* num_events:epoll_wait系统调用的返回值,表示发生的事件数量。
2. **调用epoll_wait系统调用:**
* 接下来,代码使用epoll_wait系统调用等待epoll事件发生。
* epoll_wait系统调用有四个参数:epoll文件描述符、epoll事件数组、epoll事件的最大数量和超时时间。
* epoll文件描述符是epoll_fd_。
* epoll事件数组是ev。
* epoll事件的最大数量是max_events。
* 超时时间是timeout_ms。
* epoll_wait系统调用会阻塞,直到epoll事件发生或超时。
3. **错误处理:**
* 如果epoll_wait系统调用返回-1,则代码返回一个ErrnoError对象。
* ErrnoError对象包含了错误信息和错误代码。
4. **处理事件:**
* 如果epoll_wait系统调用成功,则代码开始处理发生的事件。
* 首先,如果num_events大于0并且first_callback_不为空,则调用first_callback_函数。
* first_callback_函数是一个回调函数,它将在epoll事件循环中接收到第一个事件时被调用。
* 接下来,代码遍历发生的事件,并对每个事件调用相应的处理程序。
* 处理程序是epoll_handlers_映射中存储的Info结构体中的handler成员。
* 在调用处理程序之前,代码还会检查该事件是否属于该处理程序感兴趣的事件类型。
* 如果该事件不属于该处理程序感兴趣的事件类型,则代码会继续处理下一个事件。
* 在处理完所有发生的事件之后,代码会从epoll_handlers_映射中删除所有需要删除的文件描述符。
* 需要删除的文件描述符存储在to_remove_列表中。
5. **返回结果:**
* 最后,代码返回num_events,表示发生的事件数量。
**总而言之,这段代码用于等待epoll事件循环中的事件发生,并处理这些事件。如果等待成功,则返回发生的事件数量,否则返回ErrnoError对象。**
3. 总结
可见使用了 Epoll 后,只要简单的几个步骤即可,作为框架使用还是相当方便的,其中隐藏了 epoll 的数据构造,用户只要提供 监听对象和对于的触发方法即可。不过值得注意的是,这是单线程的,如果注册太多的监听对象,或者某一个处理方法中耗时过长,还是相当影响其他监听事件的处理的。
Android 系统的第二阶段初始化程序创建一个 Epoll 实例,用于监听文件描述符事件,具体是为了以下几个目的:
- 处理输入/输出事件: Epoll 实例可以监听文件描述符的输入/输出事件,例如,当用户输入数据或应用程序读取数据时。当发生输入/输出事件时,Epoll 实例会通知相应的应用程序或进程进行处理。
- 处理信号事件: Epoll 实例也可以监听信号事件,例如,当应用程序收到终止信号时。当发生信号事件时,Epoll 实例会通知相应的应用程序或进程进行处理。
- 处理定时器事件: Epoll 实例还可以监听定时器事件,例如,当某个定时器到期时。当发生定时器事件时,Epoll 实例会通知相应的应用程序或进程进行处理。
通过使用 Epoll 实例监听文件描述符事件,Android 系统可以及时响应用户的输入、应用程序的请求以及其他事件,从而提高系统的响应速度和性能。
例如,在 Android 系统中,当用户点击屏幕时,系统会将点击事件转换为一个文件描述符事件,然后通过 Epoll 实例通知相应的应用程序或进程进行处理。应用程序或进程收到事件通知后,会根据需要进行相应的操作,例如,打开一个新的窗口或启动一个新的服务。
又例如,在 Android 系统中,定时器服务会使用 Epoll 实例来监听定时器事件。当某个定时器到期时,定时器服务会将定时器事件转换为一个文件描述符事件,然后通过 Epoll 实例通知相应的应用程序或进程进行处理。应用程序或进程收到事件通知后,会根据需要进行相应的操作,例如,执行某个任务或发送一条消息。
总之,Android 系统的第二阶段初始化程序创建一个 Epoll 实例,用于监听文件描述符事件,是为了提高系统的响应速度和性能,并为应用程序提供一种高效的事件处理机制。