五种I/O方式及select

什么是IO?

I/O是input/output的缩写,即输入输出端口。每个设备都会有一个专用的I/O地址,用户来处理自己的输入输出信息。在Linux中,一切皆文件,文件就是一串二进制流而已,不管是socket,还是FIFO,管道,终端都是文件,都是流,在信息交换的的过程中,我们都是对这些流进行数据的收发操作,简称I/O操作

I/O操作具体分为俩部分

  1. 等待数据就绪,也就是文件描述符上由事件就绪我们才可以对其进行IO操作
  2. 数据搬迁,说白了就是将一个文件中的数据搬到另一个文件中

五种I/O模型

  1. 阻塞I/O:在内核将数据准备好之前,系统调用会一直等待(所有的套接字,默认都是阻塞方式)
  2. 非阻塞式I/O:如果内核还未将数据准备好,系统调用仍会直接返回,并且返回EWOULDBLOCK错误码

非阻塞IO往往需要程序员以循环的方式反复尝试独写文件描述符,这个过程叫轮询,这对CPU来说是较大的浪费,一般只有特定场景下才使用

  1. 信号驱动I/O:内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作

优点:无需等待;系统通知后去处理;代码简单;无需轮询

缺点:处理读数据时处于阻塞状态;

  1. I/O复用(I/O多路转接):核心在于I/O多路转接可以同时等待多个文件描述符的就绪状态

(用一个线程等待多个文件描述符)

  1. 异步I/O:由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序合适可以开始拷贝数据)

注:收到通知后,不一定会去处理

任何I/O过程中都包含俩个步骤:一是等待数据,二是将数据从内核拷贝到用户空间,而且在实际应用中等待消耗的时间往往都远远高于拷贝让I/O高效,最核心的方法就是让等待时间尽量少

同步通信于异步通信

  • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。一旦调用返回,就得到返回值了;换句话说,就是由调用者主动等待这个调用结果(调用者主动,有返回值)
  • 异步:调用在发出之后,这个调用就直接返回了,没有返回结果,换句话说,当一个异步过程调用发送后,调用者不会立刻得到结果,而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用(被调用者通知调用者,没有返回结果)

这里的同步,与同步和互斥中的同步不一样

线程/进程间的同步互斥:

  • 线程/进程同步:线程/进程间直接的制约关系,基本场景:两个或两个以上的进程/线程在运行过程中协调步调,按预定的先后顺序运行。比如B任务的运行依赖于A任务产生的数据
  • 互斥:一个公共资源同一时刻只能被一个线程/进程使用,多个线程/进程不能同时使用公共资源
  • 同步是一种复杂的互斥,互斥是一种特殊的同步

I/O多路转接之select:

系统提供select函数来实现多路复用输入输出模型

  • select系统调用是用来让我们的程序监视多个文件描述符的变化
  • 程序会停在select这里等待,知道被监视的文件描述符由一个或多个发送了状态改变

select函数原型:

#include<sys/select.h>
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

参数解释:

  • nfds是需要监视的最大的文件描述符值+1
  • rdset,wrset,exset分别对应需要检测的可读文件描述符的集合,可以写文件描述符的集合,以及异常文件描述符的集合
  • 参数timeout为结构timeval,用来设置select()的等待时间, 如果需要监视的文件描述符在这段时间内没有事件发生,则返回值为0

参数timeout的取值:

  • NULL:表示select()没有timeout,select一直被阻塞,直到某个文件描述符上发生了事件
  • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生
  • 特定的时间值:如果在指定的时间段里没有时间发生,select将超时返回

fd_set结构:

void FD_CLR(int fd,fd_set *set); //用来清除描述词组set中相关fd的位
int FD_ISSET(int fd,fd_set *set); //用来测试描述词组set中相关的fd的位是否为真
void FD_SET(int fd, fd_set *set); //用来设置描述词组set中相关的fd位
void FD_ZERO(fd_set *set); //用来清除描述词组set中的全部位

这个结构其实就是一个整数数组,更严格的说,是一个“位图”,使用位图中对应的位来表示要监视的文件描述符

函数返回值:

  • 如果执行成功,返回门将描述符的状态以改变的个数
  • 如果返回0,代表在描述词状态改变之前,已经超过timeout时间,没有返回
  • 当有错误发生时则返回-1,错误原因存于error,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。

理解select执行过程:

理解select模型的关键在于理解fd_set,为了说明方便,去fd_set长度为1字节,fd_set中的每一位代表一个文件描述符,则一个字节长的fd_set最大可以代表8个fd

  • 执行fd_set set;FD_ZERO(&set),则对应的fd_set 用位表示为00000000
  • 若fd=5,执行FD_SET(fd,&set)后,set变为0001 0000(第五位为1)
  • 若在加入fd=2,fd=1,则set变成 0001 0011
  • 执行select(6,&set,0,0,0)阻塞等待*
  • 若fd=1,fd=2上都发生可读事件,则select返回,此时select返回,此时set变成了 0000 0011(没有事件发生的fd=5 被清空)

select读就绪:

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

select的优点:

  • select资源占用比较少
  • 用户量较多的时候它的性能和效率比较好

select的缺点:

  • 它总共监视的文件描述符是有限的,最多1024个
  • 因为参数为输入型参数,在操作时,一直要遍历,查找使用
  • select所监视的文件描述符,select的调用频繁,然后他会反复遍历,可能会达到性能瓶颈
  • 当select频繁调用的过程中,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时很大
  • select的参数为输入输出型

猜你喜欢

转载自blog.csdn.net/audience_fzn/article/details/81276642