POSIX(可移植操作系统接口)把同步IO操作定义为导致进程阻塞直到IO完成的操作,反之则是异步IO
这些概念之所以容易令人迷惑,在于很多人对I/O就没有清晰准确的理解,后面的理解自然不可能正确。我想用一个具体的例子来说明一下I/O。
设想自己是一个进程。这个进程需要接收一个输入,我们不管这个输入是从网络套接字来,还是键盘,鼠标来,输入的来源可以千千万万。但是,都必须由内核来帮进程完成,为啥内核这么霸道?因为计算机上运行的可不只是咱进程一个进程,还有很多进程。这些进程兄弟也可能需要从这些输入设备接收输入,没有内核居中协调,岂不是乱套。
从进程的角度看,内核帮助它完成输入,其实包括三个步骤:
- 1、内核替进程接收好数据,这些数据暂时存在内核的内存空间
- 2、内核将数据从自己的内存空间复制到进程的内存空间
- 3、告诉(通知)进程,输入数据来了,赶快读吧
这三步看似挺简单,其实在具体实现时,有很多地方需要考虑:
- 0、进程如何告诉内核自己要接收一个输入?
- 1、内核接到进程的请求,替进程接收好数据这段时间, 进程咋办?
- 2、内核在将数据复制到进程的内存空间这段时间,进程咋办?
- 3、到底什么时候告诉进程数据准备好了,是在内核接收好数据之后就告诉进程,还是在将数据复制到进程的内存空间之后再告诉他?
- 4、内核以什么样的方式告诉进程,数据准备好了?
2、阻塞式I/O模型
对上面5个问题,最简单的解决方案就是阻塞式I/O模型,它的过程是这样的:
进程:内核内核,我要接收一个键盘输入,快点帮我完成!
内核:好咧!biubiu!内核迅速将进程阻塞,可怜的进程顿时石化,就像被孙悟空点了定一样。
就这样,进程在石化中,时间一点点流逝。终于,内核收到了数据。
内核:数据终于来了,我要开干了!duang duang duang,先把数据存在自己的内核空间,然后又复制到进程的用户空间。
内核:biubiu!内核解除了进程的阻塞,进程瞬间复活,进程的记忆还是停留在让内核帮他接收输入时。
进程:哇!内核真靠谱,数据已经有了!干活去!
我们可以看到,进程发出接收输入的请求给内核开始,就处于阻塞状态,直到内核将数据复制到进程的用户空间,进程才解除阻塞。
以上过程可通过下图来解释:
3、非阻塞式I/O
进程发现,阻塞式I/O中,自己总要被阻塞好久,好不爽啊,于是进程改用了非阻塞式I/O,其过程是这样的:
进程:内核内核,我要接收一个输入,赶紧帮我看看,数据到了没有,先说好,不要阻塞我。
内核:查看了一下自己的内核空间,没有发现数据,于是迅速告诉进程,没有呢!并继续帮进程等着数据。
如此这样,进程不断地问内核,终于,过了一段时间,进程再一次询问时,内核往自己的空间中一查,呦!数据来了,不胜其烦的内核迅速告诉进程,数据好了!
进程:快给我!
内核:biu!内核干净利落地阻塞了进程,悲催的进程还是石化了!
内核赶紧将自己空间的输入数据复制到进程的用户空间,复制好后。
内核:biu!内核解除了进程的阻塞,进程立马复活
进程:哇!数据来了,啥也不说,干活!
我们看到,所谓的非阻塞I/O,其实在内核将数据从内核空间复制到进程的用户空间时,进程还是被阻塞的。
具体过程如下图所示:
4、异步I/O
上面的两种I/O解决方案中,进程都被阻塞了,只不过是阻塞时间长短不一样。
- 第一种方案中进程被阻塞的时间长一些,在内核接收数据以及将数据复制到进程的用户空间时,都被阻塞。
- 第二种方案中,只在内核将数据从内核空间复制到进程的用户空间时,进程才被阻塞。
我们现在说的异步I/O,目的就是让进程绝对不被阻塞。其过程是这样的:
进程:内核内核,我要接收一个输入,弄好了告诉我。同时将一个信号和信号处理函数告诉内核,然后继续干自己的活了。
内核:得了您嘞,您先忙。
一直到内核接收到数据并将数据从内核空间复制到进程的用户空间后,内核才给进程发送信号。进程在信号处理函数中可以直接处理数据。
其过程可用下图解释:
5、IO多路复用
在上面的阻塞IO中,我们的进程都是直接阻塞在IO系统调用recvfrom上,可以说是与IO系统调用“零距离”。
这种做法有一定的弊端,就是当进程要同时等待多个输入,哪个先来就处理哪个时,进程不知道哪个先来,自然也就不知道应该被阻塞到哪一个输入的recvfrom上好。
聪明如你,可能会说,那全部用异步IO呗。但这样也不方便,就是进程要为每一个输入都在系统中注册一个信号和信号处理函数,这可是要与内核打交道的。如果要等的输入非常多,岂不是非常的麻烦。
为了方便处理这种情况,进程可以使用一个输入管家,就是select系统调用,该调用可以帮助进程管理所有的输入(通常是套接字),每当一个输入中有数据到来时,就告诉进程。这样进程实际上是阻塞在select系统调用上,而不是阻塞在真正的IO系统调用上,如下图所示:
6、那啥是同步呢?
一句话,凡是让进程阻塞(不管长短)的I/O方案都是同步I/O。也就是说,阻塞、非阻塞、信号驱动式都是同步I/O。
7、总结
IO分两阶段:
1.数据准备阶段
2.内核空间复制回用户进程缓冲区阶段
一般来讲:阻塞IO模型、非阻塞IO模型、IO复用模型(select/poll/epoll)、信号驱动IO模型都属于同步IO,因为阶段2是阻塞的(尽管时间很短)。只有异步IO模型是符合POSIX异步IO操作含义的,不管在阶段1还是阶段2都可以干别的事。