什么是I/O复用?
操作系统为你提供一个功能,当内核发现进程指定的一个或多个I/O条件就绪时(即输入已经准备好被读取,获取描述符能够接受更多的输出),他就会给进程一个通知。这种功能就称之为“I/O复用”。I/O复用主要是由select和poll两个函数提供。
- 当客户处理多个描述符(通常是交互式输入和网络套接字)时,必须使用I/O复用。
- 一个客户同时处理多个I/O套接字。
- 一个TCP服务器既要处理监听套接字,又要处理已连接套接字。
- 一个服务器既要处理TCP,又要处理UDP。
- 一个服务器要处理多个服务或多个协议。
在Unix中有五种I/O模型:
- 阻塞式I/O;
- 非阻塞式I/O;
- I/O复用(select和poll);
- 信号驱动式I/O(SIGIO);
- 异步I/O(POSIX的aio_系列函数);
- 等待数据准备好;
- 从内核向进程复制数据;
阻塞式I/O
最流行的I/O模型是阻塞式I/O(blocking I/O)模型。默认情况下,所有的socket都是阻塞的。
其示意图如下:
当应用进程发起了recvfrom系统调用,如果此时内核中的数据还没有准备好,对于网络I/O来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),那么内核需要等待数据准备好。而在应用进程这边,整个进程都会被阻塞。当数据准备好且从内核复制到应用进程缓冲区中或者发生错误,用户进程才解除阻塞状态并返回。最常见的错误是系统调用被信号中断。
所以,对于阻塞I/O而言,其在I/O执行的两个阶段都被block了。
非阻塞式I/O
在Linux下,可以设置socket使其变为non-blocking。
模型示意图如下:
从图中可以看出,当应用进程发起了一个recvfrom系统调用时,如果内核中的数据还没有准备好,那么它并不会阻塞应用进程,而是立刻返回一个EWOULDBLOCK的错误。从应用进程的角度来看,当它发起了一个I/O系统调用后,并不需要等待,而是立刻得到一个结果。当返回结果是EWOULDBLOCK时,应用进程就知道数据还没有准备好,于是它可以再次发起一个recvfrom的系统调用。一旦内核中的数据准备好了,并且又再次收到了用户进程发起的I/O系统调用,那么它就马上将数据拷贝到应用进程缓冲区中,然后返回一个成功指示。
所以,应用进程其实是需要不断地主动询问内核数据是否准备好。
I/O复用
I/O复用(I/O multiplexing)主要通过select/poll函数实现。I/O复用的好处在于单个应用进程就可以同时处理多个网络连接的I/O。其基本原理就是select/poll函数会不断轮询其所负责的所有socket,当某个socket的数据准备好了,就通知应用进程。
其I/O模型如下:
当应用进程发起了select的系统调用,那么整个应用进程就会被阻塞。同时内核会不断轮询select所负责的所有socket,当其中任何一个socket中的数据准备好了,select就会返回。此时应用进程再发起一个recvfrom系统调用,内核就会将数据复制到应用进程缓冲区中。
在I/O复用模型中,其实际上和阻塞I/O并没有太大的不同,同时由于它需要发起两次系统调用,性能可能更差。I/O复用的优势在于它可以同时处理多个connection。所以,如果处理的连接数不是很高的话,使用I/O复用的性能不一定比使用多线程+阻塞I/O性能更好。
信号驱动式I/O模型
信号驱动式模型是让内核在描述符就绪时发送SIGIO信号通知我们。
其I/O模型如下:
在信号驱动I/O模型中,应用进程发起一个sigaction系统调用,该系统调用将立即返回,此时应用进程不会被阻塞,而是继续工作。当数据准备好被读取时,内核就为该进程产生一个SIGIO信号。随后即可调用recvfrom读取数据,并通知主循环数据已经准备好等待处理,也可以立即通知主循环,让它读取数据。
这种模型的优势在于,等待数据到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已经准备好被处理,也可以是数据已准备好被读取。
异步I/O模型
异步I/O(asynchronous I/O)有POSIX规范定义。其工作机制是,告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与信号驱动模型的主要区别在于,信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O是由内核通知我们I/O操作何时完成。
其模型示意图如下:
当应用进程发起aio_read的系统调用后,会立即返回。从内核角度来看,当它接收到一个异步I/O的系统调用,首先它会立即给应用进程一个返回,所以并不会对应用进程产生任务阻塞。然后内核会等待数据准备好,并将数据拷贝到应用进程,当拷贝完成之后,内核会给应用进程发送一个指定的信号。
各种I/O模型的比较
各种I/O模型的比较如下图所示:
可以看出,前四种模型的主要区别在于第一阶段,它们的第二阶段都是一样的:在数据从内核复制到调用者缓冲区期间进程阻塞于recvfrom系统调用。相反异步I/O模型在这两个阶段都要处理,从而不同于其他四种模型。
阻塞I/O与非阻塞I/O的比较
由之前的说明可知,当发起一个I/O系统调用,阻塞I/O会一直阻塞应用进程直到整个I/O操作完成,而非阻塞I/O会在内核中的数据没有准备好的情况下立即返回。
同步I/O和异步I/O对比
POSIX对两者的定义如下:
- 同步I/O操作导致请求进程阻塞,直到I/O操作完成;
- 异步I/O操作不导致请求进程阻塞;