Unix I/O 模型
Unix I/O 模型
Unix 可用的 I/O 模型有以下5种:
1.阻塞式 I/O
2.非阻塞式 I/O
3.I/O 复用(select 和 poll)
4.信号驱动式 I/O
5.异步 I/O
前四种又称作 同步 I/O 操作,因为它们会导致进程阻塞,直到 I/O 操作完成。
接下来将会用 UDP 的 recvfrom 函数(因为这个函数比较的简单)来简单介绍以下这5种 I/O 模型。
阻塞式 I/O(block I/O)
阻塞式 I/O 可以说是最常用的 I/O 模型了。在默认情况下所有的套接字都是阻塞的。
当用户进程调用了 recvfrom 函数后(实质上是一个系统调用),控制权由用户进程转接给内核。内核准备好数据报
并将数据从内核复制到用户进程空间
后,再将控制权转接给用户进程。可以说在调用 recvfrom 开始到它返回的整段时间内,用户进程都是被阻塞的。
非阻塞式 I/O(nonblocking I/O)
如果将 recvfrom 设置为非阻塞的,在调用它时会遇到以下两种情况:
1.内核已经准备好数据报了。
2.内核还未准备好数据报。
在第一种情况下,控制权由用户进程转接给内核,内核将数据从内核复制到用户进程空间
后将控制权返回。
在第二种情况下,内核立即返回一个 EWOULDBLOCK 错误,表示内核还未准备好数据报。用户进程可以选择忽视该错误,并循环调用 recvfrom 直到内核准备好了数据报,这种行为称为轮询(polling)。这么做往往会耗费大量CPU时间。
无论如何,这种 I/O 模型在内核将数据复制到用户进程空间时,用户进程是阻塞的。
I/O 复用(I/O multiplexing)
我们可以调用 select 或 poll 使得用户进程阻塞在多个系统调用的某一个之上,而不是阻塞在单一一个系统调用上。这里我们再添加一个 sendto 函数来说明 I/O 复用。
代码中调用 sendto 和 recvfrom 的先后顺序往往会影响到程序的功能。如果在代码中先调用了 sendto 再调用 recvfrom,但是在特定情况下又发生 recvfrom 可读又在 sendto 可写之前,我们必须等待 sendto 返回后再处理 recvfrom。使用 select 或 poll 函数能使得 sendto 和 recvfrom 在处理优先级上是一样的(first come first serve),这对于其他的 I/O 函数也同样有效(这个解释例子可能不太严谨,但效果大致上是这样的)。显而易见,I/O 复用同样会阻塞用户进程
。
由于 select 本身也是一个系统调用,与阻塞式 I/O 相比,同样处理一个 recvfrom,I/O 复用需要两个系统调用而阻塞式 I/O 只需要一个。但是使用 I/O 复用的优势在于我们可以等待多个描述符就绪。
与 I/O 复用密切相关的另一种 I/O 模型是在多线程种使用阻塞式 I/O。
信号驱动式 I/O(signal-driven I/O)
我们可以使用信号,让内核在描述符就绪时发送 SIGIO 信号通知我们。首先开启套接字的信号驱动式 I/O 功能,并通过 sigaction 系统调用安装一个信号处理函数。该系统调用将立即返回,用户进程继续工作。
当内核准备好数据包时,内核就产生一个 SIGIO 信号通知用户进程,用户进程随即在信号处理函数种调用 recvfrom 读取数据报,并通知主循环已准备好待处理,也可以直接通知主循环,让它读取数据报。
信号驱动式 I/O 的优势在于等待数据报达到期间用户进程不被阻塞(非阻塞式 I/O 要花费大量 CPU 时间在轮询上)。
异步 I/O(asynchronous I/O)
异步 I/O 由 POSIX 规范定义。具体机制是:告知内核启动某个操作,并让内核在整个操作(包括数据从内核复制到用户进程的缓冲区)完成后通知用户进程。
支持 POSIX 异步 I/O 模型的系统比较罕见。