目录
一、什么是IO?
二、IO操作的两个阶段
三、五种I/O模型
1、阻塞I/O(blocking I/O)
2、非阻塞I/O(non-blocking I/O)
3、多路复用I/O(multiplexing I/O)
4、信号驱动I/O(signal-driven I/O)
5、异步I/O(asynchronous I/O)
四、五种I/O模型比较
一、什么是IO?
IO 是 Input/Output 的缩写,指的是输入和输出。在计算机当中,IO 操作通常指将数据从一个设备或文件中读取到计算机内存中,或将内存中的数据写入设备或文件中。这些设备可以包括硬盘驱动器、网卡、键盘、屏幕等。
通常用户进程中的一个完整I/O分为两个阶段:用户进程空间→内核空间→设备空间
I/O分为内存I/O、网络I/O和磁盘I/O三种
二、IO操作的两个阶段
Linux中进程无法直接操作I/O设备,其必须通过系统调用请求内核来协助完成I/O操作。
内核会为每个I/O设备维护一个缓冲区。
对于一个输入操作来说,进程I/O系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备(比如网卡设备)中读取(因为设备I/O一般速度较慢,需要等待);
内核缓冲区有数据则直接复制到用户进程空间。所以,对于一个网络输入操作通常包括两个不同阶段:
1、等待网络数据到达网卡,把数据从网卡读取到内核缓冲区,准备好数据。
2、从内核缓冲区复制数据到用户进程空间。
网络I/O的本质是对socket的读取,socket在Linux系统中被抽象为流,I/O可以理解为对流的操作。
网络I/O的模型可分为两种:
●异步I/O(asynchronous I/O)
●同步I/O(synchronous I/O)
同步I/O又包括
●阻塞I/O(blocking I/O)
●非阻塞I/O(non-blocking I/O)
●多路复用I/O(multiplexing I/O)
●信号驱动I/O(signal-driven I/O)
强调一下:信号驱动I/O属于同步I/O,原因往后看。
三、五种I/O模型
1、阻塞I/O(blocking I/O)
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达,当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步是把数据从内核缓冲区复制到应用程序缓冲区。
同步阻塞I/O模型是最常用、最简单的模型。在Linux中,默认情况下,所有套接字都是阻塞的。下面我们以阻塞套接字的recvfrom的调用图来说明阻塞,如图所示
2、非阻塞I/O(non-blocking I/O)
非阻塞的recvform系统调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error(EAGAIN或EWOULDBLOCK)。
进程在返回之后,可以先处理其他的业务逻辑,稍后再发起recvform系统调用。采用轮询的方式检查内核数据,直到数据准备好。再拷贝数据到进程,进行数据处理。
在Linux下,可以通过设置套接字选项使其变为非阻塞。非阻塞的套接字的recvfrom操作如图所示
可以看到前三次调用recvfrom请求时,并没有数据返回,内核返回errno(EWOULDBLOCK),并不会阻塞进程。当第四次调用recvfrom时,数据已经准备好了,于是将它从内核空间拷贝到程序空间,处理数据。
在非阻塞状态下,I/O执行的等待阶段并不是完全阻塞的,但是第二个阶段依然处于一个阻塞状态(调用者将数据从内核拷贝到用户空间,这个阶段阻塞)。
3、多路复用I/O(multiplexing I/O)
I/O多路复用的好处在于单个进程就可以同时处理多个网络连接的I/O。它的基本原理是不再由应用程序自己监视连接,而由内核替应用程序监视文件描述符。
以select函数为例,当用户进程调用了select,那么整个进程会被阻塞,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好,select就会返回。
这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程,如下图所示。
4、信号驱动I/O(signal-driven I/O)
该模型允许socket进行信号驱动I/O,并注册一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据,如图所示
注意:虽然信号驱动IO在注册完信号处理函数以后,就可以做其他事情了。但是第二阶段拷贝数据的过程当中进程依然是被阻塞的,而后要介绍的异步IO是完全不会阻塞进程的,所以信号驱动虽然具有异步的特点,但依然属于同步IO, 原因就是其在从内核中将数据拷贝到用户空间的时候时阻塞的。
5、异步I/O(asynchronous I/O)
相对于同步I/O,异步I/O不是按顺序执行。用户进程进行aio_read系统调用之后,就可以去处理其他逻辑了,无论内核数据是否准备好,都会直接返回给用户进程,不会对进程造成阻塞。这是因为
aio_read只向内核递交申请,并不关心有没有数据。
等到数据准备好了,内核直接复制数据到进程空间,然后内核向进程发送通知,此时数据已经在用户空间了,可以对数据进行处理。
四、五种I/O模型比较
前四种I/O模型都是同步I/O操作,它们的区别在于第一阶段,而第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。
相反,异步I/O模型在等待数据和接收数据的这两个阶段都是非阻塞的,可以处理其他的逻辑,用户进程将整个I/O操作交由内核完成,内核完成后会发送通知。在此期间,用户进程不需要检查I/O操作的状态,也不需要主动拷贝数据。
总结一下:其实只要是涉及到用户进程到内核空间进行数据拷贝这一个动作的都属于是同步IO, 像aio_read这样的系统调用连数据的拷贝都是内核帮我们完成的, 这种才是真正意义上的异步。
好了, 分享到此结束了, 希望对需要的人有启发和帮助。