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

相关文章

MySQL中COALESCE函数示例详解

《MySQL中COALESCE函数示例详解》COALESCE是一个功能强大且常用的SQL函数,主要用来处理NULL值和实现灵活的值选择策略,能够使查询逻辑更清晰、简洁,:本文主要介绍MySQL中C... 目录语法示例1. 替换 NULL 值2. 用于字段默认值3. 多列优先级4. 结合聚合函数注意事项总结C

Linux下修改hostname的三种实现方式

《Linux下修改hostname的三种实现方式》:本文主要介绍Linux下修改hostname的三种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下修改ho编程stname三种方式方法1:修改配置文件方法2:hFvEWEostnamectl命

Linux虚拟机不显示IP地址的解决方法(亲测有效)

《Linux虚拟机不显示IP地址的解决方法(亲测有效)》本文主要介绍了通过VMware新装的Linux系统没有IP地址的解决方法,主要步骤包括:关闭虚拟机、打开VM虚拟网络编辑器、还原VMnet8或修... 目录前言步骤0.问题情况1.关闭虚拟机2.China编程打开VM虚拟网络编辑器3.1 方法一:点击还原VM

Linux搭建Mysql主从同步的教程

《Linux搭建Mysql主从同步的教程》:本文主要介绍Linux搭建Mysql主从同步的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux搭建mysql主从同步1.启动mysql服务2.修改Mysql主库配置文件/etc/my.cnf3.重启主库my

Linux系统之authconfig命令的使用解读

《Linux系统之authconfig命令的使用解读》authconfig是一个用于配置Linux系统身份验证和账户管理设置的命令行工具,主要用于RedHat系列的Linux发行版,它提供了一系列选项... 目录linux authconfig命令的使用基本语法常用选项示例总结Linux authconfi

jdk21下载、安装详细教程(Windows、Linux、macOS)

《jdk21下载、安装详细教程(Windows、Linux、macOS)》本文介绍了OpenJDK21的下载地址和安装步骤,包括Windows、Linux和macOS平台,下载后解压并设置环境变量,最... 目录1、官网2、下载openjdk3、安装4、验证1、官网官网地址:OpenJDK下载地址:Ar

Java8需要知道的4个函数式接口简单教程

《Java8需要知道的4个函数式接口简单教程》:本文主要介绍Java8中引入的函数式接口,包括Consumer、Supplier、Predicate和Function,以及它们的用法和特点,文中... 目录什么是函数是接口?Consumer接口定义核心特点注意事项常见用法1.基本用法2.结合andThen链

linux本机进程间通信之UDS详解

《linux本机进程间通信之UDS详解》文章介绍了Unix域套接字(UDS)的使用方法,这是一种在同一台主机上不同进程间通信的方式,UDS支持三种套接字类型:SOCK_STREAM、SOCK_DGRA... 目录基础概念本机进程间通信socket实现AF_INET数据收发示意图AF_Unix数据收发流程图A

linux环境openssl、openssh升级流程

《linux环境openssl、openssh升级流程》该文章详细介绍了在Ubuntu22.04系统上升级OpenSSL和OpenSSH的方法,首先,升级OpenSSL的步骤包括下载最新版本、安装编译... 目录一.升级openssl1.官网下载最新版openssl2.安装编译环境3.下载后解压安装4.备份

MySQL 日期时间格式化函数 DATE_FORMAT() 的使用示例详解

《MySQL日期时间格式化函数DATE_FORMAT()的使用示例详解》`DATE_FORMAT()`是MySQL中用于格式化日期时间的函数,本文详细介绍了其语法、格式化字符串的含义以及常见日期... 目录一、DATE_FORMAT()语法二、格式化字符串详解三、常见日期时间格式组合四、业务场景五、总结一、