Android EventHub的Epoll原理 笔记

EventHub对epoll的应用,本质上是用"统一等待队列"替换了"零散忙等" 。它的精妙不在于发明新用法,而在于将三类性质完全不同的FD(设备节点、文件系统事件、唤醒管道)纳入同一个epoll实例,用一套机制解决所有"等什么、什么时候等、被谁唤醒"的问题。

直接看源码骨架和运行时流,比纯文字描述更清晰。


🔧 一、核心数据结构与初始化(静力学结构)

EventHub构造函数中关于epoll的部分可拆解为1个实例 + 3个监听源

cpp 复制代码
// frameworks/native/services/inputflinger/reader/EventHub.cpp
EventHub::EventHub(void) {
    // 1. 创建唯一的epoll实例(内核中对应一个红黑树+就绪链表)
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);  // 参数内核2.6.8后忽略,>0即可

    // 2. 创建inotify实例,监听/dev/input目录增删
    mINotifyFd = inotify_init();
    inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    
    // 3. **关键动作**:将inotify fd加入epoll
    epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, {EPOLLIN, EPOLL_ID_INOTIFY});

    // 4. 创建唤醒管道(pipe),本质是内核缓冲队列
    pipe(wakeFds);  // mWakeReadPipeFd / mWakeWritePipeFd
    
    // 5. **关键动作**:将管道读端加入epoll
    epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, {EPOLLIN, EPOLL_ID_WAKE});

    // 6. 设备fd的加入不在这里,由openDeviceLocked()在扫描/热插拔时执行
}

关键理解

  • EPOLL_ID_INOTIFYEPOLL_ID_WAKE 不是FD,是存储在epoll_event.data.u32自定义标签 。epoll返回时,通过这个标签立刻知道是谁醒了,不需要遍历
  • 设备FD对应的data.fd存的是设备FD本身,通过getDeviceByFdLocked()反查设备上下文 。

⚙️ 二、运行时核心:getEvents()中的epoll等待(动力学)

getEvents()是InputReader线程的主循环体,epoll_wait是它的心脏搏动点

cpp 复制代码
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    for (;;) {
        // ... 前置处理(设备移除/添加事件先返回)...
        
        // ★★★ 核心阻塞点 ★★★
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        mPendingEventCount = size_t(pollResult);
        mPendingEventIndex = 0;  // 从头处理这批事件

        // 处理本轮epoll返回的所有就绪FD
        while (mPendingEventIndex < mPendingEventCount) {
            const epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
            uint32_t epollId = eventItem.data.u32;  // 或 data.fd

            if (epollId == EPOLL_ID_INOTIFY) {
                // 设备增删 → 调readNotifyLocked(),最终open/close设备
                mPendingINotify = true;
            } else if (epollId == EPOLL_ID_WAKE) {
                // 主动唤醒(如IMS重配置),读空管道即可
                awoken = true;
            } else {
                // ★ 普通输入设备有数据!
                int fd = eventItem.data.fd;
                if (eventItem.events & EPOLLIN) {
                    // 从设备节点read原始input_event
                    read(fd, readBuffer, sizeof(struct input_event) * capacity);
                    // 转换成RawEvent,填充到buffer返回上层
                }
                if (eventItem.events & EPOLLHUP) {
                    // 设备挂载点消失,标记关闭
                }
            }
        }
        
        // 如果buffer已满或有重要事件,return到InputReader
        if (event - buffer > 0) return event - buffer;
    }
}

核心优势

  • 一次等待,多个来源 :不论是有新的触摸报点、有人插拔USB键盘、还是SystemServer要求重新扫描,都在同一个epoll_wait上醒来。避免了select/poll的FD集合全量传递,时间复杂度从O(n)降到近似O(1)(仅返回就绪的少量FD)。
  • 边缘触发(隐含) :虽然EventHub设置为EPOLLIN(电平触发),但read()设备节点时一般读完所有数据,符合边缘触发的处理习惯------不读完就一直触发

🔌 三、设备插拔:inotify→epoll的回声链路

这是一个双栈监听的精巧设计 :

  1. 第一层(inotify)/dev/input目录被watch,当有新eventX节点创建或删除,内核向mINotifyFd写入inotify_event结构体。
  2. 第二层(epoll)mINotifyFd被epoll监听,当它可读时,epoll_wait返回。
  3. EventHub读到IN_CREATE后,调用openDeviceLocked()
    • open()设备节点获取FD
    • 再次调用epoll_ctl(EPOLL_CTL_ADD)将此设备FD加入epoll
    • 至此,新设备也进入了"统一等待池"

等效图

复制代码
物理插入 → 内核创建设备节点 → inotify上报 → epoll发现inotify可读 → EventHub打开设备 → 设备FD加入epoll → 下次触摸事件直接通过设备FD唤醒epoll

整个过程无需轮询,完全事件驱动


🚰 四、唤醒管道(wake pipe):epoll的自中断机制

epoll_wait是阻塞的,但某些场景需要主动打断等待 (例如IMS需要立即重新扫描设备)。EventHub没有使用pthread_cond_signal之类的跨线程信号,而是用管道写操作模拟FD就绪

cpp 复制代码
void EventHub::wake() {
    // 仅仅是向管道写入一个字节
    write(mWakeWritePipeFd, "W", 1);
}

由于管道读端在epoll中监听了EPOLLIN,写入操作立即触发epoll_wait返回 ,且返回的事件ID为EPOLL_ID_WAKE

为什么不用signal?

管道是FD,天然融入epoll模型,不干扰其他等待的FD,且线程安全。这是Linux下**自唤醒(self-pipe trick)**的经典实现。


📊 五、与原始poll/select的性能对比

特性 epoll (EventHub实现) poll/select
监听FD数量 无上限(取决于系统) FD_SETSIZE(1024)或线性扫描
就绪FD获取 仅返回就绪列表,O(K) 全量传入传出,O(N)
每次调用拷贝 仅首次epoll_ctl注册 每次调用都要拷贝全部FD集合
内核时间复杂度 O(1)(回调机制) O(N)(线性遍历)
EventHub特殊点 混合监听设备、inotify、管道 无法混合,需多个循环

数据佐证

  • 触控屏采样率可达240Hz,每个事件都需要从内核→EventHub。epoll的回调机制(内核就绪时直接放入链表)比poll每次重新线性扫描节省大量CPU
  • Android 4.4之前使用poll,之后全面切换为epoll + wake pipe,就是为了解决高采样率下的性能瓶颈 。

🧠 六、总结一句话

EventHub的epoll本质是:将所有可等待资源(设备、目录、控制信号)统一抽象为"文件描述符",让内核充当仲裁者------谁就绪,谁上报,绝不空转。

这种设计使得InputReader线程可以在没有任何输入事件时完全休眠,不占CPU;事件爆发时瞬间唤醒,逐个消化,是Android触摸"跟手性"的底层基石之一。

相关推荐
Kapaseker1 天前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴1 天前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe2 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农2 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少2 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker2 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋2 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我3 天前
让我们实现一个更好看的内部阴影按钮
android·flutter