epoll
epoll 是 Linux 下的「高性能 IO 多路复用器」专门用来同时监听大量文件描述符socket、管道、设备等的读写事件不阻塞、CPU 占用极低。一、为什么要用 epoll传统的select/poll有致命缺陷监听上限低select 最多 1024 个每次都要遍历所有描述符连接多了极慢内核到用户空间数据拷贝频繁总结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) 复杂度。缺陷 3select 有硬上限 1024源码里有个宏c运行#define __FD_SETSIZE 1024意味着一个 select 最多监听 1024 个 socket。想改就要重新编译内核非常麻烦。poll 没有上限但效率问题依然存在。epoll 无硬上限。缺陷 4无法告诉用户 “具体哪个 fd 就绪”select/poll 返回后你不知道谁就绪只能从头到尾循环检查一遍非常蠢。epoll 直接返回一个数组里面只有就绪的 fdselect /poll/epoll 核心对比图epoll 完美解决无上限监听只返回有事件的描述符不用遍历内核与用户空间共享内存零拷贝支持水平触发 LT边缘触发 ETLT 水平触发只要缓冲区还有数据就一直提醒你ET 边缘触发只有数据 “刚到来那一刻” 提醒你一次安卓源码里用哪个几乎全用 ET边缘触发比如InputReader 触摸事件Looper 消息循环Binder 线程网络框架原因减少唤醒次数 → 更少 CPU → 更流畅二、epoll 3 个核心 API1.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, // 要监听的目标 fdsocket/管道等 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_eventepoll 用它来描述「要监听什么事件 关联数据」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)); // 监听可读事件 边缘触发ETAndroid 高性能首选 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. 边缘触发 ETAndroid 源码首选只有状态变化时才触发一次比如从无数据 → 有数据必须一次性读完所有数据性能极高Android 系统全用这个七、epoll 核心流程总结epoll_create1→ 创建 epoll 实例配置epoll_event→ 监听什么事件epoll_ctl(ADD)→ 把 fd 加入监听epoll_wait→ 阻塞等事件遍历返回的事件 → 处理读写epoll_ctl(DEL)→ 不需要时删除监听关闭 fd → 释放资源总结epoll 是 Android/Linux 高性能 IO 监听核心只靠 3 个 APIcreate1→ctl→waitAndroid 源码全用边缘触发 (ET)只处理有事件的 fd不浪费性能
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2499744.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!