【原创】Linux学习系列-IO模型
IO模型是什么?
个人理解IO模型就是应用程序进行IO操作时和操作系统的通信和协作方式。
为什么要学习IO模型?
我个人在学习Netty、Mina等网络编程框架时,搜索网上资料以及看书的过程中,经常会碰到一些名词:同步、异步、阻塞、非阻塞、事件驱动、轮询等等。这些名词乍一看,貌似我都懂,但是仔细分析时又发现似懂非懂,其实根本原因还是对于Unix的IO模型没有完全理解。
Unix中有哪些IO模型?
Unix中总共定义了五种IO模型:
- 阻塞式IO模型
- 非阻塞式IO模型
- IO复用模型
- 信号驱动式IO模型
- 异步IO模型
图例说明
下面的五幅IO模型图均出自《Unix网络编程卷一:套接字API》。这些图均是以UDP编程API为例子来说明,所以不会看到熟悉的TCP方法。理解这些图,首先需要对于Unix操作系统有一定深度的理解,内核空间和用户空间要明白各自是什么?并且要明白,应用程序和内核有各自的缓冲区,这也是理解异步IO的关键所在!
1.阻塞式IO模型
2.非阻塞式IO模型
阻塞和非阻塞的关键在于应用进程的系统调用是否立即返回?如果立即返回,则称之为非阻塞;反之亦然。对于非阻塞,显然是以应用进程的反复的系统调用为代价来换取及时响应,以达到释放应用进程的目的。从性能角度讲,单从一次完整的IO调用(从发起调用到获得结果)来看,非阻塞的多次系统调用会增加CPU的负载;但是如果从大量的IO调用来看,阻塞IO会占用大量的进程,而进程又是一个稀缺资源,并且进程的增加同样会因为CPU进行大量的上下文切换而严重增加CPU的负载!看来量变引起质变的原理,同样适用于这里!
3.IO复用模型
很多人对于IO复用模型和非阻塞模型比较困惑,没有理解两者之间的那点区别!其实,IO复用的重点是“复用”二字,其核心概念就是一个进程来监控多个Socket的IO事件;而对于非阻塞IO来说,一个进程只能监控一个Socket的IO事件。在编码层面,就是利用轮询函数来实现对于多个Socket的监听,目前的轮询函数包括:select()、poll()、epoll(),这三个轮询函数的区别,后续会单独成文《Linux学习系列-轮询函数》。
4.信号驱动式IO模型
信号驱动式IO模型其实很简单,就是利用IO事件触发的SIGIO信号和其回调函数,在回调函数进行IO处理或者通过回调函数通知应用进程主循环进行IO处理。我们经常在一些网络框架中看到其支持“统一事件源“,其实就是IO复用模型和信号驱动模型的整合,在轮询函数中不但监听IO事件,而且监听信号(事件)。对于”统一事件源“会在《Linux学习系列-轮询函数》中做详细阐述。
5.异步IO模型
从理论上讲,阻塞IO、IO复用和信号驱动IO都是同步模型。因为这三种IO模型中,IO读写操作(将数据从内核缓冲区读入用户缓冲区或将数据从用户缓冲区写入内核缓冲区),都是在IO事件发生之后,由应用程序来完成的。而POSIX规范所定义的异步IO模型则不同。对异步IO而言,用户可以直接对IO进行读写操作,这些操作告诉内核用户读写缓冲区的位置,以及IO操作完成之后内核通知应用程序的方式。异步IO的读写操作总是立即返回,而不论IO是否阻塞的,因为真正的读写操作已经由内核接管。也就是说,同步IO模型要求用户代码自行执行IO操作(将数据从内核缓冲区读入用户缓冲区或将数据从用户缓冲区写入内核缓冲区),而异步IO机制则由内核来执行IO操作(数据在内核缓冲区和用户缓冲区见的移动是有内核在后台完成的)。可以这样理解,同步IO向程序通知的是IO就绪事件,而异步IO向应用程序通知的IO完成事件。