epoll 是 Linux 下的「高性能 IO 多路复用器」,专门用来同时监听大量文件描述符(socket、管道、设备等)的读写事件,不阻塞、CPU 占用极低。

一、为什么要用 epoll?

传统的 select/poll 有致命缺陷:

  1. 监听上限低(select 最多 1024 个)
  2. 每次都要遍历所有描述符,连接多了极慢 
  3. 内核到用户空间数据拷贝频繁

总结

  • select/poll:每次全量遍历、全量拷贝、效率低、有上限 → 高并发不行
  • epoll:内核事件驱动、只返回活跃连接、无上限 → 高并发神器

缺陷2解释

缺陷 1:每次都要把整个 fd 集合从用户空间拷贝到内核

你监听 1000 个 socket:

  • 每次调用 select/poll,都要把这 1000 个 fd 发给内核
  • 内核遍历一遍
  • 再把结果拷贝回来

高并发下,拷贝 + 遍历 非常浪费 CPU。

epoll 不需要拷贝:内核里自己维护一个监听列表,你只需要增删改,不用每次全量重发。


缺陷 2:每次都要遍历所有 fd,不管有没有事件

比如你监听 10000 个连接:

  • 只有 1 个 socket 有数据
  • select/poll 仍然要把 10000 个全部扫描一遍

连接越多,越慢,O (n) 复杂度。

epoll 只返回有事件的 fd,不用遍历,O (1) 复杂度。


缺陷 3:select 有硬上限 1024

源码里有个宏:

c

运行

#define __FD_SETSIZE 1024

意味着一个 select 最多监听 1024 个 socket。想改就要重新编译内核,非常麻烦。

poll 没有上限,但效率问题依然存在。

epoll 无硬上限。


缺陷 4:无法告诉用户 “具体哪个 fd 就绪”

select/poll 返回后,你不知道谁就绪,只能从头到尾循环检查一遍,非常蠢。

epoll 直接返回一个数组,里面只有就绪的 fd

select /poll/epoll 核心对比图

epoll 完美解决:

  • 无上限监听
  • 只返回有事件的描述符,不用遍历
  • 内核与用户空间共享内存,零拷贝
  • 支持水平触发 LT + 边缘触发 ET

LT 水平触发:只要缓冲区还有数据,就一直提醒你

ET 边缘触发:只有数据 “刚到来那一刻” 提醒你一次

安卓源码里用哪个

几乎全用 ET(边缘触发)比如:

  • InputReader 触摸事件
  • Looper 消息循环
  • Binder 线程
  • 网络框架

    原因:减少唤醒次数 → 更少 CPU → 更流畅

    二、epoll 3 个核心 API

    1. epoll_create1()

    创建一个 epoll 实例,返回epoll 文件描述符

    // 头文件
    #include <sys/epoll.h>
    
    // 创建 epoll 实例(Android 源码标准用法)
    int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
    // EPOLL_CLOEXEC:进程 fork 后自动关闭这个 fd,防止泄露

    2. epoll_ctl()

    增 / 删 / 改 epoll 监听的文件描述符(核心操作)

    int epoll_ctl(
        int epfd,        // epoll 实例 fd
        int op,          // 操作类型:ADD(添加)、DEL(删除)、MOD(修改)
        int target_fd,   // 要监听的目标 fd(socket/管道等)
        struct epoll_event *event  // 要监听什么事件
    );

    操作类型:

    • EPOLL_CTL_ADD:添加监听
    • EPOLL_CTL_DEL:删除监听
    • EPOLL_CTL_MOD:修改监听事件

    监听事件(常用):

    • EPOLLIN :目标 fd 有数据可读
    • EPOLLOUT:目标 fd 可以写数据
    • EPOLLERR:发生错误
    • EPOLLET :边缘触发(高性能模式)

    3. epoll_wait()

    阻塞等待事件发生,返回有事件的 fd 列表

    三、数据结构:struct epoll_event

    epoll 用它来描述「要监听什么事件 + 关联数据」

    typedef union epoll_data {
        void        *ptr;
        int          fd;    // 最常用:存目标文件描述符
        uint32_t     u32;
        uint64_t     u64;
    } epoll_data_t;
    
    struct epoll_event {
        uint32_t     events;      // 要监听的事件(EPOLLIN等)
        epoll_data_t data;        // 用户数据(一般存目标fd)
    };

    四、完整可运行代码

    这是Android 源码中标准的 epoll 用法模板

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/epoll.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    
    // 最大同时处理事件数
    #define MAX_EVENTS 10
    
    int main() {
        // ======================================
        // 步骤1:创建 epoll 实例
        // ======================================
        // EPOLL_CLOEXEC:子进程不继承,防止fd泄露(Android 强制要求)
        int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
        if (epoll_fd == -1) {
            perror("epoll_create1 失败");
            exit(1);
        }
        printf("epoll 实例创建成功,fd = %d\n", epoll_fd);
    
        // ======================================
        // 步骤2:创建一个测试用管道(模拟要监听的IO)
        // ======================================
        int pipe_fd[2];
        if (pipe(pipe_fd) == -1) {
            perror("pipe 创建失败");
            exit(1);
        }
        int read_fd = pipe_fd[0];  // 读端
        int write_fd = pipe_fd[1]; // 写端
    
        // ======================================
        // 步骤3:配置要监听的事件
        // ======================================
        struct epoll_event event;
        memset(&event, 0, sizeof(event));
    
        // 监听:可读事件 + 边缘触发(ET,Android 高性能首选)
        event.events = EPOLLIN | EPOLLET;
        // 把要监听的 fd 存进去,epoll_wait 会返回给我们
        event.data.fd = read_fd;
    
        // ======================================
        // 步骤4:将目标fd添加到epoll监听列表
        // ======================================
        if (epoll_ctl(
                epoll_fd,                // epoll 实例
                EPOLL_CTL_ADD,           // 操作:添加监听
                read_fd,                 // 要监听的fd
                &event                   // 监听事件配置
            ) == -1) {
            perror("epoll_ctl 添加失败");
            close(epoll_fd);
            exit(1);
        }
        printf("成功添加 fd %d 到 epoll 监听\n", read_fd);
    
        // ======================================
        // 步骤5:往管道写点数据(触发可读事件)
        // ======================================
        const char *test_data = "hello epoll!";
        write(write_fd, test_data, strlen(test_data));
    
        // ======================================
        // 步骤6:循环等待事件(Android 源码主循环逻辑)
        // ======================================
        struct epoll_event events[MAX_EVENTS]; // 存放触发的事件
        while (1) {
            printf("\n等待事件中...\n");
    
            // 阻塞等待事件,-1=永久等待
            int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
            if (n == -1) {
                perror("epoll_wait 失败");
                break;
            }
    
            // 遍历所有触发的事件(只遍历有事件的,极快!)
            for (int i = 0; i < n; i++) {
                int trigger_fd = events[i].data.fd; // 拿到触发的fd
                uint32_t trigger_events = events[i].events; // 触发的事件类型
    
                // 判断:是不是可读事件
                if (trigger_events & EPOLLIN) {
                    printf("触发事件:fd %d 有数据可读\n", trigger_fd);
    
                    // 读取数据
                    char buf[1024];
                    ssize_t len = read(trigger_fd, buf, sizeof(buf)-1);
                    if (len > 0) {
                        buf[len] = '\0';
                        printf("读取到数据:%s\n", buf);
                    }
                }
            }
        }
    
        // 关闭资源
        close(read_fd);
        close(write_fd);
        close(epoll_fd);
        return 0;
    }

    五、Android 源码里真实的 epoll 用法

    1. Android Input 系统(触摸 / 按键)

    1. Android Input 系统(触摸 / 按键)

    源码路径:frameworks/native/services/inputflinger/InputReader.cpp

    // 简化版源码
    void InputReader::loopOnce() {
        // 用 epoll 监听所有输入设备(触摸屏、按键)
        int eventCount = epoll_wait(mEpollFd, mPendingEvents, MAX_EVENTS, -1);
        for (int i = 0; i < eventCount; i++) {
            int fd = mPendingEvents[i].data.fd;
            // 读取触摸/按键事件
            InputDevice* device = getDeviceByFd(fd);
            device->process();
        }
    }

    2. Android init 进程(系统启动核心)

    源码路径:system/core/init/epoll.cpp

    // init 进程用 epoll 监听所有子进程、设备、socket
    int Epoll::RegisterHandler(int fd, Handler* handler, uint32_t events) {
        epoll_event ev = {};
        ev.events = events;
        ev.data.ptr = handler;
        return epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev);
    }

    六、2 种触发模式(面试 + 源码必问)

    1. 水平触发 LT(默认)

    • 只要 fd 有数据,一直触发
    • 简单、不容易丢数据
    • 适合新手

    2. 边缘触发 ET(Android 源码首选)

    • 只有状态变化时才触发一次(比如从无数据 → 有数据)
    • 必须一次性读完所有数据
    • 性能极高,Android 系统全用这个

    七、epoll 核心流程总结

    1. epoll_create1 → 创建 epoll 实例
    2. 配置 epoll_event → 监听什么事件
    3. epoll_ctl(ADD) → 把 fd 加入监听
    4. epoll_wait → 阻塞等事件
    5. 遍历返回的事件 → 处理读写
    6. epoll_ctl(DEL) → 不需要时删除监听
    7. 关闭 fd → 释放资源

    总结

    1. epoll 是 Android/Linux 高性能 IO 监听核心
    2. 只靠 3 个 API:create1 → ctl → wait
    3. Android 源码全用边缘触发 (ET)
    4. 只处理有事件的 fd,不浪费性能

    Logo

    有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

    更多推荐