本篇博文主要介绍UNIX中五种I/O模型。主要参考了UNP和这篇博文。
UNIX中的I/O模型分为一下五种:
- 阻塞I/O (blocking I/O)
- 非阻塞I/O (nonblocking I/O)
- I/O复用 (I/O multiplexing)
- 信号I/O (signal driven I/O)
- 异步I/O (the POSIX aio_ functions)
对于任意一个I/O读取(input)操作,主要分为两个阶段:
1. 等待数据准备就绪(Waiting for the data to be ready)
2. 将准备好的数据从内核拷贝到用户缓冲(Copying the data from the kernel to the process)
在网络编程中, 第一个阶段经常会使得读取进程进入等待状态,当数据就绪时,就会被拷贝到内核缓冲中。这两个阶段是理解后面关于非阻塞和异步的关键。
blocking IO
目前用的比较多的是阻塞I/O模型。默认情况下,所有的socket都是阻塞的。我们用下图来解释阻塞I/O模型:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
nonblocking IO
linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问kernel数据好了没有。 并且它的阻塞是发生在将数据从内核缓冲拷贝到用户进程的过程中(第二阶段),在数据准备时(第一阶段)是不会阻塞的。
I/O multiplexing
I/O复用可以实现用一个进程监听多个套接口。主要的系统调用包括select和poll,但是由于他们的伸缩性不太好,现在用的比较多的是epoll。在监听多个套接口时,当任何一个套接口有数据可读,则会返回。从下面的示意图我们可以看到
select/poll/epoll在等待套接口变得可读时会阻塞。 它主要阻塞在第一各阶段。当他们返回时,说明数据已经准备就绪,可以调用recvfrom函数从内核中将数据拷贝到应用进程中了( 第二阶段)。可以看出,这里需要两个系统调用性能是比不上前面两种I/O模型的。它的主要优势在与可以监听多个套接口,如果监听的套接口比较少,前面两个模型的效率还会更高一些。
Asynchronous I/O
我们通过调用aio_read来进行异步读操作。同时向内核传递套接口描述符,应用缓冲指针,缓冲大小和读取成功时,如何通知进程等参数。下面是示意图:
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以 不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
signal I/O
在signal I/O中。我们告知内核当数据准备就绪时通过信号通知进程。这个IO模型和非阻塞型IO有点类似, 他们都是不需要阻塞在等待数据准备这个阶段上(第一阶段),但是和非阻塞IO相比,信号IO不需要反复去检查数据是否准备就绪,因为数据准备就绪时内核会通过信号通知它 。不过在将数据从内核拷贝到进程缓冲区时,他们都是有阻塞的。
几种IO模型的比较
从这个模型的比较中,我们可以看出:blocking IO,nonblock IO, IO multiplexing,signal IO 这四种IO都是有阻塞的,并且他们的第二个阶段都是阻塞的。只是他们阻塞的阶段不同。但是异步IO是两个阶段都并不阻塞的。
同步I/O VS 异步 I/O
- Asynchronous I/O operation causes the requesting process to be blocked until that I/O operation complete
- Anasynchronous I/O operation does not cause the requesting process to be blocked
从UNP给出的定义中我们可以看到,同步的意思是:I/O操作会引起阻塞。这里的I/O操作是指两阶段中的第二个阶段(actual I/O operation),毕竟第一个阶段只是等待数据准备而已。
所以我们可以得出结论,blocking IO,nonblock IO, IO multiplexing,signal IO 这四种IO都是属于同步的。
最后,再举几个不是很恰当的例子来说明这四个IO Model(blocking ,nonnlocking, IO multilexing,异步IO):
有A,B,C,D四个人在钓鱼:
A用的是最老式的鱼竿,所以呢,得一直守着,等到鱼上钩了再拉杆;
B的鱼竿有个功能,能够显示是否有鱼上钩,所以呢,B就和旁边的MM聊天,隔会再看看有没有鱼上钩,有的话就迅速拉杆;
C用的鱼竿和B差不多,但他想了一个好办法,就是同时放好几根鱼竿,然后守在旁边,一旦有显示说鱼上钩了,它就将对应的鱼竿拉起来;
D是个有钱人,干脆雇了一个人帮他钓鱼,一旦那个人把鱼钓上来了,就给D发个短信。
这里第一阶段就是等鱼上钩,第二阶段就是把鱼钩拉起来。,将鱼钩拉上来才是 actual I/O operation,而在这个动作上,前面三个都是要阻塞的,所以前三种都是同步的模型。