linux的i/o模型

同步、异步,阻塞、非阻塞,这四种状态常分不清,主要是这四种状态的定义本身也不是很明确,所以各种解答的方式都有。常见的分类有以下:

  • 同步阻塞IO — BIO (java.io)
  • 同步非阻塞IO —NIO(java.nio)
  • 异步非阻塞IO —AIO (java.nio)

阻塞是指执行I/O操作的线程,在I/O操作过程中能不能处理其他任务;

同步指I/O操作过程中的消息通知机制。

举例说明

同步/异步

你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下”,然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

阻塞/非阻塞

你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。

在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

2. Unix 5种I/O模型

《UNIX网络编程:卷一》的第六章书中列出了五种IO模型:

  • 阻塞式I/O
  • 非阻塞式I/O
  • I/O复用(select,poll,epoll…)
  • 信号驱动式I/O(SIGIO)
  • 异步I/O(POSIX的aio_系列函数)

2.1 阻塞式I/O

同步阻塞 IO 模型是最常用的一个模型,也是最简单的模型。在linux中,默认情况下所有的socket都是blocking。它符合人们最常见的思考逻辑。

在这个IO模型中,用户空间的应用程序执行一个系统调用(recvform),这会导致应用程序阻塞,什么也不干,直到数据准备好,等待kernel准备好从网络上接收到的数据报 + 等待收到的报文被从kernel复制到buf中,recvfrom方法才会返回,最后进程再处理数据。

这就是阻塞式IO模型

阻塞式I/O

2.2 非阻塞式I/O

非阻塞IO时对一个非阻塞描述符循环调用recvfrom,持续的轮询(polling),以查看某个操作是否就绪。与阻塞IO不一样,”非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 ‘被’ CPU光顾”。

非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。如此循环的进行recvform系统调用,检查内核数据,直到数据准备好,再拷贝数据到进程。拷贝数据整个过程,进程仍然是属于阻塞的状态

这就是非阻塞式IO模型

非阻塞式    i/o

2.3 I/O复用

IO multiplexing就是我们说的select,poll,epoll 。为何叫多路复用,是因为它I/O多路复用可以同时监听多个fd,如此就减少了为每个需要监听的fd开启线程的开销。

select调用是内核级别的,可以等待多个socket,能实现同时对多个IO端口进行监听,当其中任何一个socket的数据准好了,就能返回进行可读然后进程再进行recvform系统调用,将数据由内核拷贝到用户进程,这个过程是阻塞的。

I/O复用模型会用到select、poll、epoll函数,这几个函数也会使进程阻塞,但是和阻塞I/O所不同的的,这几个函数可以同时阻塞多个I/O操作`。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时(不是等到socket数据全部到达再处理, 而是有了一部分数据就会调用用户进程来处理),才真正调用I/O操作函数。

IO复用有人把其成为同步非阻塞的,也有称为同步阻塞。其实这个是否阻塞还需要看第一个阶段,第一个阶段有的阻塞,有的不阻塞。主要也是阻塞在select阶段,属于用户主动等待阶段,我们且规范为阻塞状态,所以,把IO多路复用归为同步阻塞模式

这是IO复用的模型:

select、poll、epoll的不同

img

2.4 信号驱动式I/O

信号驱动式I/O:首先我们允许Socket进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

也就是说第一个阶段,完全是非阻塞的,等数据到达会给一个信号通知,第二个阶段recvfrom还是阻塞过程,和之上无差异。

信号驱动式I/O 过程如下:

2.5 异步I/O

异步IO不是顺序执行,用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知IO两个阶段,进程都是非阻塞的

2.6 总结

针对这5中IO模型,我采用一张图来总结一下。

3. java IO

Unix中的五种I/O模型,除信号驱动I/O外,Java对其它四种I/O模型都有所支持。其中Java最早提供的blocking I/O即是同步阻塞I/O,而NIO即是同步非阻塞I/O,同时通过NIO实现的Reactor模式即是I/O复用模型的实现,通过AIO实现的Proactor模式即是异步I/O模型的实现。

所以说严格意义上来说,通过Reactor模式实现的NIO,和unix中的I/O多路复用是相同的概念,但这是一种编程模型,而不是原生支持。这也是我们下面所要进行的netty讲解的主要思想。