本文章参考《UNIX网络编程--第一卷:套接口API(第3版)》---第6章第2节--I/O模型
另外一篇讲得更好的
文章,里面详细讲述了阻塞 非阻塞 同步 异步这几个
阻塞I/O
非阻塞I/O
I/O复用(select和poll)
信号驱动I/O(SIGIO)
异步I/O(POSIX的aio_系列函数)
以下所有例子都使用recvfrom()函数来做讲解。
阻塞I/O模型
在缺省情况下,所有套接口都是阻塞的,进程调用recvfrom,其系统调用直到数据报到达且被拷贝到应用进程的缓冲区或者发生错误才返回。最常见的错误是系统调用被信号中断。我们说进程从调用recvfrom开始到它返回的整段时间内是被阻塞的,recvfrom成功返回后,进程开始处理数据报。
应用进程 内核
| recvfrom =========系统调用========> 无数据报准备好 ||
| || 等待
| || 数据
进程阻塞于 《 数据报准备好 ||
recvfrom的调用 | 拷贝数据报 ||
| || 将数据从内核
| || 拷贝到用户空间
| 处理数据报 <========返回成功指示=======拷贝完成 ||
非阻塞I/O模型
进程把一个套接口设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。
当一个应用程序对一个非阻塞的描述字循环调用recvfrom是,我们称之为轮询(polling)。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间,不过这种模型很少会用到。
应用进程 内核
| recvfrom =========系统调用========> 无数据报准备好 |
| <====EWOULDBLOCK====== |
| recvfrom =========系统调用========> 无数据报准备好 |
等
| <====EWOULDBLOCK====== 》
| recvfrom =========系统调用========> 无数据报准备好 |
待
进程反复调用
| <====EWOULDBLOCK====== |
数
recvfrom 等待 《
recvfrom =========系统调用========> 数据报准备好 |
据
返回成功 | 拷贝数据报 T
指示(轮询) | || |
| ||
将数据从内核
| ||
拷贝到用户空间
| || |
| 处理数据报 <========返回成功指示=========拷贝完成 |
I/O复用模型
有了I/O复用(I/O multiplexing),我们就可以调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。
我们阻塞于select调用,等待数据报套接口变为可读。当select返回套接口可读这一条件时,我们调用recvfrom把所读数据报拷贝到应用进程缓冲区。
应用进程 内核
|| select =========系统调用========> 无数据报准备好 ||
进程受阻于select
| || |
调用,等待可能
| ||
多个套接口中的 《 》 等待数据
任一个变为可读
| ||
| || |
|| <=======返回可读条件===== || ||
|| recvfrom =========系统调用=======> 数据报准备好 ||
| 拷贝数据报 |
数据拷贝到应用
| || |
缓冲区期间进程
《
》
将数据从内核
阻塞
| ||
拷贝到用户空间
| || |
|| 处理数据报 <========返回成功指示========拷贝完成 ||
信号驱动I/O模型
我们也可以用信号,让内核在描述字就绪时发送SIGIO信号通知我们。我们称这种模型为信号驱动I/O(signal-driven I/O)。
我们首先开启套接口的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用立即发回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已经准备好待处理,也可以立即通知主循环,让它读取数据报。
无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间,进程不被阻塞。主循环可以继续执行,只要不时等待来自信号处理函数的通知:既可以是数据已经准备好被处理,也可以是数据报已准备好被读取。
应用进程 内核
建立SIGIO的 =========系统调用========> |
|| 信号处理程序 <==========返回========== |
| |
|
进程继续执行 《 等待数据
| |
| |
||信号信号处理程序 <======递交SIGIO====
数据报准备好
||
|| recvfrom ========系统调用======>
拷贝数据报
||
| || |
数据拷贝到应用
| || |
缓冲区期间进程
《
》
将数据从内核
阻塞
| ||
拷贝到用户空间
| || |
|| 处理数据报 <========返回成功指示========拷贝完成 ||
异步I/O模型
异步I/O(asynchronous I/O)有POSIX规范定义。后来演变成当前POSIX规范的各种早期标准定义的实时函数中存在的差异已经取得一致。一般地说,这些函数的工作机制是:告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到我们自己的缓冲区)完成后通知我们。这种模型与前与前面介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。
应用进程 内核
| aio_read =========系统调用========> 无数据报准备好 ||
| <=========返回========== ||
| 》 等待
数据
| ||
进程继续执行 《 数据报准备好 ||
| 拷贝数据报 ||
| || 将数据从内核
| || 拷贝到用户空间
| 处理数据报 <===递交在aio_read中指定的信号===拷贝完成 ||
我们调用aio_read函数(POSIX异步I/O函数以aio_或lio_开头),给内核传递描述字、缓冲区指针、缓冲区大小(与read相同的三个参数)、文件偏移(与lseek类似),并告诉内核当整个操作完成时如何通知我们。该系统调用立即返回,在等待I/O完成期间,我们的进程不被阻塞。
各种I/O模型比较
==========================================================
|| 阻塞I/O || 非阻塞I/O || I/O复用 || 信号驱动I/O || 异步I/O ||
========================================================== +
|| 发起 || 检查 || 检查 || || 发起 || +
|| | || 检查 || | || || || +
|| | || 检查 || |
阻 || || || +
|| | || 检查 || |
塞 || || || 等待
|| | || 检查 || |
|| || || 数据
|| | || 检查 || |
|| || || +
|| | || 检查 || +
|| || || +
|| | 阻 || 检查 || 就绪发起
|| 通知发起 || || =
|| | || 检查 || |
|| | || || +
|| | 塞 || 检查 || |
|| | || || +
|| | || 检查 || |
阻 || | 阻 || ||
将数据
|| | || | || |
塞 || |
塞 || ||
从内核
|| | || | || |
|| | || ||
拷贝到
|| | || | || |
|| | || ||
用户空间
|| + || + || +
|| + || || +
|| 完成 || 完成 || 完成 || 完成 || 通知 || +
==========================================================
-------------------------------------------------------------------------------------------- -------------------
第一阶段处理不同 处理两个阶段
第二阶段处理不同
(阻塞于recvfrom调用)
同步I/O与异步I/O
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相匹配。