1 概述
网络IO的本质就是socket流的读取,通常一次IO读操作会涉及到两个对象和两个阶段。
两个对象分别是:
- 用户进程(线程)Process(Thread)
- 内核对象 Kernel
两个阶段:
- 等待流数据准备(wating for the data to be ready);
- 从内核向进程复制数据(copying the data from the kernel to the process);
对于socket流而已:
- 第一步通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
- 第二步把数据从内核缓冲区复制到应用进程缓冲区。
过Richard Stevens的经典书籍UNP,书中给出了5种IO模型:
- blocking IO - 阻塞IO
- nonblocking IO - 非阻塞IO
- IO multiplexing - IO多路复用
- signal driven IO - 信号驱动IO
- asynchronous IO - 异步IO
这五种IO模型,前面四种是同步IO,第五种是异步IO,下面我们来分析这五种IO模型。
2 详细分析
2.1 阻塞IO
最简单的I/O模型是阻塞I/O模型,在这种模型下,两个阶段的操作都是阻塞的。在数据没准备好的时候,process原地等待kernel准备数据。kernel准备好数据后,process继续等待kernel将数据copy到自己的buffer。在kernel完成数据的copy后process才会从recvfrom系统调用中返回。
2.2 非阻塞IO
进程把一个套接口设置成非阻塞是在通知内核:当所请求的I/O操作不能满足要求时候,不把本进程投入睡眠,而是返回一个错误。也就是说当数据没有到达时并不等待,而是以一个错误返回非阻塞IOrecvfrom操作的第一个阶段是不会block等待的,如果kernel数据还没准备好,那么recvfrom会立刻返回一个EWOULDBLOCK错误。当kernel准备好数据后,进入处理的第二阶段的时候,process会等待kernel将数据copy到自己的buffer,在kernel完成数据的copy后process才会从recvfrom系统调用中返回。
2.3 多路复用IO
IO多路复用,就是我们熟知的select、poll、epoll模型。在IO多路复用的时候,process在两个处理阶段都是block住等待的。初看好像IO多路复用没什么用,其实select、poll、epoll的优势在于可以以较少的代价来同时监听处理多个IO。
linux提供select/poll,进程通过将一个或多个fd传递给select或poll系统调用,阻塞在select;这样select/poll可以帮我们侦测许多fd是否就绪。但是select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限。linux还提供了一个epoll系统调用,epoll是基于事件驱动方式,而不是顺序扫描,当有fd就绪时,立即回调函数rollback;
2.4 信号驱动异步模型
我们也可以用信号,让内核在描述字就绪时发送SIGIO信号通知我们。我们称这种模型为信号驱动I/O(signal-driven I/O)。我们首先开启套接口的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用立即返回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间,进程不被阻塞。主循环可以继续执行,只要不时等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。
2.5 异步I/O模型
异步IO要求process在recvfrom操作的两个处理阶段上都不能等待,也就是process调用recvfrom后立刻返回,kernel自行去准备好数据并将数据从kernel的buffer中copy到process的buffer在通知process读操作完成了,然后process在去处理。遗憾的是,linux的网络IO中是不存在异步IO的,linux的网络IO处理的第二阶段总是阻塞等待数据copy完成的。真正意义上的网络异步IO是Windows下的IOCP(IO完成端口)模型。很多时候,我们比较容易混淆non-blocking IO和asynchronous IO,认为是一样的。但是通过上图,几种IO模型的比较,会发现non-blocking IO和asynchronous IO的区别还是很明显的,non-blocking IO仅仅要求处理的第一阶段不block即可,而asynchronous IO要求两个阶段都不能block住。
这种模型与信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们何时启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
3 总结
POSIX把这两个术语定义如下:
- 同步I/O操作(synchronous I/O operation)导致请求进程阻塞,直到I/O操作完成。
- 异步I/O操作(asynchronous I/O operation)不导致请求进程阻塞。
根据上述定义,我们的前4种模型——阻塞I/O模型、非阻塞I/O模型、I/O复用模型和信号驱动I/O模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom)将阻塞进程。只有异步I/O模型与POSIX定义的异步I/O相匹配。
下图展示了五种IO模型的区别: