阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO
Linux 五种 IO 模型
1、基本概念
五种IO模型包括:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO。
首先需要了解下系统调用的几个函数和基本概念。
1.1 简单介绍几个系统调用函数
recvfrom: Linux系统提供给用户用于接收网络IO的系统接口。从套接字上接收一个消息,可同时应用于面向连接和无连接的套接字。
如果此系统调用返回值 <0,并且 errno 为 EWOULDBLOCK或EAGAIN(套接字已标记为非阻塞,而接收操作被阻塞或者接收超时 )时,连接正常,阻塞接收数据(这很关键,前4种IO模型都设计此系统调用)。
select: select系统调用允许程序同时在多个底层文件描述符上,等待输入的到达或输出的完成。以数组形式存储文件描述符,64位机器默认2048个。当有数据准备好时,无法感知具体是哪个流OK了,所以需要一个一个的遍历,函数的时间复杂度为O(n)。
poll: 以链表形式存储文件描述符,没有长度限制。本质与select相同,函数的时间复杂度也为O(n)。
epoll: 是基于事件驱动的,如果某个流准备好了,会以事件通知,知道具体是哪个流,因此不需要遍历,函数的时间复杂度为O(1)。
sigaction: 用于设置对信号的处理方式,也可检验对某信号的预设处理方式。Linux使用SIGIO信号来实现IO异步通知机制。
1.2 同步 & 异步
同步和异步是针对应用程序和内核交互而言的,也可理解为被被调用者(操作系统)的角度来说。 同步是用户进程触发IO操作并等待或轮询的去查看是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知,需要CPU支持
1.3 阻塞 & 非阻塞
阻塞和非阻塞是针对于进程在访问数据的时候,也可理解为调用者(程序)角度来说。根据IO操作的就绪状态来采取的不同的方式。 阻塞方式下读取或写入方法将一直等待,而非阻塞方式下读取或写入方法会立即返回一个状态值。
2、详细
阻塞式IO
-
使用系统调用,并一直阻塞直到内核将数据准备好,之后再由内核缓冲区复制到用户态,在等待内核准备的这段时间什么也干不了
-
下图函数调用期间,一直被阻塞,直到数据准备好且从内核复制到用户程序才返回,这种IO模型为阻塞式IO
-
阻塞式IO式最流行的IO模型
非阻塞式IO
-
内核在没有准备好数据的时候会返回错误码,而调用程序不会休眠,而是不断轮询询问内核数据是否准备好
-
下图函数调用时,如果数据没有准备好,不像阻塞式IO那样一直被阻塞,而是返回一个错误码。数据准备好时,函数成功返回。
-
应用程序对这样一个非阻塞描述符循环调用成为轮询。
-
非阻塞式IO的轮询会耗费大量cpu,通常在专门提供某一功能的系统中才会使用。通过为套接字的描述符属性设置非阻塞式,可使用该功能
IO多路复用
-
类似与非阻塞,只不过轮询不是由用户线程去执行,而是由内核去轮询,内核监听程序监听到数据准备好后,调用内核函数复制数据到用户态
-
下图中select这个系统调用,充当代理类的角色,不断轮询注册到它这里的所有需要IO的文件描述符,有结果时,把结果告诉被代理的recvfrom函数,它本尊再亲自出马去拿数据
-
IO多路复用至少有两次系统调用,如果只有一个代理对象,性能上是不如前面的IO模型的,但是由于它可以同时监听很多套接字,所以性能比前两者高
-
多路复用包括:
- select:线性扫描所有监听的文件描述符,不管他们是不是活跃的。有最大数量限制(32位系统1024,64位系统2048)
- poll:同select,不过数据结构不同,需要分配一个pollfd结构数组,维护在内核中。它没有大小限制,不过需要很多复制操作
- epoll:用于代替poll和select,没有大小限制。使用一个文件描述符管理多个文件描述符,使用红黑树存储。同时用事件驱动代替了轮询。epoll_ctl中注册的文件描述符在事件触发的时候会通过回调机制激活该文件描述符。epoll_wait便会收到通知。最后,epoll还采用了mmap虚拟内存映射技术减少用户态和内核态数据传输的开销
信号驱动式IO
-
使用信号,内核在数据准备就绪时通过信号来进行通知
-
首先开启信号驱动io套接字,并使用sigaction系统调用来安装信号处理程序,内核直接返回,不会阻塞用户态
-
数据准备好时,内核会发送SIGIO信号,收到信号后开始进行io操作
异步IO
-
异步IO依赖信号处理程序来进行通知
-
不过异步IO与前面IO模型不同的是:前面的都是数据准备阶段的阻塞与非阻塞,异步IO模型通知的是IO操作已经完成,而不是数据准备完成
-
异步IO才是真正的非阻塞,主进程只负责做自己的事情,等IO操作完成(数据成功从内核缓存区复制到应用程序缓冲区)时通过回调函数对数据进行处理
-
unix中异步io函数以aio_或lio_打头
3、对比
-
前面四种IO模型的主要区别在第一阶段,他们第二阶段是一样的:数据从内核缓冲区复制到调用者缓冲区期间都被阻塞住!
-
前面四种IO都是同步IO:IO操作导致请求进程阻塞,直到IO操作完成
-
异步IO:IO操作不导致请求进程阻塞