首页 > 代码库 > 《网络编程》I/O 模型

《网络编程》I/O 模型

前言

在分析 I/O 模型之前,首先了解 同步 I/O 和 异步 I/O 的基本概念:

  1. 同步 I/O:进程调用 I/O 操作函数时,在 I/O 操作函数返回之前,该进程会被挂起(即阻塞),直到 I/O 操作完成后返回;
  2. 异步 I/O:进程调用 I/O 操作函数时,在 I/O 操作函数返回之前,该进程不会被挂起(即不阻塞),当 I/O 操作完成后会通知方式告知进程;

在 Unix 系统中主要有 5 种 I/O 模型:阻塞式 I/O;非阻塞式 I/O;I/O 多路复用;信号驱动式 I/O;异步 I/O;

一般一个输入操作需要两个阶段:(1)等待数据准备就绪;(2)从内核向进程复制数据

阻塞式 I/O

        当进程调用 I/O 操作时,若数据未准备就绪,则进程会一直处于阻塞状态,直到数据准备就绪,并从内核把该数据复制到应用进程的缓冲区后才返回。即 I/O 操作的两个阶段都属于阻塞状态。

        以 UDP 数据报的读取过程为例,若进程调用 recvfrom 函数从套接字读取数据,若此时套接字缓冲区没有数据,则进程会阻塞于  recvfrom 函数调用,直到套接字缓冲区有数据,并把该数据读取到进程缓冲区后返回。在 recvfrom 函数返回之前,进程一直处于阻塞状态。



非阻塞式 I/O

        当进程调用 I/O 操作时,若数据未准备就绪,则立即返回一个 EWOULDBLOCK 错误,直到数据准备就绪,在数据准备就绪之前,应用进程采用轮询的方式检查数据是否准备就绪。当数据准备就绪之后,在内核把该数据复制到应用进程的缓冲区之前,进程处于阻塞状态,直到数据复制完成后才返回。即 I/O 操作第一阶段处于轮询检查状态,第二阶段处于阻塞状态。

        以 UDP 数据报的读取过程为例,若进程调用 recvfrom 函数从套接字读取数据,若此时套接字缓冲区没有数据,则会返回一个 EWOULDBLOCK 错误给进程,在数据准备就绪之前,应用进程采用轮询的方式检查数据是否准备就绪。直到套接字缓冲区有数据,并把该数据读取到进程缓冲区后返回。


I/O 多路复用

        在 I/O 多路复用中,一般系统调用 select 或 poll 函数,在数据准备就绪之前阻塞于 select 或 poll 的系统调用上,而不阻塞于 I/O 操作上。当 select 或 poll 发现数据准备就绪后,通过实际的 I/O 操作将数据从内核复制到应用进程的缓冲区上。即第一阶段阻塞于 select 或 poll 的系统调用上,第二阶段阻塞于实际的 I/O 操作。

        以 UDP 数据报的读取过程为例,若进程调用 select 函数等待数据报套接字变为可读,当 select 函数返回套接字可读时,调用 recvfrom 把所读数据报复制到应用进程缓冲区。


信号驱动式 I/O

        当进程调用 I/O 操作时,若数据未准备就绪,则等待数据准备就绪,在等待期间进行不会阻塞。若在某一时刻数据准备就绪,内核会通知进程启动 I/O 操作,将数据从内核复制到应用进程缓冲区。即第一阶段处于等待阶段,此时,进程并不会阻塞,第二阶段阻塞于实际的 I/O 操作。

        首先开启套接字的信号驱动式 I/O 功能,并通过函数 sigaction 系统调用一个信号处理函数,该系统调用立即返回,进程继续工作,即进程不会被阻塞。当数据报准备好读取时,内核就为进程产生一个 SIGIO 信号,捕获该信号并对该信号进行处理,在处理函数中调用 recvfrom 函数读取数据报,并通知主循环数据已经准备好待处理,也可以通知主循环,让它读取数据报。等待数据报到达期间进程不会被阻塞。


异步 I/O

        应用进程启动一个异步 I/O 操作,并让内核在整个操作(将数据从内核复制到应该进程的缓冲区)完成后通知应用进程。在整个过程中,进程不会被阻塞。

        调用 aio_read 函数,给内核传递描述符、缓冲区指针、缓冲区大小和文件偏移,并告知内核整个操作完成时如何通知进程。该系统调用立即返回,而且在等待 I/O 完成期间,进程不会被阻塞。



5 种 I/O 模型比较

前面四种 阻塞式 I/O、非阻塞式 I/O、I/O 多路复用 以及 信号驱动式 I/O 属于同步 I/O。下图显示了这 5 种 I/O 模型之间的区别。





参考资料:

《Unix 网络编程》

《网络编程》I/O 模型