Skip to content

epoll机制

结合 libutils Looper 源码和单测,解释 epoll 注册、等待、序列号、FD 复用与响应失效。

基于android-17.0.0_r1
AndroidepollNative Looper文件描述符源码阅读

epoll机制 ​

本文承接 nativePollOnce详解,不写脱离 Android 的 epoll API 教程,而是追踪 libutils::Looper 如何维护 FD 请求、把序列号写入 epoll_event,并在回调修改或关闭 FD 后拒绝旧 response。

1. 两张表 ​

源码文件:system/core/libutils/include/utils/Looper.h

相关字段:mRequests、mSequenceNumberByFd、mRequestsGeneration

cpp
std::unordered_map<SequenceNumber, Request> mRequests;
std::unordered_map<int, SequenceNumber> mSequenceNumberByFd;
std::atomic<uint64_t> mRequestsGeneration;

struct Request {
    int fd;
    int ident;
    int events;
    sp<LooperCallback> callback;
    void* data;
};

一张表由序列号找到完整 Request,另一张表由 FD 找当前序列号。epoll 的 data.u64 保存序列号而不是裸 FD,避免关闭后快速复用数字造成旧事件误认。

2. 注册流程 ​

源码文件:system/core/libutils/Looper.cpp

相关函数:addFd()

cpp
if (!callback.get()) {
    if (!mAllowNonCallbacks || ident < 0) return -1;
} else {
    ident = POLL_CALLBACK;
}

std::scoped_lock _l(mLock);
const SequenceNumber seq = mNextRequestSeq++;
Request request{fd, ident, events, callback, data};
mRequestsGeneration.fetch_add(1, std::memory_order_release);
epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);

callback 非空时 ident 被强制改为 POLL_CALLBACK;无 callback 只有在允许该模式且 ident 非负时合法。每次添加或替换都增加 generation。

源码文件:system/core/libutils/Looper.cpp

相关函数:addFd()

cpp
auto seq_it = mSequenceNumberByFd.find(fd);
if (seq_it == mSequenceNumberByFd.end()) {
    epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &eventItem);
    mRequests.emplace(seq, request);
    mSequenceNumberByFd.emplace(fd, seq);
} else {
    epoll_ctl(mEpollFd.get(), EPOLL_CTL_MOD, fd, &eventItem);
    // 删除旧序列号,写入新 Request
}

同一个 FD 再注册是替换,不是追加第二个监听器。旧 response 不会自动修改,而是靠序列号和 generation 在消费时失效。

3. 等待返回 ​

源码文件:system/core/libutils/Looper.cpp

相关函数:pollInner()、pollOnce()

cpp
int eventCount = epoll_wait(epollFd, eventItems,
        EPOLL_MAX_EVENTS, timeoutMillis);

for (int i = 0; i < eventCount; i++) {
    const SequenceNumber seq = eventItems[i].data.u64;
    const auto& request_it = mRequests.find(seq);
    if (request_it != mRequests.end()) {
        const auto& request = request_it->second;
        int events = 0;
        if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
        if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
        if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
        if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
        mResponses.push_back({.seq = seq, .events = events, .request = request});
    }
}

查不到序列号的事件被忽略。pollOnce() 下一轮先消费 mResponses,因此一次 epoll_wait 可以产生多个待处理 response。

4. 过期检查 ​

源码文件:system/core/libutils/Looper.cpp

相关函数:isResponseStale()

cpp
bool Looper::isResponseStale(const uint64_t generation,
        const SequenceNumber& seq) const {
    if (mRequestsGeneration.load(std::memory_order_acquire) == generation) {
        return false;
    }
    std::scoped_lock _l(mLock);
    return mRequests.find(seq) == mRequests.end();
}

generation 未变化时走无锁快路径;变化后再确认序列号是否仍在 Request 表。删除或替换 FD 后,旧 response 因此不会调用新 listener。

5. 锁外回调 ​

源码文件:system/core/libutils/Looper.cpp

相关函数:pollInner()

cpp
const uint64_t generation = mRequestsGeneration.load(std::memory_order_acquire);
mLock.unlock();
for (Response& response : mResponses) {
    if (response.request.ident != POLL_CALLBACK) continue;
    if (isResponseStale(generation, response.seq)) continue;
    int callbackResult = response.request.callback->handleEvent(
            response.request.fd, response.events, response.request.data);
    if (callbackResult == 0) {
        std::scoped_lock _l(mLock);
        removeSequenceNumberLocked(response.seq);
    }
    response.request.callback.clear();
}

回调锁外执行,因为它可能关闭、重新注册或删除其他 FD。返回 0 才注销当前请求,callback 引用随后清空。

6. 删除与复用 ​

源码文件:system/core/libutils/Looper.cpp

相关函数:removeFd()、removeSequenceNumberLocked()、scheduleEpollRebuildLocked()

cpp
int Looper::removeFd(int fd) {
    std::scoped_lock _l(mLock);
    auto it = mSequenceNumberByFd.find(fd);
    if (it == mSequenceNumberByFd.end()) return 0;
    return removeSequenceNumberLocked(it->second);
}

// removeSequenceNumberLocked 更新两张表、generation,并执行 EPOLL_CTL_DEL。

当旧 FD 已关闭而同一个数字被新 FD 复用,EPOLL_CTL_MOD 可能返回 ENOENT。源码会尝试 EPOLL_CTL_ADD,并安排 rebuildEpollLocked():重建时关闭旧 epoll,重新注册 wake FD 和当前 Request 表。

7. 单测输入 ​

源码文件:system/core/libutils/Looper_test.cpp

相关测试:PollOnce_WhenNonCallbackFdIsSignalled_ReturnsIdent()、ThreeCallbacksSignalInOnePoll()、PollOnce_RemovePendingFdInCallback()

cpp
pipe.writeSignal();
mLooper->addFd(pipe.receiveFd, expectedIdent,
        Looper::EVENT_INPUT, nullptr, expectedData);
int result = mLooper->pollOnce(100, &fd, &events, &data);
EXPECT_EQ(expectedIdent, result);
EXPECT_EQ(pipe.receiveFd, fd);
EXPECT_EQ(Looper::EVENT_INPUT, events);
EXPECT_EQ(expectedData, data);

这些测试分别验证非 callback response 的 ident/fd/data、同一轮三个 FD 的回调数量,以及一个回调删除其他待处理 FD 后旧 response 被跳过。

8. 动手验证 ​

bash
rg -n "mRequests|mSequenceNumberByFd|mRequestsGeneration|epoll_ctl|epoll_wait" \
  system/core/libutils/Looper.cpp system/core/libutils/include/utils/Looper.h
rg -n "PollOnce_WhenNonCallbackFdIsSignalled|ThreeCallbacksSignalInOnePoll|RemovePendingFdInCallback" \
  system/core/libutils/Looper_test.cpp

9. 边界说明 ​

本文不展开 Linux 内核 epoll 数据结构、pollAll() 或 Java 同步屏障。固定源码支持的是 Looper 对 epoll 的请求表、序列号、response 失效和 FD 复用恢复。