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

相关文章

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

MySQL高级查询之JOIN、子查询、窗口函数实际案例

《MySQL高级查询之JOIN、子查询、窗口函数实际案例》:本文主要介绍MySQL高级查询之JOIN、子查询、窗口函数实际案例的相关资料,JOIN用于多表关联查询,子查询用于数据筛选和过滤,窗口函... 目录前言1. JOIN(连接查询)1.1 内连接(INNER JOIN)1.2 左连接(LEFT JOI

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

MySQL中FIND_IN_SET函数与INSTR函数用法解析

《MySQL中FIND_IN_SET函数与INSTR函数用法解析》:本文主要介绍MySQL中FIND_IN_SET函数与INSTR函数用法解析,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一... 目录一、功能定义与语法1、FIND_IN_SET函数2、INSTR函数二、本质区别对比三、实际场景案例分

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro