【Linux】【进程】epoll内核实现总结+ET和LT模式内核实现方式
【Linux】【网络】epoll内核实现总结+ET和LT模式内核实现方式
1.epoll的工作原理
eventpoll结构
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关.
struct eventpoll{..../*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/struct rb_root rbr; // 监视列表(红黑树)/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/struct list_head rdlist; //就绪队列(双向链表)....
};
- 通过epoll_ctl方法向epoll对象添加事件。这些事件都会挂载在红黑树rbr中,红黑树的增删查改效率都是O(logn),是一个高效的索引结构。并且可以利用红黑树键值的唯一性进行去重操作(重复添加无效)。
- 底层驱动程序允许操作系统注册一些回调函数,所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法。
- 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
epitem对象
rbr和rdlist中的元素都是这样的结构。在epoll中,对于每一个事件,都会建立一个epitem对象,该对象既挂接在rbr红黑树中也挂接在rdlist双链表中。
struct epitem{struct rb_node rbn;//红黑树节点struct list_head rdllink;//双向链表节点struct epoll_filefd ffd; //事件句柄信息struct eventpoll *ep; //指向其所属的eventpoll对象struct epoll_event event; //期待发生的事件类型
}
- 当中rbn和rdlist结构分别描述该节点在rbr红黑树和rdlist双链表中的链接关系。
- 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.
- 如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1).
就绪队列:底层结构是rdlist双向链表,将监视且就绪的事件添加到队列。(核心结构,中间结构)
监视列表:底层结构是rbr红黑树,通过epoll_ctl添加就绪事件。(就像是一份名单)
中断回调:添加就绪事件的同时,系统向硬件驱动注册中断回调,事件发生时由他完成检查和就绪的任务。(完成实际的工作步骤)
2.整体网络操作

1.用户通过调用epoll_create(size)创建epoll模型(eventpoll结构),当中就包括了rbr监视列表和rdlist就绪队列。
2.通过epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, event)
- 向rbr监视列表添加监视套接字的对应事件
- 同时在硬件驱动(网卡)中注册对应的中断回调。
- 做到以上两点,当数据到来时rdlist就绪队列就可以获取到对应事件的就绪节点了。
3.计算机收到了对端传送的数据。数据经由网卡传送到内存,然后网卡通过中断信号通知cpu有数据到达,cpu执行硬件驱动对应的中断回调程序。此处中断程序的主要功能有:
- 将数据不断解包,层层向上交付,一直交给对应套接字的接收队列(tcp传输层)。
- 在rbr监视列表(红黑树)中查找是否监视套接字的对应事件。
- 如果确认监视,则构建就绪节点,将节点插入到rdlist就绪队列当中
- 唤醒进程(如果进程被epoll_wait阻塞)
4.最后用户调用epoll_wait(epfd, events, maxevents, timeout)将epoll模型就绪队列中的就绪事件获取到events数组(用户空间),并返回就绪事件的数量。当然,如果此时就绪队列为空则进程阻塞或超时返回0。
5.接下来就是用户层的工作了,对照events数组当中的就绪事件,向指定的套接字句柄进行读写操作(当然还有期间的数据处理工作)。
内核角度
eventpoll对象相当于是socket和进程之间的中介,socket的数据接收并不直接影响进程,而是通过改变eventpoll的就绪列表来改变进程状态:
- 在select/poll中,在IO等待阶段进程是被挂起在每个监视socket的等待队列中,当数据到来时又需要将进程从每个socket的等待队列中移除再放入CPU运行队列才能唤醒进程,这就涉及到两次遍历,效率O(2n);
- 而在epoll中,进程是被挂起在epoll模型(也是文件系统的一员)的等待队列中的,当有任意监视事件就绪(就绪队列中有节点),只需要将进程从epoll等待队列中移除再放入CPU运行队列即可,效率O(1)。
用户角度
epoll_wait获取上来的所有事件都是就绪事件,可以直接对照套接字和对应事件进行读写操作,不需要向select/poll那样把所有监视套接字都遍历判断一遍就绪了。
select/poll 的工作机制:
-
等待阶段:当进程调用
select或poll时,内核会将该进程挂起,并将其添加到每个被监视的套接字的等待队列中。 -
事件发生:如果某个套接字上有事件(如可读或可写)发生,内核需要遍历该套接字的等待队列,将所有在此等待的进程从等待队列中移除,并将其放入 CPU 的运行队列中,以便唤醒这些进程。
-
性能影响:由于每个进程可能监视多个套接字,因此在事件发生时,内核需要对每个套接字的等待队列进行遍历和操作,导致时间复杂度为 O(n),其中 n 是被监视的套接字数量。
从 select.c 源码来看,Linux 内核中 select 和 poll 机制的等待队列操作主要涉及 进程挂起、等待队列管理、进程唤醒 这几个关键步骤。以下是详细的解析:
1. 进程如何被挂起到等待队列
在 do_select() 函数中,进程在等待 I/O 事件时会进入睡眠状态:
set_current_state(TASK_INTERRUPTIBLE);
TASK_INTERRUPTIBLE表示进程进入 可中断睡眠 状态,等待 I/O 事件发生。
然后,内核会遍历所有需要监视的 文件描述符(FD),并调用 poll_wait():
if (f_op && f_op->poll)mask = (*f_op->poll)(file, retval ? NULL : wait);
file->f_op->poll是文件的poll()函数,通常由设备驱动提供。poll_wait()(内联定义在linux/poll.h)会将当前进程添加到 该文件的等待队列。
2. poll_wait() 将进程加入等待队列
在 __pollwait() 中,进程会被添加到文件描述符的等待队列:
void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p)
{struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);struct poll_table_page *table = p->table;/* 创建新的等待队列条目 */struct poll_table_entry * entry = table->entry;table->entry = entry+1;get_file(filp);entry->filp = filp;entry->wait_address = wait_address;/* 初始化等待队列条目 */init_waitqueue_entry(&entry->wait, current);/* 将当前进程添加到该文件描述符的等待队列 */add_wait_queue(wait_address, &entry->wait);
}
wait_address是 文件描述符(socket、管道等)的等待队列。add_wait_queue()将当前进程 (current) 挂起到 文件描述符的等待队列 中。
此时,进程已经进入 睡眠状态,等待事件发生。
3. 事件发生时,进程如何被唤醒
当套接字(或文件)上有 可读、可写 事件发生时,设备驱动会调用:
wake_up(&wait_queue);
该操作最终会调用:
static void wake_up_process(struct task_struct *p)
{if (task_is_running(p))return;p->state = TASK_RUNNING;enqueue_task(p); // 将进程放入 CPU 运行队列
}
- 从等待队列移除进程
- 修改进程状态为
TASK_RUNNING - 将进程放入 CPU 的运行队列
此时,进程被 唤醒,可以继续执行 select() 或 poll() 后续代码,处理 I/O 事件。
4. select 和 poll 的低效之处
4.1 select/poll 遍历每个文件的等待队列
- 进程会被 挂起到所有监视的套接字的等待队列。
- 事件发生时,内核需要遍历 所有文件的等待队列,找到需要唤醒的进程。
4.2 事件触发后需要两次遍历
- 遍历文件描述符的等待队列,将进程移除。
- 遍历所有等待的进程,将其放入 CPU 运行队列。
时间复杂度:O(2n)
如果有 成千上万个套接字,性能开销非常大。
5. epoll 如何优化这个过程
5.1 epoll_wait 进程只挂起在 epoll 的等待队列
add_wait_queue(&ep->wq, &wait);
schedule_timeout(timeout);
- 进程 不再被挂起到每个套接字的等待队列,而是挂起到
epoll实例的等待队列。
5.2 事件发生时
static void ep_poll_callback(struct eppoll_entry *epi)
{list_add_tail(&epi->rdllink, &ep->rdlist);wake_up(&ep->wq);
}
- 只需要操作
epoll的等待队列,即可唤醒epoll_wait(),无需遍历所有套接字。 - 时间复杂度 O(1),比
select/poll更高效。
6. 总结
| 机制 | 等待队列 | 事件发生时的操作 | 时间复杂度 |
|---|---|---|---|
select/poll | 进程挂起在 所有套接字 的等待队列 | 遍历所有套接字的等待队列,移除进程 | O(2n) |
epoll | 进程挂起在 epoll 实例 的等待队列 | 只需操作 epoll 等待队列 | O(1) |
epoll 通过 就绪队列 机制,避免了 select / poll 的 双重遍历等待队列,大幅提升高并发场景的 I/O 处理效率 🚀。
epoll 的工作机制:
- 等待阶段:
epoll引入了一个专用的事件监视对象(即epoll实例)。当进程调用epoll_wait时,内核将该进程挂起,并将其添加到epoll实例的等待队列中,epoll_wait 进程只挂起在 epoll 的等待队列
add_wait_queue(&ep->wq, &wait);
schedule_timeout(timeout);
2.进程 不再被挂起到每个套接字的等待队列,而是挂起到 epoll 实例的等待队列。只需要操作 epoll 的等待队列,即可唤醒 epoll_wait(),无需遍历所有套接字。
- 事件发生:当任一被监视的套接字上有事件发生时,内核会将该事件添加到
epoll实例的就绪队列中。如果这是第一个就绪事件,内核会将等待在epoll实例上的进程从等待队列中移除,并放入 CPU 的运行队列中,唤醒该进程。
2.LT/ET
水平触发(LT)模式:
-
默认模式:在 LT 模式下,当被监控的文件描述符上有事件发生时,
epoll_wait会通知应用程序。即使应用程序没有立即处理该事件,后续的epoll_wait调用仍会再次通知,直到事件被处理。 -
特点:LT 模式下,内核会持续通知应用程序某个文件描述符的事件,直到应用程序处理为止。
边沿触发(ET)模式:
-
高效模式:在 ET 模式下,当被监控的文件描述符上有事件发生时,
epoll_wait仅通知应用程序一次。如果应用程序没有及时处理,后续的epoll_wait调用将不会再次通知。因此,应用程序需要确保在收到通知后,非阻塞地读取或写入数据,直到返回EAGAIN。 -
特点:ET 模式减少了重复通知的次数,提高了效率,但要求应用程序采用非阻塞 I/O,并在收到事件后立即处理。
内核实现上的区别:
-
总结一下epoll该函数: epoll_wait函数会使调用它的进程进入睡眠(timeout为0时除外),如果有监听的事件产生,该进程就被唤醒,同时将事件从内核里面拷贝到用户空间返回给该进程。
-
LT模式只不过比ET模式多执行了一个步骤,就是当epoll_wait获取完就绪队列epoll事件后,LT模式会再次将epoll事件添加到就绪队列。
-
LT模式多了这样一个步骤会让LT模式调用epoll_wait时会一直检测到epoll事件,直到socket缓冲区数据清空为止。
epoll为什么高效
-
eventpoll等待队列机制,当就绪队列没有epoll事件时主动让出CPU,阻塞进程,提高CPU利用率。
-
socket等待队列机制,只有接收到数据时才会将epoll事件插入就绪队列,唤醒进程获取epoll事件。
-
红黑树提高epoll事件增加,删除,修改效率。
-
任务越多,进程出让CPU概率越小,进程工作效率越高,所以epoll非常适合高并发场景。
epoll采用阻塞方式是否影响性能
epoll机制本身也是阻塞的,当epoll_wait未检测到epoll事件时,会出让CPU,阻塞进程,这种阻塞是非常有必要的,如果不及时出让CPU会浪费CPU资源,导致其他任务无法抢占CPU,只要epoll机制能够在检测到epoll事件后,及时唤醒进程处理,并不会影响epoll性能。
参考:
https://blog.csdn.net/zty857016148/article/details/143615927
https://blog.csdn.net/weixin_45605341/article/details/140578005
相关文章:
【Linux】【进程】epoll内核实现总结+ET和LT模式内核实现方式
【Linux】【网络】epoll内核实现总结ET和LT模式内核实现方式 1.epoll的工作原理 eventpoll结构 当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关. struct eventpoll{..../*红…...
英码科技基于昇腾算力实现DeepSeek离线部署
DeepSeek-R1 模型以其创新架构和高效能技术迅速成为行业焦点。如果能够在边缘进行离线部署,不仅能发挥DeepSeek大模型的效果,还能确保数据处理的安全性和可控性。 英码科技作为AI算力产品和AI应用解决方案服务商,积极响应市场需求࿰…...
测试常见问题汇总-检查表(持续完善)
WEB页面常见的问题 按钮功能的实现:返回按钮是否可以正常返回 信息保存提交后,系统是否给出“成功”的提示信息,列表数据是否自动刷新 没有勾选任何记录直接点【删除】,是否给出“请先选择记录”的提示 删除是否有删除确认框 …...
【SQL】SQL约束
🎄约束 📢作用:是用于限制存储再表中的数据。可以再创建表/修改表时添加约束。 📢目的:保证数据库中数据的正确、有效性和完整性。 📢对于一个字段可以同时添加多个约束。 🎄常用约束: 约束分类 约束 描述关键字非…...
解决 `pip is configured with locations that require TLS/SSL` 错误
问题描述 在使用 pip 安装 Python 包时,可能会遇到以下错误: WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.这意味着 Python 的 ssl 模块未正确安装或配置,导致 p…...
如何commit后更新.gitignore实现push
目录 步骤 1: 更新 .gitignore 文件 步骤 2: 移除已追踪的大文件 步骤 3: 提交更改 步骤 4: 尝试推送 注意事项 如果已经执行了git commit,但后来意识到需要更新.gitignore文件以排除某些不应该被追踪的大文件或目录,并希望在不丢失现有提交记录的情…...
Python 面向对象的三大特征
前言:本篇讲解面向对象的三大特征(封装,继承,多态),还有比较细致的(类属性类方法,静态方法),分步骤讲解,比较适合理清楚三大特征的思路 面向对象的…...
机器学习_18 K均值聚类知识点总结
K均值聚类(K-means Clustering)是一种经典的无监督学习算法,广泛应用于数据分组、模式识别和降维等领域。它通过将数据划分为K个簇,使得簇内相似度高而簇间相似度低。今天,我们就来深入探讨K均值聚类的原理、实现和应用…...
从低清到4K的魔法:FlashVideo突破高分辨率视频生成计算瓶颈(港大港中文字节)
论文链接:https://arxiv.org/pdf/2502.05179 项目链接:https://github.com/FoundationVision/FlashVideo 亮点直击 提出了 FlashVideo,一种将视频生成解耦为两个目标的方法:提示匹配度和视觉质量。通过在两个阶段分别调整模型规模…...
Nuclei 使用手册
Nuclei 是一个开源的快速、高效的漏洞扫描工具,主要用于网络安全领域的漏洞检测。它由 go 语言开发,设计目的是为了高效地扫描 Web 应用程序、网络服务等目标,帮助安全研究人员、渗透测试人员以及红队成员发现潜在的漏洞。 下载链接…...
python学opencv|读取图像(六十七)使用cv2.convexHull()函数实现图像轮廓凸包标注
【1】引言 前序学习进程中,已经初步探索了对图像轮廓的矩形标注和圆形标注: python学opencv|读取图像(六十五)使用cv2.boundingRect()函数实现图像轮廓矩形标注-CSDN博客 但实际上,这两种标注方法都是大致的&#x…...
基于SpringBoot的“高校创新创业课程体系”的设计与实现(源码+数据库+文档+PPT)
基于SpringBoot的“高校创新创业课程体系”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 系统首页界面 个人中心界…...
前端带样式导出excel表格,html表格生成带样式的excel表格
众所周知,前端生成表格通常是用xlsx、excel.js等js库,但这些库想要生成时增加excel样式会很麻烦。 有这么一个js库把html表格连样式带数据一并导出为excel表格: html-table-to-excel npm install html-table-to-excel 使用 html表格: <…...
人形机器人 - 仿生机器人核心技术与大小脑
以下是针对仿生机器人核心技术的结构化总结,涵盖通用核心技术与**“大脑-小脑”专用架构**两大方向: 一、机器人通用核心技术 这些技术是仿生机器人实现功能的基础,与生物体的“身体能力”对应: 1. 感知与交互技术 多模态传感器融合 视觉:3D视觉(如RGB-D相机)、动态目…...
【Linux】【网络】Libevent 内核实现简略版
【Linux】【网络】Libevent 内核实现简略版 1 event_base结构–>相当于Reactor 在使用libevent之前,就必须先创建这个结构。 以epoll为例: 1.1evbase void* evbase-->epollop结构体(以epoll为例) libevent通过一个void…...
大数据学习(49) - Flink按键分区状态(Keyed State)
&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦ᾑ…...
架构——LVS负载均衡主要模式及其原理、服务水平、优缺点
LVS(Linux Virtual Server)是一款高性能的开源负载均衡软件,支持多种负载均衡模式。以下是其主要模式及其原理、服务水平、优缺点: 1. NAT 模式(Network Address Translation) 原理: 请求流程…...
【React组件通讯双重视角】函数式 vs 类式开发指南
目录 前言 正文 父组件向子组件传值 函数式写法 类式写法 子组件向父组件传值 函数式写法 类式写法 兄弟组件通信 函数式写法 类式写法 跨层级通信(使用Context) 函数式写法 类式写法 进阶通讯方式(补充说明…...
VScode内接入deepseek包过程(本地部署版包会)
目录 1. 首先得有vscode软件 2. 在我们的电脑本地已经部署了ollama,我将以qwen作为实验例子 3. 在vscode上的扩展商店下载continue 4. 下载完成后,依次点击添加模型 5. 在这里可以添加,各种各样的模型,选择我们的ollama 6. 选…...
Ubuntu虚拟机NDK编译ffmpeg
目录 一、ffmpeg源码下载1、安装git(用于下载ffmpeg源码)2、创建源码目录,下载ffmpeg源码 二、下载ubuntu对应的NDK,并解压到opt下1、下载并解压2、配置 ~/.bashrc 三、源码编译、1、创建编译脚本2、脚本文件内容3、设置可执行权限并运行4、编译的结果在…...
机器学习:k近邻
所有代码和文档均在golitter/Decoding-ML-Top10: 使用 Python 优雅地实现机器学习十大经典算法。 (github.com),欢迎查看。 K 邻近算法(K-Nearest Neighbors,简称 KNN)是一种经典的机器学习算法,主要用于分类和回归任务…...
js第十二题
题十二:轮播图 要求: 1.鼠标不在图片上方时,进行自动轮播,并且左右箭头不会显示;当鼠标放在图片上方时,停止轮播,并且左右箭头会显示; 2.图片切换之后,图片中下方的小…...
讯飞唤醒+VOSK语音识别+DEEPSEEK大模型+讯飞离线合成实现纯离线大模型智能语音问答。
在信息爆炸的时代,智能语音问答系统正以前所未有的速度融入我们的日常生活。然而,随着数据泄露事件的频发,用户对于隐私保护的需求日益增强。想象一下,一个无需联网、即可响应你所有问题的智能助手——这就是纯离线大模型智能语音…...
Day4 25/2/17 MON
【一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解(马士兵)】https://www.bilibili.com/video/BV13g41157hK?p4&v…...
HTML【详解】input 标签
input 标签主要用于接收用户的输入,随 type 属性值的不同,变换其具体功能。 通用属性 属性属性值功能name字符串定义输入字段的名称,在表单提交时,服务器通过该名称来获取对应的值disabled布尔值禁用输入框,使其无法被…...
Jvascript网页设计案例:通过js实现一款密码强度检测,适用于等保测评整改
本文目录 前言功能预览样式特点总结:1. 整体视觉风格2. 密码输入框设计3. 强度指示条4. 结果文本与原因说明 功能特点总结:1. 密码强度检测2. 实时反馈机制3. 详细原因说明4. 视觉提示5. 交互体验优化 密码强度检测逻辑Html代码Javascript代码 前言 能满…...
LeetCode刷题---哈希表---290
单词规律 290. 单词规律 - 力扣(LeetCode) 题目: 给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 s 中的每个非空单词…...
用React实现一个登录界面
使用React来创建一个简单的登录表单。以下是一个基本的React登录界面示例: 1. 设置React项目 如果你还没有一个React项目,你可以使用Create React App来创建一个。按照之前的步骤安装Create React App,然后创建一个新项目。 2. 创建登录组…...
图论:tarjan 算法求解强连通分量
题目描述 有一个 n n n 个点, m m m 条边的有向图,请求出这个图点数大于 1 1 1 的强连通分量个数。 输入格式 第一行为两个整数 n n n 和 m m m。 第二行至 m 1 m1 m1 行,每一行有两个整数 a a a 和 b b b,表示有一条…...
Haskell语言的物联网
Haskell语言在物联网中的应用 引言 物联网(IoT,Internet of Things)是现代科技发展的重要领域,它将日常生活中的各种设备通过互联网连接起来,实现智能化的控制与管理。随着设备数量的激增,以及数据处理需…...
