zoukankan      html  css  js  c++  java
  • 图解UNIX的I/O模型

    版权声明:本文为博主原创文章,转载请注明出处: leehao.me https://blog.csdn.net/lihao21/article/details/51620374

    一、简述

    UNIX系统将所有的外部设备都看作一个文件来看待,所有打开的文件都通过文件描述符来引用。文件描述符是一个非负整数,它指向内核中的一个结构体。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。而对于一个socket的读写也会有相应的文件描述符,称为socketfd(socket描述符)。
    在UNIX系统中,I/O输入操作(例如标准输入或者套接字的输入)通常包含以下两个不同的阶段:

    • 等待数据准备好
    • 从内核向进程复制数据

    例如对于套接字的输入,第一步是等待数据从网络中到达,当所等待的数据到达时,数据被复制到内核中的缓冲区。第二步则是把数据从内核缓冲区复制到应用进程的缓冲区。
    根据在这两个不同阶段处理的不同,可以将I/O模型划分为以下五种类型:

    • 阻塞式I/O模型
    • 非阻塞式I/O模型
    • I/O复用
    • 信号驱动式I/O
    • 异步I/O

    二、I/O模型

    为简单起见,我们以UDP套接字中的recvfrom函数作为系统调用来说明I/O模型。recvfrom函数类似于标准的read函数,它的作用是从指定的套接字中读取数据报。recvfrom会从应用进程空间运行切换到内核空间中运行,一段时间后会再切换回来。有关recvfrom函数的介绍,可以参考本文的参考资料3。

    2.1 阻塞式I/O模型

    阻塞式I/O模型可以说是最简单的I/O模型。

    阻塞式I/O模型
    图1:阻塞式I/O模型

    图1是阻塞式I/O模型的示意图,阅读此图须注意箭头的方向,沿着剪头方向顺时针阅读。
    图1中,应用进程调用recvfrom,然后切换到内核空间中运行,直到数据报到达且被复制到应用进程缓冲区中才返回。我们说进程从调用recvfrom开始到它返回的整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。

    2.2 非阻塞式I/O模型

    进程把一个套接字设置为非阻塞是指,在等待I/O数据时,进程并不阻塞,如果数据还没准备好,则直接返回一个错误。

    非阻塞式I/O模型
    图2:非阻塞式I/O模型

    图2是非阻塞I/O模型的示图。
    在前两次调用recvfrom时由于数据报没准备好,因此内核马上返回一个系统调用错误。第3次调用recvfrom时,数据报已准备好,数据报被复制到应用进程的缓冲区,接着recvfrom成功返回。
    当一个应用进程像这样不断对一个非阻塞描述符循环调用recvfrom时,我们称之为轮询。应用进程会持续轮询内核,以确定某个操作是否就绪。轮询操作会消耗大量的CPU时间。

    2.3 I/O复用模型

    我们常用的select和poll函数使用了I/O复用模型。我们以select为例说明I/O复用模型的特点。

    I/O复用模型
    图3:I/O复用模型

    图3是I/O复用模型的示意图。
    当我们调用select函数时,将会阻塞于此函数,等待数据报套接字变为可读。当等待的多个套接字中的其中一个或者多个变得可读时,我们调用recvfrom把数据报复制到应用进程缓冲区。
    比较图3与图1,I/O复用模型好像没什么优势,而且应用进程为了获取数据报,还得增加了一个额外的select系统调用。不过I/O复用模型的优势在于可以同时等待多个(而不只是一个)套接字描述符就绪。

    2.4 信号驱动式I/O模型

    信号驱动I/O模型用得比较少,图4是该模型的示意图。

    信号驱动式I/O模型
    图4:信号驱动式I/O模型

    为了使用该I/O模型,需要开启套接字的信号驱动I/O功能,并通过sigaction系统调用安装一个信号处理函数。sigaction函数立即返回,我们的进程继续工作,即进程没有被阻塞。当数据报准备好时,内核会为该进程产生一个SIGIO信号,这样我们可以在信号处理函数中调用recvfrom读取数据报,也可以在主循环中读取数据报。无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间不被阻塞。

    2.5 异步I/O模型

    异步I/O模型的工作机制是,启动某个操作,并让内核在整个操作(包括等待数据和将数据从内核复制到用户空间)完成后通知应用进程。

    异步I/O模型
    图5:异步I/O模型

    图5是异步I/O模型的示意图。我们调用aio_read函数,告诉内核,当整个I/O操作完成后通知我们。该系统调用立即返回,而在等待I/O完成期间,应用进程不会被阻塞。当I/O完成(包括数据从内样复制到用户进程)后,内核会产生一个信号通知应用进程,应用进程对数据报进行处理。
    异步I/O模型与信号驱动式I/O的区别在于:信号驱动式I/O在数据报准备好时就通知应用进程,应用进程还需要将数据报从内核复制到用户进程缓冲区;而异步I/O模型则是整个操作完成才通知应用进程,应用进程在整个操作期间都不会被阻塞。

    2.6 各种I/O模型的比较

    这里写图片描述
    图6:5种I/O模型的比较

    从图6可以看到,前四种I/O模型的主要区别在于第一个阶段,它们的第二个阶段是一样的:在数据从内核复制到应用进程的缓冲区期间,进程会被阻塞于recvfrom系统调用。
    而异步I/O模型则是整个操作完成内核才通知应用进程。

    三、同步I/O和异步I/O

    POSIX标准将同步I/O和异步I/O定义为:

    • 同步I/O操作:导致请求进程阻塞,直到I/O操作完成。

    • 异步I/O操作:不导致请求进程阻塞。

    根据上述两个定义,本文介绍的前面四种模型,包括阻塞式I/O,非阻塞式I/O,I/O复用和信号驱动式I/O模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom)将阻塞进程。只有异步I/O模型才符合POSIX标准的异步I/O定义。

    四、生活中的类比例子

    以生活中钓鱼为例子(例子参考了参考资料2),来说明各种I/O模型的不同,例子中的等待鱼上钩对应于上文中的等待数据,拉竿操则作对应于上文的将数据从内核复制到用户空间。

    有A,B,C,D,E五个人在钓鱼。
    A使用了最古老的鱼竿,所以开始钓鱼后,就一直守着,直接鱼上钩了再拉竿;
    B由于着急想知道有没鱼上钩,所以隔一会就看一次鱼竿看有没鱼上钩,直到看到鱼上钩后,再拉竿;
    C同时使用了N支鱼竿来钩鱼,然后等着,只要有其中一支鱼竿有鱼上钩,就将对应的鱼竿拉起来;
    D的鱼竿比较高级,当有鱼上钩后,会发出警报提示,所以D开始钓鱼后不用一直守着,一旦鱼竿发出警报,D再回来拉竿即可;
    E为了更省事,直接雇个佣人给他钓鱼,当佣人钓起鱼后,再通知E去取鱼即可。

    五、参考资料

    1. Unix网络编程,卷1:套接字联网API,第三版,W. Richard Stevens著
    2. http://blog.csdn.net/historyasamirror/article/details/5778378
    3. http://pubs.opengroup.org/onlinepubs/009695399/functions/recvfrom.html
  • 相关阅读:
    第一个只出现一次的字符字符(python)
    丑数(python)
    as3.0对图片进行不规则切割源代码实例
    AS3代码生成xml方法
    获取fla 总场景个数
    微信小程序开发工具下载
    actionscript(flash)和java后台的数据交互
    截取位图的某一部分 (像素)
    拷贝颜色通道
    将文本转换为位图
  • 原文地址:https://www.cnblogs.com/applelife/p/10516984.html
Copyright © 2011-2022 走看看