Linux - 三组 I/O 复用函数的比较

2023-12-27 14:32

本文主要是介绍Linux - 三组 I/O 复用函数的比较,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

三组 I/O 复用函数的比较

  • select
  • poll
  • epoll

select

/* According to POSIX.1-2001 */
#include <sys/select.h>/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

poll

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);

epoll

#include <sys/epoll.h>// 返回一个文件描述符, 标识内核事件表
int epoll_create(int size);
// 操作内核事件表
int epoll_ctl(int epfd, // epoll_create 的返回值int op, // 操作类型: 注册 修改 删除int fd, // 要操作的文件描述符struct epoll_event *event); // 事件类型
// 在一段超时时间内等待一组文件描述符上的事件
// 成功返回就绪的文件描述符的个数, 失败返回-1
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
LT & ET 模式

LT 模式是默认的工作模式 , 此模式下的 epoll 相当于一个效率较高的 poll

当在 epoll 内核事件表中注册一个文件描述符上的 EPOLLET 事件时 , epoll 将以 ET 模式来操作该文件描述符 , ET 模式是 epoll 的高效工作模式

对于采用 LT 工作模式的文件描述符 , 当 epoll_wait 检测到其上有事件发生并将此事件通知应用程序后 , 应用程序可以不立即处理该事件 , 当下一次调用 epoll_wait 时 , 该事件还会被再次通告

对于采用 ET 工作模式的文件描述符 , 应用程序必须立刻处理该事件 , 因为后续的 epoll_wait 调用将不再通知这一事件

ET 模式在很大程度上降低了同一个事件被重复触发的次数 , 因此效率要比 LT 模式高

EPOLLONESHOT 事件

在多进程或多线程中 , 如果一个进程(或线程)读取完某个 socket 上的数据后开始处理这些数据 , 在这期间该 socket 上又有新的数据可读 (EPOLLIN 再次被触发) , 这时另一个进程(线程)就会被唤醒来读取这些新的数据 , 此时就出现了两个进程(线程)同时操作一个 socket 的局面

为了解决这个问题 , 让一个 socket 在任意时刻都只被一个进程(线程)处理 , 可以使用 epoll 的 EPOLLONESHOT 事件实现

对于注册了 EPOLLONESHOT 事件的文件描述符 , 系统最多只触发一个其上注册的事件(可读事件, 可写事件, 异常事件) , 并且只触发一次 , 除非使用 epoll_ctl 函数重置该文件描述符上注册的 EPOLLONESHOT 事件

所以当 一个线程处理完注册了 EPOLLONESHOT 事件的socket的时候 , 该线程应该立即重置这个 socket 上的 EPOLLONESHOT 事件 , 以确保下一次这个 socket 可读时 , 其 EPOLLIN 事件能被触发 , 能让其他线程有机会继续处理这个 socket

epoll 工作原理

当某⼀进程调用 epoll_create 函数时,Linux内核会创建⼀个 eventpoll 结构体,这个结构体中有两个成员与 epoll 的使用方式密切相关

struct eventpoll { ... .../*红⿊树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给⽤户的满⾜条件的事件*/ struct list_head rdlist; ... ...
} ; 

每⼀个epoll对象都有⼀个独⽴的 eventpoll 结构体,⽤于存放通过 epoll_ctl 函数向epoll对象中添加进来的事件
这些事件都会挂载在红⿊树中,如此重复添加的事件就可以通过红⿊树⽽⾼效的识别出来(红⿊树的插⼊时间效率是O(logN),其中N为树的⾼度)
⽽所有添加到epoll中的事件都会与设备(网卡)驱动程序建⽴回调关系

也就是说 , 当响应的事件发⽣时会调⽤这个回调⽅法
这个回调⽅法在内核中叫 eppollcallback , 它会将发⽣的事件添加到 rdlist 双链表中
在epoll中,对于每⼀个事件,都会建⽴⼀个epitem结构体

struct epitem
{struct rb_node rbn;//红黑树节点struct list_head rdllink;//双向链表节点struct epoll_filefd ffd; //事件句柄信息struct eventpoll *ep; //指向其所属的 eventpoll 对象struct epoll_event event; //期待发生的事件类型
}

当调⽤ epoll_wait 检查是否有事件发⽣时,只需要检查 eventpoll 对象中的rdlist 双链表中是否有 epitem 元素即可

如果 rdlist 不为空,则把发⽣的事件复制到⽤户态,同时将事件数量返回给⽤户

这个操作的时间复杂度是O(1)

相同点

都能同时监听多个文件描述符

都是通过某种结构体变量来告诉内核监听哪些文件描述符上的哪些事件 , 并使用该结构体类型的参数来获取内核处理的结果

不同点

select 的参数类型 fd_set 没有将文件描述符和事件绑定 , 只是一个文件描述符集合 , 因此 select 需要提供 三个此类型的参数来分别 传入和输出 可读 , 可写 , 异常等事件 , 这使得 select 不能处理更多类型的事件 , 又因为内核对 fd_set 集合的在线修改 , 下次调用 select 之前必须重置 这 3 个 fd_set 集合

poll 的参数类型 pollfd 将文件描述符和事件绑定 , 任何事件都被统一处理 , 并且内核每次修改的是 pollfd 结构体的 revents 成员 , 而 events 成员保持不变 , 因此下次调用 poll 时无需重置 pollfd 类型的事件集参数

epoll 适用于连接数量多 , 但活动连接较少的情况 , 因为当活动连接较多时 , 回调函数的触发过于频繁 , 其效率未必高于 select 和 poll

系统调用selectpollepoll
事件集合用户通过 3 个fd_set类型的参数分别传入可读, 可写, 异常事件, 内核通过对这些参数的在线修改来反馈其中的就绪事件 . 这使得每次调用 select 都要重置这 3 个参数统一处理所有事件类型 , 因此只需要一个 事件集参数 . 用户通过 pollfd.events 传入事件 , 内核通过修改 pollfd.revents 反馈其中就绪的事件内核通过一个事件表直接管理所有事件 . 每次调用 epoll_wait无需反复传入用户事件 . epoll_wait 的参数 events 仅用来反馈就绪的事件
应用程序索引就绪文件描述符的时间复杂度O(n)O(n)O(1)
最大支持的文件描述符数量有最大限制6553565535
工作模式LT 模式LT 模式LT / ET (高效模式)
内核实现和工作效率采用轮训方式检测就绪事件 , 算法的时间复杂度为 O(n)采用轮训方式检测就绪事件 , 算法的时间复杂度为 O(n)采用回调机制来检测就绪事件 , 算法时间复杂度为 O(1)

这篇关于Linux - 三组 I/O 复用函数的比较的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/543432

相关文章

GO语言中函数命名返回值的使用

《GO语言中函数命名返回值的使用》在Go语言中,函数可以为其返回值指定名称,这被称为命名返回值或命名返回参数,这种特性可以使代码更清晰,特别是在返回多个值时,感兴趣的可以了解一下... 目录基本语法函数命名返回特点代码示例命名特点基本语法func functionName(parameters) (nam

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Python Counter 函数使用案例

《PythonCounter函数使用案例》Counter是collections模块中的一个类,专门用于对可迭代对象中的元素进行计数,接下来通过本文给大家介绍PythonCounter函数使用案例... 目录一、Counter函数概述二、基本使用案例(一)列表元素计数(二)字符串字符计数(三)元组计数三、C

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

Python中的filter() 函数的工作原理及应用技巧

《Python中的filter()函数的工作原理及应用技巧》Python的filter()函数用于筛选序列元素,返回迭代器,适合函数式编程,相比列表推导式,内存更优,尤其适用于大数据集,结合lamb... 目录前言一、基本概念基本语法二、使用方式1. 使用 lambda 函数2. 使用普通函数3. 使用 N

Python如何实现高效的文件/目录比较

《Python如何实现高效的文件/目录比较》在系统维护、数据同步或版本控制场景中,我们经常需要比较两个目录的差异,本文将分享一下如何用Python实现高效的文件/目录比较,并灵活处理排除规则,希望对大... 目录案例一:基础目录比较与排除实现案例二:高性能大文件比较案例三:跨平台路径处理案例四:可视化差异报

MySQL中REPLACE函数与语句举例详解

《MySQL中REPLACE函数与语句举例详解》在MySQL中REPLACE函数是一个用于处理字符串的强大工具,它的主要功能是替换字符串中的某些子字符串,:本文主要介绍MySQL中REPLACE函... 目录一、REPLACE()函数语法:参数说明:功能说明:示例:二、REPLACE INTO语句语法:参数

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全