Linux下的I/O复用技术之epoll

I/O多路复用

指在单个线程或进程中,同时处理多个I/O操作的技术。

旨在提高程序处理多个并发I/O操作的能力,避免程序因等待某个I/O操作而被阻塞。在传统的I/O模型中当程序进行I/O操作时(如读取文件、接受网路数据等),如果数据还未准备好,程序会被阻塞,直到I/O操作完成,这会导致效率低下,尤其是在需要处理大量并发连接的网络应用中。I/O复用技术的核心理念是允许一个进程或线程同时处理多个I/O操作,而不是等待某一个操作完成后再去处理其他任务。通过则何种方式,程序能够在多个I/O之间切换,充分利用系统资源,避免每个I/O事件都创建一个新的线程或进程,从而大大提高效率。在linux中相关技术有select()、poll()、epoll(),程序会通过上述机制同时监控多个I/O事件,并在其中某个文件描述符(或I/O操作)就绪时,进行相应的处理。这样即使有多个I/O操作正在进行,程序也可以及时响应,并继续进行其他任务,从而达到“非阻塞”的效果。

epoll涉及到的系统调用函数

epoll_create()

用于创建一个epoll示例,返回一个文件描述符,程序通过这个文件描述符与epoll实例进行交互。他为事件提供一个内核空间的数据结构,并为之后的epoll_ctl()和epoll_wait()调用提供一个有效的上下文。

int epoll_create(int size);

size:指定内核事件表的初始大小。这个参数在现代linux系统中已没有什么作用,通常   设置为1即可,因为内核会动态调整。

返回值:创建成功返回一个非负值,表示epoll实例的文件描述符,创建失败返回-1,      并设置errno为相应的错误代码。

int epoll_create1(int flags);

flags:创建时指定的额外选项

传入0代表不指定额外选项

EPOLL_CTL_ADD:添加新的文件描述符及其事件EPOLL_CTL_MOD:修改已经注册的文件描述符的事件EPOLL_CTL_DEL:删除文件描述符的注册

传入EPOLL_CLOEXEC:设置文件描述符为执行时关闭,以确保在exec系列函数调用后自动关闭该文件的文件描述符。

epoll_ctl()

用于控制epoll实例中的事件,包括向epoll注册、修改或删除文件                          描述符的I/O事件。

int epoll_ctl(int epfd, int op,int fd, struct epoll_event* event);

epfd:由epoll_create()返回的epoll文件描述符,用于标识epoll实例。

op:操作类型,指示所执行的操作。

EPOLL_CTL_ADD:添加新的文件描述符及其事件EPOLL_CTL_MOD:修改已经注册的文件描述符的事件EPOLL_CTL_DEL:删除文件描述符的注册

fd:需要注册、修改或删除的文件描述符

event:struct epoll_event类型的指针,表示与文件描述符关联的事件类型,这个结构体参数的作用不仅仅是保存文件描述符,他还包含了与该文件描述符关联的事件类型、以及其他用于标识和处理时间的数据

返回值:成功返回0,失败返回-1,并设置errno为相应的错误代码。

..................................................................................................................................................

补:epoll_event数据结构

该结构体定义了与文件描述符关联的事件类型及其他数据

struct epoll_event {uint32_t  events;  //事件类型epoll_data_t data;  //与文件描述符关联的数据,通常为文件描述符或指针
};

其中:

events:表示该文件描述符的事件类型,注意这些事件类型是可以a|b的混合注册的

data:是一个联合体epoll_data_t,用于存储与文件描述符相关的自定义数据,通常用于存储文件描述符本身或替他数据

epoll_data_t可以是:int fd(文件描述符);  void* ptr(指向用户数据的指针)等

..................................................................................................................................................

示例:

struct epoll_event ev;
ev.event = EPOLLIN;  //设置为读取事件
ev.data.fd = server_fd;  //设置文件描述符
int res = epoll_create(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);

epoll_wait()

用于等待已注册的文件描述符上的事件发生。该函数会阻塞直到至少一个事件发生或者超时。等待并返回已发生的事件。

int epoll_wait(int epfd, struct epoll_event* event, int maxevents, int timeout);

epfd:由epoll_create返回的epoll文件描述符

events:指向epoll_event结构体数组的指针,用于接收已发生的事件。

event是一个数组,用于存储epoll_event类型的结构体,即用于接收发生的事件event。这里的事件是指,在epoll_ctl阶段注册到epoll中的事件event,并且他得已经发生,已经发生的意思是指已注册的文件描述符的状态满足了你注册的事件条件(如:在epoll_ctl阶段为某个文件描述符fd注册了EPOLLIN,当fd上有数据可读时,例如socket接收到网络数据了,那么这个fd上就有数据可以读取了,这个事件就被认为已经发///或者为某个文件描述符注册的EPOLLOUT,当fd可以用来写数据时,比如socket的发送缓冲区有空闲空间时,那么这个fd就可以进行写,这个事件被认为已经发生了)。

epoll_wait()的作用可以这样理解,它的作用就是监听在epoll_ctl注册到epfd中的事件集合,然后将发生的事件传入events数组中,通过遍历这个events可以得到event,通过event的fd和events成员可以得知该fd可以进行events对应操作了(比如:struct epoll_event ev;  ev.fd = fd1;  ev.events = EPOLLOUT,那么我们可以对fd1进行写操作了,write(fd, ....))。

maxevents:指定events数组的大小,即一次最多返回的事件数

timeout:指定等待的超时时间(单位:毫秒)。设置为-1时表示无限等待,设置为0表示                非阻塞,其他正值表示最大等待时间。

返回值:成功则返回已就绪的事件数,即发生的事件数量,失败则返回-1,并设置errno          为相应的错误代码。

示例:

struct epoll_event events[10];
int nfds = epoll_wait(epfd, events, 10, -1);
for (int i = 0; i < nfds; ++i) {if (events[i].events & EPOLLIN) {// 处理可读事件}if (events[i].events & EPOLLOUT) {// 处理可写事件}
}

 综合使用简单示例:

// 创建一个 epoll 实例,返回 epoll 文件描述符
int epfd = epoll_create(1);// 定义一个 epoll_event 结构体变量,用于描述要监听的事件
struct epoll_event ev;// 设置事件类型为 EPOLLOUT,表示监听 "可写" 事件
ev.events = EPOLLOUT;// 绑定要监听的文件描述符(socketfd)到 ev 的 data.fd 字段
ev.data.fd = socketfd;// 将指定的文件描述符(socketfd)注册到 epoll 实例 epfd 中,监听可写事件
epoll_ctl(epfd, EPOLL_CTL_ADD, socketfd, &ev);// 定义一个数组,用来接收 epoll_wait 返回的就绪事件
struct epoll_event events[10];// 调用 epoll_wait,阻塞等待内核检测 epfd 中注册的事件
// -1 表示永远等待,直到有事件发生
int nfds = epoll_wait(epfd, events, 10, -1);// 遍历所有返回的就绪事件
for (int i = 0; i < nfds; ++i) {// 检查当前事件是否包含 EPOLLOUT,可写事件if (events[i].events & EPOLLOUT) {// 取出就绪的文件描述符int fd = events[i].data.fd;// 定义要发送的消息内容const char* message = "hello";// 使用 write 将消息写入到对应的文件描述符ssize_t bytes_written = write(fd, message, strlen(message));// 这里没有做错误检查,实际项目中最好检查 bytes_written 是否出错}
}
// 创建 epoll 实例,返回 epoll 文件描述符
int epfd = epoll_create1(0);// 注意:这里通过一个 open 打开了一个文件,得到一个文件描述符,但它不一定满足下面的 EPOLLIN 事件,
// 只有当 example 文件中有数据时,它才可以被读,才满足该 fd 监听的 / 感兴趣的事件 EPOLLIN,
// 它才可以在 epoll_wait 的时候被添加到 events 中。
int fd = open("example.txt", O_RDONLY);  // 注意:这里应该是 O_RDONLY,不是 0_RDONLY// 定义一个 epoll_event 结构体变量
struct epoll_event ev;// 设置监听的事件类型为 EPOLLIN(可读事件)
ev.events = EPOLLIN;// 将文件描述符 fd 保存到 ev.data.fd 中
ev.data.fd = fd;// 调用 epoll_ctl,将 fd 注册到 epoll 实例 epfd 中,关注可读事件
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);// 定义一个数组,用来存放 epoll_wait 返回的就绪事件
struct epoll_event events[10];// 调用 epoll_wait,阻塞等待 epfd 中注册的文件描述符上有事件发生
int nfds = epoll_wait(epfd, events, 10, -1);// 遍历所有返回的就绪事件
for (int i = 0; i < nfds; ++i) {// 如果当前事件是 EPOLLIN(可读事件)if (events[i].events & EPOLLIN) {// 在这里处理对应的可读文件描述符}
}

epoll底层实现原理

核心组件

①红黑树

epoll内部维护了一颗红黑树,通过红黑树来管理(增加、删除、修改)所有被监控的文件描述符;当调用epoll_ctl增加、删除或修改监控事件时,会在红黑树中插入、移除或更新相应的节点;红黑树的高效查找和插入特性(时间复杂度为O(Logn))使得epoll在管理大量文件描述符时性能优越。

②就绪链表(双向链表)

epoll使用一个就绪链表来保存当前已经触发事件的文件描述符;当某个文件描述符变为就绪状态时(例如数据可读或可写),其对应的事件会被添加到就绪链表中;这使得epoll_wait调用只需直接扫描这个链表,从而避免了向poll或select那样逐个遍历所有的文件描述符。

与内核的交互

epoll是依赖内核中的事件通知机制来工作的,通常通过文件系统(如proc文件系统等)监控文件描述符的状态;每个文件描述符在内核中都有一个对应的事件回调函数,当事件发生时(即通过epoll_ctl添加到红黑树中的event对应的fd满足注册的事件状态时),会触发这个回调函数,将事件添加到就绪链表中,当调用epoll_wait时,内核将扫描就绪链表,并返回链表中的数据。

触发机制

在epoll中,触发机制决定了epoll_wait如何返回文件描述符的事件。这直接影响事件的通知方式和应用程序对文件描述符的处理策略,epoll支持两种触发机制:水平触发和边缘触发。

①水平触发(level triggered

这是epoll默认的触发方式。文件描述符只要处于就绪状态(可读或可写),epoll_wait就会一直返回该事件(调用该方法返回的int值大小中有它一席,并且传入给epoll_wait的events数组也会一直存入这个事件event);无论文件描述符的状态是否变化,只要其仍然满足条件(如缓冲区有数据可读),事件都会重复触发,直到应用程序对其处理完成。

优点:使用简单,适合大多数场景。不容易遗漏事件,即使处理稍有延迟,也可以通过多次调用读取剩余数据。

缺点:对于大量文件描述符,就绪事件可能被重新触发,导致处理效率较低。

struct epoll_event ev;
ev.events = EPOLLIN;  //水平触发是默认的

②边沿触发(edge triggered

事件只会在状态变化时触发(如从不可读变为可读,或从不可写变成可写);如果应用程序没有在事件触发时处理完数据,则不会再次触发,可能导致数据遗漏。

优点:减少了重复通知,提高了系统效率,适合大规模并发场景,支持高性能的非阻塞模式。

缺点:复杂性高,必须一次性读取或写入尽可能多的数据,否则可能会遗漏数据。

struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;	//设置为边沿触发

epoll工作流程

首先,用户通过调用epoll_create创建一个epoll实例,用于管理所有需要监听的文件描述符(每个epoll实例中都有一个独立的evetnepoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加的事件)。接着,使用epoll_ctl向epoll内部的红黑树中添加、修改或删除需要监听的文件描述符及事件;最后,通过调用epoll_wait等待事件发生,内核会将已经触发的事件加入就绪链表,并将链表中的就绪文件描述符返回给用户程序,从而实现高效的事件驱动模型。

相较于selectpollepoll为什么高效?

以下几个差异导致epoll更高效

1.事件监听和管理方式的不同

select、poll:每次调用select、poll,都需要将所有文件描述符表传递给内核,内   核会对这些文件描述符逐一查其状态,这种查找是线性查找,时间复杂度是O(n), 导致性能开销大,尤其是在文件描述符数量较多时。

epoll使用一个红黑树来存储用户注册的文件描述符事件,文件描述符只需通过   epoll_ctl注册一次;每次epoll_wait时,内核只需检查红黑树上的事件,并通过 一个就绪链表直接返回有事件发生的文件描述符;事件分发是基于回调的机制,无 需线性扫描。

2.数据拷贝的效率

select、poll:每次调用select、poll都需要将文件描述符列表从用户态拷贝到内核 态,再从内核态返回结果到用户态,如果文件描述符很多,这个过程会占用大量的 cpu和内存带宽。

epoll采用共享内存机制,文件描述符只在epoll_ctl注册时传递给内核;内核和用 户空间之间通过共享的就绪链表来传递数据,避免每次调用时的大量拷贝。

3.支持更大的文件描述符集合

select文件描述符数量受到系统常量FD_SETSIZE的限制(通常是1024个)。超过限 制后无法使用。

poll支持更多的文件描述符,但依然需要遍历整个文件描述符列表

epoll支持的文件描述符数量只受限于系统的最大文件描述符数量,理论上可以达到   数十万甚至更多。即使文件描述符数量庞大,只关注有事件发生的文件描述符,效 率依然很高。

4.触发机制的差异

select、poll:只支持水平触发,即只要文件描述符的状态满足条件,每次都会返回, 可能会导致重复处理。

epoll支持水平触发和边缘触发,边缘触发模式下,只有当文件描述符的状态从未满   足到满足时,才会触发事件,进一步减少系统调用的次数,提高性能。

5.线程安全性

epoll是线程安全的(epoll_wait内存实现对共享的就绪事件列表有锁机制保活,确 保线程安全),多个线程可以同时调用epoll_wait,充分利用多核CPU提高并   发  能力。

select、poll:通常需要额外的同步机制来确保多线程访问同一个文件描述符集合时的 线程安全性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/78287.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用 C 语言实现通用的冒泡排序算法

在日常编程中&#xff0c;排序算法是一个非常常见且重要的工具。虽然有许多排序算法可以选择&#xff0c;但如果你需要一个能够处理不同数据类型的排序算法&#xff0c;如何设计一个通用的排序算法呢&#xff1f;今天我们将实现一个通用的冒泡排序算法&#xff0c;支持不同数据…

C# 变量全解析:声明、初始化与使用

在多用途的编程语言中&#xff0c;程序存取数据是一项基础且关键的功能&#xff0c;而这一功能主要通过变量来实现。本文将全面深入地探讨 C# 中的变量&#xff0c;包括变量的种类、声明、初始化、自动初始化、多变量声明以及如何使用变量的值。 变量概述 变量是一个名称&…

Dify中的文本分词处理技术详解

Dify中的文本分词处理技术详解 引言核心架构概览索引处理器工厂 文本分词技术详解基础分词器增强型递归字符分词器固定分隔符文本分词器递归分割算法 索引处理器中的分词应用特殊索引处理器的分词特点问答索引处理器父子索引处理器 分词技术的应用场景技术亮点与优势总结 引言 …

如何打包python程序为可执行文件

将 Python 程序打包为可执行文件是一个常见需求&#xff0c;尤其是在希望将应用程序分享给不具备 Python 环境的用户时。以下是使用 PyInstaller 工具将 Python 程序打包为可执行文件的步骤。 步骤 1&#xff1a;安装 PyInstaller 如果您还没有安装 PyInstaller&#xff0c;请…

美团Java后端二面面经!

场景题是面试的大头&#xff0c;建议好好准备 Q. [美团]如何设计一个外卖订单的并发扣减库存系统&#xff1f; Q.[美团]为啥初始标记和重新标记需要STW&#xff1f; Q.[美团]骑手位置实时更新&#xff0c;如何保证高并发写入&#xff1f; Q.[美团]订单表数据量过大导致查询…

在应用运维过程中,业务数据修改的证据留存和数据留存

在应用运维过程中,业务数据修改的证据留存和数据留存至关重要,以下是相关介绍: 一、证据留存 操作日志记录 : 详细记录每一次业务数据修改的操作日志,包括操作人员、操作时间、修改内容、修改前后数据的对比等。例如,某公司业务系统中,操作日志会精确记录员工小张在 2…

Eigen迭代求解器类

1. 迭代求解器核心类概览 Eigen 提供多种迭代法求解稀疏线性方程组 AxbAxb&#xff0c;适用于大规模稀疏矩阵&#xff1a; 求解器类适用矩阵类型算法关键特性ConjugateGradient对称正定&#xff08;SPD&#xff09;共轭梯度法&#xff08;CG&#xff09;高精度&#xff0c;内…

ORACLE数据库备份入门:第四部分:2-备份场景举例

下面以4个常见的场景为例&#xff0c;介绍如何规划备份方案。备份方案没有标准答案&#xff0c;需要根据实现情况来制定&#xff0c;也和管理员的个人使用习惯有很大相关性。 1 交易型数据库备份 以银行的交易系统为例&#xff0c;除了前一章节提到的关于RPO和RTO的指标外&am…

小白如何学会完整挪用Github项目?(以pix2pix为例)

[目录] 0.如何完整地复现/应用一个Github项目 1.建立适用于项目的环境 2.数据准备与模型训练阶段 3.训练过程中的一些命令行调试必备知识0.如何完整地复现/应用一个Github项目 前日在健身房的组间同一位好友交流时&#xff0c;得到了一个一致结论—— ** Github \texttt{Githu…

蓝桥杯 5. 交换瓶子

交换瓶子 原题目链接 题目描述 有 N 个瓶子&#xff0c;编号为 1 ~ N&#xff0c;放在架子上。 例如有 5 个瓶子&#xff0c;当前排列为&#xff1a; 2 1 3 5 4每次可以拿起 2 个瓶子&#xff0c;交换它们的位置。 要求通过若干次交换&#xff0c;使得瓶子的编号从小到大…

Linux 系统渗透提权

Linux 系统渗透提权 比赛题库-Linux 系统渗透提权 文章目录 Linux 系统渗透提权比赛题库-Linux 系统渗透提权 前言一、解题过程1.使用渗透机对服务器信息收集&#xff0c;并将服务器中 SSH 服务端口号作为 flag 提 交&#xff1b;2.使用渗透机对服务器信息收集&#xff0c;并将…

华为OD机试真题——查找接口成功率最优时间段(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C、GO六种语言的最佳实现方式&#xff1b; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析&#xff1b; 本文收录于专栏&#xff1a;《2025华为OD真题目录…

华为OD机试真题——绘图机器(2025A卷:100分)Java/python/JavaScript/C++/C/GO最佳实现

2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析&#xff1b; 并提供Java、python、JavaScript、C、C语言、GO六种语言的最佳实现方式&#xff01; 本文收录于专栏&#xff1a;《2025华为OD真题目录全流程解析/备考攻略/经验…

基于 Python(selenium) 的百度新闻定向爬虫:根据输入的关键词在百度新闻上进行搜索,并爬取新闻详情页的内容

该项目能够根据输入的关键词在百度新闻上进行搜索,并爬取新闻详情页的内容。 一、项目准备 1. 开发环境配置 操作系统:支持 Windows、macOS、Linux 等主流操作系统,本文以 Windows 为例进行说明。Python 版本:建议使用 Python 3.8 及以上版本,以确保代码的兼容性和性能。…

MySQL表的操作 -- 表的增删改查

目录 1. 表的创建2. 表的查看3. 表的修改4. 表的删除5. 总结 1. 表的创建 1.查看字符集及效验规则 2. 表的创建 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎;创建用户表1 创建用…

如何解决极狐GitLab 合并冲突?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 合并冲突 (BASIC ALL) 合并冲突发生在合并请求的两个分支&#xff08;源分支和目标分支&#xff09;对相同代码行进行了不同…

oracle不同数据库版本的自增序列

-- 查看数据库版本 SELECT * FROM v$version WHERE banner LIKE Oracle%; 1. Oracle 12c及以上版本支持 id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id NUMBER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, -- 语法 id NUMBER GENER…

VIC-3D非接触全场应变测量系统用于小尺寸测量之电子元器件篇—研索仪器DIC数字图像相关技术

在5G通信、新能源汽车电子、高密度集成电路快速迭代的今天&#xff0c;电子元件的尺寸及连接工艺已进入亚毫米级竞争阶段&#xff0c;这种小尺寸下的力学性能评估对测量方式的精度有更高的要求&#xff0c;但传统应变测量手段常因空间尺寸限制及分辨率不足难以捕捉真实形变场。…

pod 创建私有库指南

步骤 参考&#xff1a;iOS Pod 私有库创建指南-百度开发者中心 下面主要是对参考链接里面的解释&#xff1a; 创建两个仓库&#xff1a; 一个叫podframe.git&#xff0c;用来存放自定义的framework&#xff0c;比如TestPodFrame.framework一个叫podspec.git&#xff0c;用来…

【JavaEE】Spring AOP的注解实现

目录 一、AOP 与 Spring AOP二、Spring AOP简单实现三、详解Spring AOP3.1 Spring AOP 核心概念3.1.1 切点&#xff08;Pointcut&#xff09;3.1.2 连接点&#xff08;Join Point&#xff09;3.1.3 通知&#xff08;Advice&#xff09;3.1.4 切面&#xff08;Aspect&#xff09…