Skip to content

nativeWake详解

从 MessageQueue 的 needWake 判定追到 JNI、eventfd 写入、epoll 唤醒与 awoken 消费。

基于android-17.0.0_r1
AndroidnativeWakeMessageQueueeventfdepoll源码阅读

nativeWake详解 ​

本文承接 nativePollOnce详解。上一文解释了 native Looper 如何等待;本文只追踪“谁决定必须唤醒、唤醒信号如何传到 epoll、信号被谁消费”。重点是区分三件事:Java 队列的 needWake 是业务判断,nativeWake() 是 JNI 转发,eventfd 写入只是让等待者重新检查,并不直接交付 Message。

1. Java判定 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:enqueueMessage()

java
if (p == null || when == 0 || when < p.when) {
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
} else {
    needWake = mBlocked && p.target == null && msg.isAsynchronous();

    if (when >= mLast.when) {
        needWake = needWake && mAsyncMessageCount == 0;
        // 尾部插入
    } else {
        // 中间插入;遇到更早的异步消息后取消唤醒
    }
}

if (needWake) {
    nativeWake(mPtr);
}

Java 只在唤醒可能改变“下一次可交付工作”时写 eventfd:新消息成为队头,或同步屏障在队头且新异步消息可能穿过屏障。Looper 正在执行回调时 mBlocked=false,入队不会额外唤醒;它会在当前回调结束后自然重新取队列。

2. 退出唤醒 ​

源码文件:frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java

相关函数:quit(boolean)、resetForTest()

java
if (safe) {
    removeAllFutureMessagesLocked();
} else {
    removeAllMessagesLocked();
}
nativeWake(mPtr);

退出不是为了让新消息变得可执行,而是把可能阻塞在 native poll 的线程叫醒,让 next() 看到 mQuitting 并返回 null。测试重置也会唤醒,因为清理了队列和 FD 记录后,等待线程需要重新读取状态。

3. JNI转发 ​

源码文件:frameworks/base/core/jni/android_os_MessageQueue.cpp

相关函数:android_os_MessageQueue_nativeWake()、NativeMessageQueue::wake()

cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue =
            reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

JNI 层不重新判断 Java 队列状态,也不读取 mMessages。它只把 Java 保存的 mPtr 转为 NativeMessageQueue*,再转发到该对象持有的 sp<Looper>。

4. 写入eventfd ​

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

相关函数:Looper::wake()

cpp
void Looper::wake() {
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(
            write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d "
                    "(returned %zd): %s", mWakeEventFd.get(),
                    nWrite, strerror(errno));
        }
    }
}

每次唤醒写入一个 uint64_t 增量。eventfd 保存的是计数,不要求每次 Java 调用都对应一次独立 epoll 返回;多个写入可以合并,等待线程一次读取即可消费累计信号。TEMP_FAILURE_RETRY 处理被信号打断的 write;写入失败只有非 EAGAIN 才 fatal。

5. 注册wake ​

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

相关函数:rebuildEpollLocked()

cpp
epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD,
        mWakeEventFd.get(), &wakeEvent);
LOG_ALWAYS_FATAL_IF(result != 0,
        "Could not add wake event fd to epoll instance: %s",
        strerror(errno));

wake FD 在 Looper 构造和 epoll 重建时都作为固定节点注册,使用保留的 WAKE_EVENT_FD_SEQ。epoll 通过序列号识别这是内部唤醒,而不是业务文件描述符。

6. 消费唤醒 ​

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

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

cpp
if (seq == WAKE_EVENT_FD_SEQ) {
    if (epollEvents & EPOLLIN) {
        awoken();
    } else {
        ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.",
                epollEvents);
    }
}

void Looper::awoken() {
    uint64_t counter;
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

awoken() 读取并清空 eventfd 计数。清空之后 pollInner() 仍会继续处理 native Message 和 FD response;Java MessageQueue.next() 返回后还会重新检查自己的链表。因此“读到 wake FD”与“取出 Java Message”之间至少隔着两层状态判断。

7. 不必唤醒 ​

唤醒不是每次入队都需要。以下情况通常不写 eventfd:Looper 正在执行消息,插入的延迟消息没有改变队头到期时间,或者屏障后已经存在更早的异步消息。避免无效唤醒可以减少 epoll 返回和 Java 锁竞争,但 Java 链表仍会记录消息,下一轮自然会处理。

8. 并发合并 ​

多个生产者可能在 Looper 阻塞期间连续调用 nativeWake()。eventfd 的计数语义允许这些写入合并:消费者一次 read() 取走累计值;唤醒协议要求的是“至少重新检查一次”,不是为每个生产者调用保留独立回调次数。消息数量和执行次数仍由 Java 链表决定。

9. 写入失败 ​

eventfd 使用非阻塞模式。计数接近上限时,write 可能返回 EAGAIN;当前源码将它视为可忽略情况,因为 FD 已经处于可读状态,等待线程不需要额外的唤醒边沿。其他 write 错误或短写会进入 fatal 日志路径。TEMP_FAILURE_RETRY 只处理 EINTR 重试,不会把任意错误变成成功。

10. 退出边界 ​

退出调用在设置 mQuitting、清理消息后写 wake;被唤醒的 MessageQueue.next() 先处理已经到期且仍保留的消息,然后检查退出标志,调用 dispose() 并返回 null。wake 本身不取消消息,也不关闭 Java 队列;清理和销毁由 Java MessageQueue 的退出路径负责。

11. 动手验证 ​

bash
rg -n "needWake|nativeWake\(|mQuitting|resetForTest" \
  frameworks/base/core/java/android/os/LegacyMessageQueue/MessageQueue.java
rg -n "nativeWake|NativeMessageQueue::wake" \
  frameworks/base/core/jni/android_os_MessageQueue.cpp
rg -n "void Looper::wake|void Looper::awoken|mWakeEventFd|EAGAIN" \
  system/core/libutils/Looper.cpp

用四种输入阅读分支:队列阻塞时插入新队头、正在处理时插入消息、屏障队头插入异步消息、退出时唤醒。每种情况分别写出 needWake、eventfd 状态、pollInner() 返回后的下一步。

12. 边界说明 ​

本文不展开 eventfd 内核计数上限的数值、epoll 的边沿/水平触发实现细节、pollAll() 循环或 Java MessageQueue 的完整排序。固定源码支持的是 needWake 判定、JNI 转发、eventfd 写入与读取,以及唤醒后的重新检查边界。