当前位置: 首页 > ds >正文

【Linux】48.高级IO(2)

文章目录

      • 1.3.5 socket就绪条件
      • 1.3.6 select的特点
      • 1.3.7 select缺点
      • 1.3.8 I/O多路转接之poll [选学]
      • 1.3.9 socket就绪条件
      • 1.3.10 poll的优点
      • 1.3.11 poll的缺点
      • 1.3.12 I/O多路转接之epoll
      • 1.3.13 epoll的相关系统调用
        • epoll_create
        • epoll_ctl
        • epoll_wait
      • 1.3.14 epoll工作原理


1.3.5 socket就绪条件

读就绪

  • socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件描述符, 并且返回值大于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;

写就绪

  • socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
  • socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE信号;
  • socket使用非阻塞connect连接成功或失败之后;
  • socket上有未读取的错误;

异常就绪(选学)

  • socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(回忆TCP协议头中, 有一个紧急指针的字段),

1.3.6 select的特点

  • 可监控的文件描述符个数取决与sizeof(fd_set)的值. 我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096.
  • 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,
    1. 用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。
    2. select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

备注: fd_set的大小可以调整,可能涉及到重新编译内核


1.3.7 select缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小.

1.3.8 I/O多路转接之poll [选学]

poll函数接口

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};

参数说明

  • fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
  • nfds表示fds数组的长度.
  • timeout表示poll函数的超时时间, 单位是毫秒(ms).

events和revents的取值:

a91c5409a16698ff86be447804b978f8

6812da0e8afb0ed2a38685ddf319650b

返回结果

  • 返回值小于0, 表示出错;
  • 返回值等于0, 表示poll函数等待超时;
  • 返回值大于0, 表示poll由于监听的文件描述符就绪而返回.

1.3.9 socket就绪条件

同select


1.3.10 poll的优点

fdset的方式,poll使用一个pollfd不同与 的指针实现 select.使用三个位图来表示三个

  • pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比select更方便.
  • poll并没有最大数量限制 (但是数量过大后性能也是会下降).

1.3.11 poll的缺点

poll中监听的文件描述符数目增多时

  • 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
  • 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降.

1.3.12 I/O多路转接之epoll

epoll初识:

按照man手册的说法: 是为处理大批量句柄而作了改进的poll.

它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路


1.3.13 epoll的相关系统调用

epoll 有3个相关的系统调用

epoll_create
int epoll_create(int size);

创建一个epoll的句柄.

  • 自从linux2.6.8之后,size参数是被忽略的.
  • 用完之后, 必须调用close()关闭.
epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数.

  • 它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
  • 第一个参数是epoll_create()的返回值(epoll的句柄).
  • 第二个参数表示动作,用三个宏来表示.
  • 第三个参数是需要监听的fd.
  • 第四个参数是告诉内核需要监听什么事.

第二个参数的取值:

  • EPOLL_CTL_ADD :注册新的fd到epfd中;
  • EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL :从epfd中删除一个fd;

struct epoll_event结构如下:

b2292a272b7c0e18809a8c8e690b6dd0

events可以是以下几个宏的集合:

  • EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
  • EPOLLOUT : 表示对应的文件描述符可以写;
  • EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
  • EPOLLERR : 表示对应的文件描述符发生错误;
  • EPOLLHUP : 表示对应的文件描述符被挂断;
  • EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
  • EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里.

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

收集在epoll监控的事件中已经发送的事件.

  • 参数events是分配好的epoll_event结构体数组.
  • epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
  • maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
  • 参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
  • 如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败

1.3.14 epoll工作原理

73b45dc154693aa1cec2476d572399f2

  • 当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关.
struct eventpoll{ .... /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ struct list_head rdlist; .... 
};
  • 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件.
  • 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度).
  • 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.
  • 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到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).

总结一下, epoll的使用过程就是三部曲:

  • 调用epoll_create创建一个epoll句柄;
  • 调用epoll_ctl, 将要监控的文件描述符进行注册;
  • 调用epoll_wait, 等待文件描述符就绪;
http://www.xdnf.cn/news/7757.html

相关文章:

  • Leetcode 01 java
  • 已解决:Git冲突完全解决指南(附最佳实践)
  • ANSI V 级对夹球阀控制阀:高性价比零泄漏流体控制新选择-耀圣
  • Windows下使用Windeployqt.exe打包后运行exe程序报错0xc000007b问题解决方法
  • 数组day2
  • 在hadoop中实现序列化与反序列化
  • YOLOv12和MAF-YOLO的核心技术细节
  • 软考软件评测师——软件工程之开发模型与方法
  • Java中的工具类Collections和Arrays
  • odoo-052 odoo启动提示:OSError: [Errno 98] Address already in use,端口占用
  • 一些C++入门基础
  • 记忆化搜索全面解析
  • 基于 STM32 的蔬菜智能育苗系统硬件与软件设计
  • 第41天-Python+Qt四屏播放器开发指南
  • Java实践:调用jar包里的方法
  • 以太网口16路数字量DI输入采集模块 Modbus TCP协议
  • Unreal5 从入门到精通之如何实现 离线语音识别
  • Map更简洁的编码构建
  • 【jzxxoj编程:4420: 寻找自我3】2022-1-30
  • 【免杀】C2免杀技术(七)远程线程注入
  • 使用SQLite Expert个人版VACUUM功能修复数据库
  • 【Linux】第二十一章 管理存储堆栈
  • 如何处理 collation 导致的索引失效 | OceanBase SQL调优实践
  • Redis中的事务和原子性
  • 汽车充电过程中--各个电压的关系(DeepSeek)
  • Dockerfile 实战:编写高效镜像的最佳实践与常见误区
  • AR 开启昆虫学习新视界,解锁奇妙微观宇宙
  • 重构研发效能:项目管理引领软件工厂迈向智能化
  • 汽车生产中的测试台连接 – EtherCAT 转CANopen高效的网关通信
  • PyTorch中单卡训练、DataParallel(DP)和DistributedDataParallel(DDP)