Skip to content

Epoll事件循环

从 fd 生命周期讲解 init 的 Epoll 封装、两套事件循环、动态注册、分发与清理。

基于android-17.0.0_r1
AndroidinitEpolleventfdsignalfd

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.h
  • system/core/init/epoll.cpp
  • system/core/init/init.cpp
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

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

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

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 来源eventshandler 读取内容
InstallSignalFdHandlersignalfd`EPOLLINEPOLLPRI`
InstallInitNotifiereventfdEPOLLIN64 位 counter
MountHandler/proc/mounts`EPOLLERREPOLLPRI`
Keychords/dev/input/event*EPOLLINinput_event
property serviceUnix socketEPOLLINproperty 请求或 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

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

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

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

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

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

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

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

cpp
epoll.SetFirstCallback(ReapAnyOutstandingChildren);

Wait() 只要收到至少一个事件,就先执行 first_callback_。它并不要求返回事件中一定包含 SIGCHLD,因此任何 fd 唤醒都顺便清扫已经退出但尚未处理的子进程。

源码文件:system/core/init/epoll.cpp

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

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

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修正掩码后可重试
重复 fdmap 插入拒绝保留原 handler可先注销再注册
epoll_ctl ADD注册未提交回滚 mapfd owner仍可处理
epoll_wait返回错误永久循环记录后重试视 errno 而定
keychord 注册单设备无 handlerwarning热插拔可再次尝试
临时等待注册无 SIGCHLD epoll50ms 轮询 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

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 类型:

sh
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追踪 ​

复现本文时,建议从所有权而不是函数名清单开始:

sh
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 模型是那条路径的前置。