I/O模型入门

来源:互联网 发布:项目数据分析师含金量 编辑:程序博客网 时间:2024/06/08 12:32

最近在学习I/O模型,在网上看到了一篇写的很好的博客http://litaotao.blog.51cto.com/6224470/1289790,对I/O模型有了一定的了解,后面又看了《UNIX网络编程卷1:套接字联网API(第3版)》的第六章有关I/O模型的部分,遂对书上的相关内容做摘抄,作为读书笔记。
链接的博客描述的基于磁盘I/O的I/O模型,而书描述的是基于网络传输的I/O模型。


Unix下可用的5种I/O模型的基本区别:

  • 阻塞式I/O;
  • 非阻塞式I/O;
  • I/O复用(select和poll);
  • 信号驱动式I/O;
  • 异步I/O(POSIX的aio_系列函数)。

一个输入操作通常包括两个不同的阶段:
1,等待数据准备好;
2,从内核向进程复制数据。

对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。

套接字
源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
它是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
非常非常简单的举例说明下:Socket=Ip address+ TCP/UDP + port。

1,阻塞式I/O(blocking I/O)

最流行的I/O模型是阻塞式I/O模型。

以数据报套接字作为例子
这里写图片描述
在本文中,我们把recvfrom函数视为系统调用,因为我们正在区分应用进程和内核,不论它如何实现。一般都会从在应用进程空间中运行切换到在内核空间中运行,一段时间之后再切换回来。

在图6-1中,进程调用recvfrom,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发送错误才返回。最常见的错误时系统调用被信号中断。我们说进程在调用recvfrom开始到它返回的整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。

2,非阻塞式I/O模型(noblocking I/O)

进程把一个套接字设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。
这里写图片描述
前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK错误。第四次调用recvfrom时已有一个数据报准备好,它被复制到应用进程缓冲区,于是recvfrom成功返回。我们接着处理数据。

当一个应用进程像这样对一个非阻塞式描述符循环调用recvfrom时,我们称之为轮询(polling)。应用进程持续轮询内核,以查看某个操作是否就绪。这么做往往耗费大量CPU时间,不过这种模型偶尔也会遇到,通常是在专门提供某一种功能的系统中才有。

3,I/O复用模型(I/O multiplexing)

select
该函数允许进程指示内核等待多个事件中的任何一个发生,并且只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。

poll
poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。

有了I/O复用,我们可以调用select或poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。
这里写图片描述
我们阻塞于select调用,等待数据报套接字变为可读。当select返回套接字可读这一条件时,我们调用recvfrom把所读数据报复制到应用进程缓冲区。

比较图6-3和图6-1,I/O复用并不显得有什么优势,事实上由于使用select需要两个而不是单个系统调用,I/O复用并不显得有什么优势。不过,使用select的优势在于我们可以等待多个描述符就绪。

与I/O复用密切相关的另一种I/O模型是在多线程中使用阻塞式I/O。这种模型与上述模型极为相似,但它没有使用select阻塞在多个文件描述符上,而是使用多个线程(每个文件描述符一个线程),这样每个线程都可以自由地调用诸如recvfrom之类的阻塞式I/O系统调用了。

4,信号驱动式I/O模型(signal-driven I/O)

我们也可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。我们称这种模型为信号驱动式I/O。
这里写图片描述
我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recefrom读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它读取数据报。

无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。

5,异步I/O模型(asynchronous I/O)

异步I/O由POSIX规范定义,告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与上面的信号驱动模型的区别在于:信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O操作是由内核通知我们I/O操作何时完成。
这里写图片描述
我们调用aio_read函数(POSIX异步I/O函数以aio_或lio_开头),给内核传递描述符。缓冲区指针。缓冲区大小(与read相同的三个参数)和文件偏移(与lseek类似),并告诉内核当整个操作完成时如何通知我们。该系统调用立即返回,而且在等待I/O完成期间,我们的进程不被阻塞。本例子中我们假设要求内核在操作完成时产生某个信号。该信号直到数据已复制到应用进程缓冲区才产生,这一点不同于信号驱动式I/O模型。

各种I/O模型的比较

图6-6对比了上述5种不同的I/O模型。可以看出,前4种模型的主要区别在第一阶段,因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞于recvfrom调用。相反,异步I/O模型在这两个阶段都要处理,从而不同于其他4种模型。

同步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相匹配。