I/O模型简介
在Unix中,主要存在着五种I/O模型:
- blocking I/O
- nonblocking I/O
- I/O multiplexing ( select and poll )
- signal driven I/O ( SIGIO )
- asynchronous I/O ( the POSIX aio_functions )
对于一个network的I/O操作,主要会涉及到两个步骤:
- 等到数据准备好
- 从kernel中将数据拷贝给进程
对于一个基于socket的输入操作,第一步通常包括等待数据到达network,然后到达后将数据拷贝到kernel中的一个buffer里。第二步再从kernel的buffer把数据拷贝到应用的buffer中。
blocking I/O
通常大部分使用的I/O模型都是blocking I/O。默认情况下所有的socket都是blocking,下图展示了blocking I/O的读操作流程:
首先,当用户进程调用了recvfrom系统调用,kernel便开始了准备数据。如果数据在一开始还没有完全到达(比如,network I/O还没有收到一个完整的TCP包),kernel就要等待足够的数据到来,这时用户进程就会被阻塞。当数据准备好后,才会从kernel的buffer把数据拷贝到应用的buffer中,然后kernel返回结果,用户进程解除block的状态。
blocking I/O的特点就是在I/O执行的两个阶段都会被block。
nonblocking I/O
当将一个socket设置为nonblocking后,如果遇到一个请求的I/O操作不能马上完成时,不让这个进程暂时休眠,而是返回一个error。具体流程如下:
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。对于用户进程来说 ,它发起一个read操作后,无论是否可以执行,都会马上得到一个结果。用户进程收到error时,它会得知数据还没有准备好,然后它会重复发送read操作,直到得到肯定的答案。
I/O multiplexing
I/O multiplexing主要就是包括的select或poll两个系统调用,然后block其中之一,而不会block实际的I/O系统调用。具体实现流程如下:
当用户进程调用了select,整个进程就会被block,与此同时,kernel会监视所有select负责的socket,如果其中任何一个socket的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
Signal-Driven I/O
在Signal-Driven I/O模型中,当准备好I/O操作时,kernel会使用SIGIO信号通知用户。
使用Signal-Driven I/O模型的优势在于在等待数据到达的过程中,用户不会被block。主循环可以一直在执行,直到收到数据已经准备好的信号。
asynchronous I/O
asynchronous I/O是被POSIX定义的,在不同的POSIX标准中会有不同的实时函数。总的来说,这些函数会告知kernel开始I/O操作,然后当整个操作完成后通知用户。它与signal-driven I/O主要的区别就在于signal-driven I/O会在可以初始化I/O操作时通知用户,而asynchronous I/O会在完成I/O操作时通知用户。具体实现流程如下:
用户进程发起read操作之后,可以立刻开始去做其它的事。而kernel接收到一个asynchronous read之后,首先它会立刻返回,不会对用户进程产生任何block,然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存。当这全部都完成之后,kernel会给用户进程发送一个信号,告诉它read操作完成了。
I/O模型比较
下图显示了物种I/O模型的区别。从图中可以看出前四种模型主要区别都在与第一步,它们的第二步操作都是相同的:在接收到recvfrom系统调用后用户进程被block,直到数据从kernel拷贝到用户buffer中。而asynchronous I/O对这两步都进行了处理,且与前四种模型都不相同。