Epoll事件循环
本文面向已经读过 Init主循环 的读者。前文回答 PID 1 如何在动作、服务截止时间和外部事件之间调度;本文把视角收窄到一个 fd 的完整生命:谁创建它,谁注册 handler,哪个生产者让它就绪,epoll_wait 返回后谁消费数据,注销时又由谁清理。
本文不会把 epoll 内核红黑树、ready list 或 EPOLLET 实现当成 init 源码的一部分,也不会提前展开 SIGCHLD 回收和 property set 协议。读完后,读者应能区分 init 主线程与 property service 线程的两套 Epoll,解释 eventfd、signalfd、/proc/mounts 和 input fd 分别承载什么事件,并能从“producer 写入”一直追到业务 handler,而不是只停留在 RegisterHandler() 函数签名。
1. 对象边界
1.1 封装对象
system/core/init/epoll.h 中的 Epoll 很小,但它同时拥有两类资源:epoll_fd_ 是内核 epoll 实例的文件描述符,epoll_handlers_ 是用户态从业务 fd 到 C++ 回调的映射。内核只保存 .data.fd,不知道 std::function。
相关源码:
system/core/init/epoll.hsystem/core/init/epoll.cppsystem/core/init/init.cpp
class Epoll {
public:
typedef std::function<void()> Handler;
Result<void> Open();
Result<void> RegisterHandler(int fd, Handler handler,
uint32_t events = EPOLLIN);
Result<void> UnregisterHandler(int fd);
void SetFirstCallback(std::function<void()> first_callback);
Result<int> Wait(std::optional<std::chrono::milliseconds> timeout);
private:
struct Info {
Handler handler;
uint32_t events;
};
android::base::unique_fd epoll_fd_;
std::map<int, Info> epoll_handlers_;
std::function<void()> first_callback_;
std::unordered_set<int> to_remove_;
};unique_fd 决定 epoll 实例随对象析构关闭;业务 fd 不归 Epoll 所有。Epoll 只登记整数 fd 和 handler,真正关闭 input fd、inotify fd 或 /proc/mounts 的仍是 Keychords、MountHandler 等创建者。把“监听”误解成“拥有 fd”,很容易造成重复 close 或泄漏。
1.2 三个实例
Android 17 的 init 代码至少有三种不同生命周期的 Epoll:
| 实例 | 所在线程 | 生命周期 | 主要消费者 |
|---|---|---|---|
SecondStageMain 局部对象 | init 主线程 | 第二阶段到 PID 1 结束 | signal、eventfd、mount、keychord handler |
PropertyServiceThread 局部对象 | 两个属性线程各一份 | 属性线程永久循环 | property client socket、内部 init socket |
WaitToBeReaped 局部对象 | 调用线程 | 等待指定 PID 的时间窗 | 临时 SIGCHLD handler |
这张表纠正一个常见旧结论:property_fd 不是注册在 init 主线程 Epoll 上的“属性变化 fd”。它是 socketpair 的 init 端,用于把 InitMessage 发给 property service;接收端 init_socket 注册在 system property service 线程自己的 Epoll 上。
三者复用同一个 C++ 封装,但注册集合、阻塞条件和失败策略都不同。阅读时必须先定位 Epoll 对象属于哪个线程,再讨论某个 fd 是否“会唤醒 init”。
2. 创建实例
2.1 Open
system/core/init/epoll.cpp 的 Open() 是幂等操作。已持有合法 fd 时直接返回;否则使用 EPOLL_CLOEXEC 创建实例。
源码文件:system/core/init/epoll.cpp
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 {};
}EPOLL_CLOEXEC 作用于 epoll 实例自身,避免它被 init 启动的子进程继承。它不替业务 fd 自动添加 close-on-exec;因此 signal、eventfd、socket 和 input fd 的创建点仍分别使用 SFD_CLOEXEC、EFD_CLOEXEC、SOCK_CLOEXEC 或 O_CLOEXEC。
2.2 失败策略
主线程和 property service 线程没有可替代的轮询路径,Open() 失败均升级为 fatal。相反,WaitToBeReaped() 只是关闭流程中的辅助等待:它在 Open() 或注册失败时把 sigchld_fd 设为 -1,退化为每 50 ms sleep_for() 后重新 reap。
源码文件:system/core/init/sigchld_handler.cpp
if (auto result = epoll.Open(); result.ok()) {
result = epoll.RegisterHandler(
sigchld_fd, [sigchld_fd]() { HandleSignal(sigchld_fd); });
if (!result.ok()) {
LOG(WARNING) << "RegisterHandler() failed. Falling back to sleep_for()";
sigchld_fd = -1;
}
} else {
LOG(WARNING) << "Epoll::Open() failed. Falling back to sleep_for()";
sigchld_fd = -1;
}同一个 API 的错误不能脱离调用场景解释:核心事件循环没有 epoll 就无法维持 PID 1 语义,临时等待器则可以用精度较低的轮询恢复。
3. 注册事务
3.1 两份状态
RegisterHandler() 先写用户态 map,再调用 epoll_ctl(ADD)。这样在 epoll_ctl 成功后,任何返回事件都能找到 handler;如果系统调用失败,则删除刚插入的 map 项完成回滚。
源码文件:system/core/init/epoll.cpp
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{.handler = std::move(handler), .events = events});
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 {};
}可以把它看成一个小事务:map 插入是准备态,EPOLL_CTL_ADD 是提交点,失败后的 erase 是回滚。重复 fd 在 map 插入阶段就被拒绝,因此一个 Epoll 实例中同一 fd 只有一个 handler owner。
3.2 事件掩码
默认掩码是 EPOLLIN。SIGCHLD 和容器 SIGTERM 额外请求 EPOLLPRI;MountHandler 对 /proc/mounts 请求 EPOLLERR | EPOLLPRI。事件掩码属于注册者的协议,不由 Epoll 猜测。
| 注册者 | fd 来源 | events | handler 读取内容 |
|---|---|---|---|
InstallSignalFdHandler | signalfd | `EPOLLIN | EPOLLPRI` |
InstallInitNotifier | eventfd | EPOLLIN | 64 位 counter |
MountHandler | /proc/mounts | `EPOLLERR | EPOLLPRI` |
Keychords | /dev/input/event* | EPOLLIN | input_event |
| property service | Unix socket | EPOLLIN | property 请求或 InitMessage |
Epoll::Wait() 只对同时注册 EPOLLIN | EPOLLPRI 且收到异常组合的情况记录错误,它不会把 events 传给无参 handler。handler 要通过读取自己的 fd 来判断和消费具体数据。
4. 主线程FD
4.1 SignalFd
InstallSignalFdHandler() 注册已有的 Service::GetSigchldFd()。在不具备 reboot 能力的容器环境,还创建并注册 SIGTERM signalfd。
源码文件:system/core/init/init.cpp
static Result<void> RegisterSignalFd(Epoll* epoll, int signal, int fd) {
return epoll->RegisterHandler(
fd, [signal]() { HandleSignalFd(signal); }, EPOLLIN | EPOLLPRI);
}
static Result<int> CreateAndRegisterSignalFd(Epoll* epoll, int signal) {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, signal);
if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
return ErrnoError() << "failed to block signal " << signal;
}
unique_fd signal_fd(signalfd(-1, &mask, SFD_CLOEXEC));
if (signal_fd.get() < 0) {
return ErrnoError() << "failed to create signalfd for signal " << signal;
}
OR_RETURN(RegisterSignalFd(epoll, signal, signal_fd.get()));
return signal_fd.release();
}先 block signal 再创建 signalfd,保证信号转为可由 fd 消费的队列事件。release() 把 fd 所有权从局部 unique_fd 转给全局 sigterm_fd;若注册失败,局部对象析构自动 close。
4.2 EventFd
property callback、控制消息生产者等可能运行在其他线程。它们不直接调用主循环逻辑,而是写 wake_main_thread_fd。这个 fd 使用 eventfd counter,只表达“需要重新检查状态”,不承载每条业务消息。
源码文件:system/core/init/init.cpp
static void InstallInitNotifier(Epoll* epoll) {
wake_main_thread_fd = eventfd(0, EFD_CLOEXEC);
if (wake_main_thread_fd == -1) {
PLOG(FATAL) << "Failed to create eventfd for waking init";
}
auto clear_eventfd = [] {
uint64_t counter;
TEMP_FAILURE_RETRY(read(wake_main_thread_fd, &counter, sizeof(counter)));
};
if (auto result = epoll->RegisterHandler(wake_main_thread_fd, clear_eventfd);
!result.ok()) {
LOG(FATAL) << result.error();
}
}
static void WakeMainInitThread() {
uint64_t counter = 1;
TEMP_FAILURE_RETRY(write(wake_main_thread_fd, &counter, sizeof(counter)));
}多个 producer 在主线程醒来前连续写入会累加 counter;handler 一次读取清空当前计数。源码注释明确说明 init 不关心写了多少次,只关心至少有一次唤醒。业务状态仍保存在有锁队列、property waiter 或 ActionManager 中。
4.3 MountFd
MountHandler 在构造时打开 /proc/mounts,将 FILE* 的 fd 注册到主线程 Epoll。事件到达后,它 rewind() 并重新读取完整 mount 表,与旧集合比较,而不是把 epoll event 本身当成挂载记录。
源码文件:system/core/init/mount_handler.cpp
MountHandler::MountHandler(Epoll* epoll)
: epoll_(epoll), fp_(fopen("/proc/mounts", "re"), fclose) {
if (!fp_) PLOG(FATAL) << "Could not open /proc/mounts";
auto result = epoll->RegisterHandler(
fileno(fp_.get()),
[this]() { this->MountHandlerFunction(); },
EPOLLERR | EPOLLPRI);
if (!result.ok()) LOG(FATAL) << result.error();
}
MountHandler::~MountHandler() {
if (fp_) epoll_->UnregisterHandler(fileno(fp_.get()));
}构造顺序让 MountHandler 成为 /proc/mounts fd 和 handler 捕获对象的 owner。析构时必须先注销,随后 fp_ 才能关闭 fd;否则 Epoll 的 map 会保留捕获已析构 this 的回调。
4.4 KeychordFd
Keychords 是动态集合:启动时扫描 /dev/input,符合按键能力的设备 fd 注册到 epoll;inotify 监控设备创建和删除,热插拔时继续增删注册。
源码文件:system/core/init/keychords.cpp
if (auto result = epoll_->RegisterHandler(
fd, [this, fd]() { this->LambdaHandler(fd); }); !result.ok()) {
LOG(WARNING) << "Could not register keychord epoll handler: " << result.error();
return false;
}
void Keychords::GeteventCloseDevice(const std::string& device) {
auto it = registration_.find(device);
if (it == registration_.end()) return;
auto fd = it->second;
epoll_->UnregisterHandler(fd);
registration_.erase(it);
::close(fd);
}这里的清理顺序是“注销 handler → 从 owner 容器删除 → close fd”。注册失败只让该 input 设备不可用于 keychord,并记录 warning,不会终止 init;它与 SIGCHLD 注册失败的 fatal 策略不同。
5. 属性线程
5.1 SocketPair
StartPropertyService() 创建 SOCK_SEQPACKET socketpair。from_init_socket 留给 init 侧的 SendMessage(),init_socket 留给 property service 的 system socket 线程。
源码文件:system/core/init/property_service.cpp
void StartPropertyService(int* epoll_socket) {
InitPropertySet("ro.property_service.version", "2");
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {
PLOG(FATAL) << "Failed to socketpair() between property_service and init";
}
*epoll_socket = from_init_socket = sockets[0];
init_socket = sockets[1];
StartSendingMessages();
StartThread(PROP_SERVICE_FOR_SYSTEM_NAME, 0660, AID_SYSTEM,
property_service_for_system_thread, true);
StartThread(PROP_SERVICE_NAME, 0666, 0,
property_service_thread, false);
}两个公开 socket 各有一个线程,只有 listen_init=true 的 system property 线程把 init_socket 加入自己的 epoll。这避免两个线程同时竞争读取内部消息。
5.2 独立循环
PropertyServiceThread() 为每个公开监听 socket创建独立 Epoll。公开 fd 绑定 handle_property_set_fd;system 线程再绑定 HandleInitSocket。
源码文件:system/core/init/property_service.cpp
static void PropertyServiceThread(int fd, bool listen_init) {
Epoll epoll;
if (auto result = epoll.Open(); !result.ok()) {
LOG(FATAL) << result.error();
}
if (auto result = epoll.RegisterHandler(
fd, std::bind(handle_property_set_fd, fd)); !result.ok()) {
LOG(FATAL) << result.error();
}
if (listen_init) {
if (auto result = epoll.RegisterHandler(init_socket, HandleInitSocket);
!result.ok()) {
LOG(FATAL) << result.error();
}
}
while (true) {
auto epoll_result = epoll.Wait(std::nullopt);
if (!epoll_result.ok()) LOG(ERROR) << epoll_result.error();
}
}这套循环没有 action queue 和 service restart deadline,因此 timeout 永远是 nullopt。它被 client socket 或内部 socket 唤醒后直接执行对应 handler;属性变化若需要让 init 主线程重新检查动作,再通过 WakeMainInitThread() 跨线程通知。
5.3 消息闭环
SendLoadPersistentPropertiesMessage() 从 init 侧向 property_fd 写 protobuf InitMessage;property system 线程的 init_socket 就绪;HandleInitSocket() 读取并加载持久属性,最终设置 ro.persistent_properties.ready=true。
这里跨越两次不同的 fd 通知:socketpair 把 init 命令送进 property 线程,eventfd 再把 property 状态变化通知回 init 主线程。把两者统称为 property_fd 会丢失 owner、方向和数据语义。
6. Wait分发
6.1 超时换算
Epoll::Wait() 把缺省 timeout 转成 -1,表示无限等待;只有存在 timeout 且小于 INT_MAX 才转换为 int。init 主循环在调用前已把时间差限制为非负毫秒。
源码文件:system/core/init/epoll.cpp
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";
}TEMP_FAILURE_RETRY 处理被信号中断的系统调用。返回零代表 timeout 到期,没有 handler 被调用;返回正数才进入优先回调和逐 fd 分发。
6.2 优先回调
init 主线程在注册 handler 后调用:
源码文件:system/core/init/init.cpp
epoll.SetFirstCallback(ReapAnyOutstandingChildren);Wait() 只要收到至少一个事件,就先执行 first_callback_。它并不要求返回事件中一定包含 SIGCHLD,因此任何 fd 唤醒都顺便清扫已经退出但尚未处理的子进程。
源码文件:system/core/init/epoll.cpp
if (num_events > 0 && first_callback_) {
first_callback_();
}这个顺序保护服务状态:后续 control message 或其他 handler 观察服务前,init 先尽可能更新退出状态。property service 的 Epoll 没有设置 first callback,因此不会执行 child reap。
6.3 逐项分发
Wait() 用内核返回的 fd 查 epoll_handlers_。找不到时跳过;找到后调用无参 handler。每次 handler 返回后,才清理 to_remove_。
源码文件:system/core/init/epoll.cpp
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) {
LOG(ERROR) << "Received unexpected epoll event set: " << ev[i].events;
}
info.handler();
for (auto fd : to_remove_) {
epoll_handlers_.erase(fd);
}
to_remove_.clear();
}handler 是同步执行的。某个 handler 阻塞时,同一 Epoll 的其余 fd 无法并行分发;因此 handler 应读取必要数据、更新状态并尽快返回。property 持久化在可选配置下转交 PersistWriteThread,就是避免同步 fsync 长时间占住 property handler。
7. 注销清理
7.1 延迟删除
UnregisterHandler() 先从内核 epoll 删除 fd,再确认用户态 map 中存在,最后只把 fd 放入 to_remove_。
源码文件:system/core/init/epoll.cpp
Result<void> Epoll::UnregisterHandler(int fd) {
if (epoll_ctl(epoll_fd_.get(), EPOLL_CTL_DEL, fd, nullptr) == -1) {
return ErrnoError() << "epoll_ctl failed to remove fd";
}
auto it = epoll_handlers_.find(fd);
if (it == epoll_handlers_.end()) {
return Error() << "Attempting to remove epoll handler for FD without an existing handler";
}
to_remove_.insert(it->first);
return {};
}延迟删除不是为了多线程并发修改 map;当前实现没有给 Epoll map 加锁。它解决的是 handler 自己注销时的对象生命周期:info.handler() 仍在执行,不能在它返回前销毁承载当前调用的 std::function。
7.2 同批事件
一个 handler 可以注销另一个已在本批 ev[] 中的 fd。当前 handler 返回后 to_remove_ 被清理;循环随后查找另一个 fd 时找不到 map 项并跳过。这就是 find == end 分支的实际价值之一。
7.3 Owner析构
MountHandler 和 Keychords 的析构或设备删除路径负责调用 UnregisterHandler(),然后关闭自己拥有的 fd。Epoll 析构只关闭 epoll 实例,不能代替业务 owner 的清理逻辑。若业务对象先析构却未注销,map 中 lambda 捕获的 this 会悬空。
8. 失败矩阵
| 失败点 | 直接结果 | 上层策略 | 是否恢复 |
|---|---|---|---|
epoll_create1 | 没有事件实例 | 主循环/property 线程 fatal | 核心路径不恢复 |
| events 为 0 | 注册拒绝 | 调用者记录或 fatal | 修正掩码后可重试 |
| 重复 fd | map 插入拒绝 | 保留原 handler | 可先注销再注册 |
epoll_ctl ADD | 注册未提交 | 回滚 map | fd owner仍可处理 |
epoll_wait | 返回错误 | 永久循环记录后重试 | 视 errno 而定 |
| keychord 注册 | 单设备无 handler | warning | 热插拔可再次尝试 |
| 临时等待注册 | 无 SIGCHLD epoll | 50ms 轮询 reap | 有降级路径 |
| handler 阻塞 | 同实例不再分发 | 无抢占机制 | handler 返回后恢复 |
“Epoll 失败”不是一种统一故障。定位时先确认失败属于创建、注册、等待还是业务 handler,再根据 owner 判断是 fatal、warning、回滚还是轮询降级。
9. Epoll测试
9.1 自注销测试
epoll_test.cpp 的 UnregisterHandler 用 pipe 模拟可读 fd。handler 捕获 CatchDtor,在回调内部注销读端,并断言当前 std::function 的捕获对象仍存在。
源码文件:system/core/init/epoll_test.cpp
auto handler = [&, catch_dtor]() -> void {
auto result = epoll.UnregisterHandler(fds[0]);
ASSERT_EQ(result.ok(), !handler_invoked);
handler_invoked = true;
ASSERT_NE(sValidObjects.find((void*)&catch_dtor), sValidObjects.end());
};
epoll.RegisterHandler(fds[0], std::move(handler));
uint8_t byte = 0xee;
ASSERT_TRUE(android::base::WriteFully(fds[1], &byte, sizeof(byte)));
auto epoll_result = epoll.Wait({});
ASSERT_RESULT_OK(epoll_result);
ASSERT_EQ(*epoll_result, 1);
ASSERT_TRUE(handler_invoked);输入是 pipe 的一个字节,核心断言是返回一个事件、handler 被调用且捕获对象未提前析构。它证明延迟删除的局部生命周期,不证明多线程同时注册/注销安全,也不覆盖 first callback 顺序。
9.2 Keychord测试
keychords_test.cpp 的 TestFrame 自己创建 Epoll,通过测试 input 设备写按键,再由 RelaxForMs() 调用 epoll.Wait(wait) 驱动 handler。keys_in_series 断言串行按键不能组成并行 chord,keys_in_parallel 断言同时按下的组合能被回调观察。
这些测试反向证明 input fd 注册、等待和业务 handler 能形成闭环;它们不证明真实设备一定启用 ADB,也不证明 HandleKeychord() 最终能够启动目标服务,因为生产处理还受 init.svc.adbd=running 和 service 配置约束。
9.3 设备验证
无需修改系统即可观察 owner 和 fd 类型:
adb shell 'ls -l /proc/1/fd | grep -E "anon_inode|/proc/[0-9]+/mounts|/dev/input"'
adb shell 'cat /proc/1/fdinfo/* 2>/dev/null | grep -E "eventfd-count|tfd:"'
adb shell 'ps -AT -p 1'不同内核和权限策略可能隐藏部分 /proc/1/fd 信息。看到 anon_inode:[eventpoll] 只能证明 epoll fd 存在,不能直接确定其中注册了哪些 handler;fdinfo 的 tfd 可提供内核注册集合,但仍不能显示用户态 lambda 的内容。
10. FD追踪
复现本文时,建议从所有权而不是函数名清单开始:
rg -n "Epoll epoll|PropertyServiceThread|WaitToBeReaped" system/core/init
rg -n "RegisterHandler|UnregisterHandler|SetFirstCallback" system/core/init
rg -n "WakeMainInitThread|HandleInitSocket|MountHandlerFunction|LambdaHandler" \
system/core/init先为每个 Epoll epoll 标记线程和生命周期,再为每个注册点记录 fd owner → events → handler → 读取数据 → 状态消费者 → 清理函数。如果某类事件没有生效,可沿下列次序定位:producer 是否写入、fd 是否仍打开、是否注册在正确实例、epoll_wait 是否返回、handler 是否消费数据、业务状态是否又唤醒了另一个线程。
本文没有解释 waitid/waitpid 如何更新 Service、property client 请求如何鉴权,也没有讨论 epoll 内核实现。下一篇将沿 SIGCHLD 的真实回收路径,从 signalfd 读取一直追到 Service::Reap()、重启状态和临时服务清理;这里建立的 fd owner 与 first callback 模型是那条路径的前置。
