Linux下的五种I/O模型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40378034/article/details/88144263

一、程序空间与内核空间

在Linux中,对于一次读取的I/O的操作,数据并不会直接拷贝到程序的程序缓冲区。它首先会被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的缓冲区

  • Waiting for the data to be ready(等待数据到达内核缓冲区)
  • Copying the data from the kernel to the process(从内核缓冲区拷贝数据到程序缓冲区)

二、文件描述符

在Linux下面一切皆文件,文件描述符是内核为文件所创建的索引,所有I/O操作都通过调用文件描述符来执行,包括下面提到的socket。Linux刚启动的时候会自动设置0是标准输入,1是标准输出,2是标准错误

三、阻塞I/O

在这里插入图片描述

进程调用一个recvfrom请求,但是它不能立刻收到回复,直到数据返回,然后将数据从内核空间复制到程序空间。在数据返回前,进程都处于阻塞状态,在等待数据返回的过程中不能空闲出来干其他的事情

四、非阻塞I/O

在这里插入图片描述

当我们设置一个socket为非阻塞,相当于告诉内核当我们请求的I/O操作不能立即得到返回结果,不要把进程设置为sleep状态,而是返回一个错误信息(图中的EWOULDBLOCK)

图中前三次我们调用recvfrom请求,但是并没有数据返回,所以内核只能返回一个错误信息,但是当我们第四次调用recvfrom,数据已经准备好了,然后将它从内核空间复制到程序空间

在非阻塞状态下,等待数据的过程并不是完全阻塞的,但是从内核缓冲区拷贝数据到程序缓冲区依然处于一个阻塞状态

五、I/O复用

I/O复用的好处是我们可以通过(select/poll/epoll)一个时刻处理多个文件描述符,以select为例

在这里插入图片描述

I/O复用也是完全阻塞的,但是可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数

六、信号驱动I/O

信号驱动I/O要求内核通知我们当文件描述符准备就绪以后发送相应的信号

在这里插入图片描述

阶段1:我们首先设置socket为一个信号驱动I/O,并安装一个信号处理函数,这个过程是瞬时的,所以这个阶段是非阻塞的。阶段2:当数据已经准备好了以后,一个SIGIO信号传送给我们的进程告诉我们数据准备好了,然后进程开始等待数据从内核空间复制到程序空间,这个过程是阻塞的,因为我们的进程只能等待数据复制完毕

七、异步I/O

在这里插入图片描述

异步就是说对于等待数据到达内核缓冲区和从内核缓冲区拷贝数据到程序缓冲区这两个步骤当它们完成的时候会自动通知进程,在这段时间里面进程什么都不用操心。相比前面的信号驱动I/O,异步I/O两个阶段都是非阻塞的

八、select/poll/epoll

1)、select

基本原理:select函数监视的文件描述符分3类,分别是writefds、readfds和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写或者except),或者超时(timeout指定等待时间,如果超时立即返回设为null)函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低

  • select最大的缺陷就是单个进程所打开的fd是有一定限制的,它由FD_SETSIZE设置,默认值为1024
  • 开销大(内存拷贝):包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,每次在select调用之前,需要将监听的描述符从用户态空间拷贝到内核的地址空间,在select调用都返回整个用户注册的事件集合,它的开销随着文件描述符数量的增加而线性增大
  • 效率问题:内核在帮助应用程序监听多个描述符的时候,是一种轮询检测就绪事件的方式,扫描判断哪个socket描述符的位是就绪的,时间复杂度为O(n)

2)、poll

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd

poll没有最大连接数的限制,原因是它是基于链表来存储的,但是同样存在缺点:大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义

poll还有一个特点是水平触发,如果报告了fd后,没有被处理,那么下次poll时会再次报告该poll

3)、epoll

epoll是在2.6内核中提出的,相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放在内核的一个事件表中,这样在用户空间和内核空间的copy只需一次

基本原理:epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用事件的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知

epoll的优点:

  • 没有最大并发连接的限制,能打开的fd的上限远大于1024
  • 效率提升,不是轮询的方式,不会随着fd数目的增加效率下降
  • 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递,即epoll使用mmap减少复制开销

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT是默认模型,区别如下:

  • LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件
  • ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件

参考:https://segmentfault.com/a/1190000007986694

https://blog.csdn.net/f2016913/article/details/77850683

https://www.cnblogs.com/jeakeven/p/5435916.html

https://www.jianshu.com/p/c1ac32448a50

猜你喜欢

转载自blog.csdn.net/qq_40378034/article/details/88144263