Linux之——五种IO模型

一. 同步与异步

    之前在对线程的谈论中提到了线程对临界资源访问的一个同步与互斥的关系,这里要强调,在IO模型中的同步与异步与线程的同步与互斥完全不是一回事。

    

    所谓同步,就是指当调用者发出调用的时候,在没有得到结果之前调用并不返回,而是调用者自身一直在那里等待结果,至于等待的方式不同可以分为不同IO模型,下面会进行具体讨论;因此,这里的同步就可以理解为:调用者发出的调用(如一个系统调用函数)和所需要的结果是保持一致性也就是同步性的,你不给我结果,我就一直在那里等着就不返回,我不能单飞我一定要带着结果一起走,对于结果的等待和提取都由调用者来完成,这就是同步

    至于异步,就是调用者发出调用后就直接返回了,当然就不会有返回结果,至于等待结果和获取结果的事都由处理调用的部件来完成,完成之后再通过状态、通知或者回调来告诉调用者,这时调用者才会再回来拿到结果去处理;因此,这里的异步同样可以理解为:调用者发出的调用可以不带着结果一起自己先返回,而这时候调用者就是你没有结果我自己先走,等结果都准备妥当当的了你通知我就行我再回来拿走处理,对于结果的等待和搬迁复用都不经过调用者之手而是由别人来完成,这就是异步



二. 阻塞与非阻塞

    阻塞嘛,顾名思义就是被阻挡在那除了等就不能干别的了,当调用者发起调用时在结果返回之前调用者就被阻塞住不能再继续往下执行了,也就是当前线程或进程会被挂起等待;这里和同步是没有关系的,同步是无关进程或线程是否被阻塞的,只是强调结果的等待和获取都是自身来完成,而阻塞就是阻塞,是指当前进程或线程的运行状态;

    非阻塞就是和阻塞相反,指当前进程或线程一直在运行中,在没有结果返回的时候并不会挂起该进程或者线程使其只等待什么都不做;同样的,这里和同步或者异步也并没有什么关系;



三. 五种IO模型

    对于系统的IO也就是数据的接收和发送来说,是要必经两个过程的:一个是待数据的到来或者准备;二是当数据准备好或者到来之后要将其从内核提取到用户内存中以便处理,也就是数据的搬迁;因此,任何IO模型都要经过这两个重要的过程来进行数据的传输和处理;


  1. 阻塞IO模型

    在阻塞IO模型中,从调用系统函数获取数据开始到得到数据,当前的进程或者线程始终是处于阻塞状态的,也就是什么都不干,直到等完数据准备好和将数据搬迁到用户空间为止:

wKioL1dCazqT0gUAAAAPHT3YnIs274.png

  1. 用户首先发出系统调用函数希望获取数据;

  2. 系统调用会进入内核检查是否有数据准备完毕,如果没有就一直等待;

  3. 当数据准备完毕的时候会将数据拷贝到用户空间;

  4. 拷贝完毕返回一个获取数据成功的返回值来告诉用户可以进行数据的处理了;在此期间,用户进程或者线程一直是处于阻塞状态的,无论是等待数据还是进行数据的拷贝;


2. 非阻塞IO模型

    和阻塞式的IO模型不同,当发出了系统调用的时候,如果这时候数据还没有准备好,进程或线程并不会进入阻塞模式一直等待,而是会反复轮询“数据好没...数据好没...数据好没...”,这是比较耗CPU资源的,而当数据准备好之后会和阻塞IO模型一样进行数据的拷贝:

wKiom1dCbHvQ-30lAAAQOlapjIw722.png

  1. 首先用户发出系统调用,去向内核申请获取想要的数据;

  2. 内核检查发现数据还没准备好,就会返回一个错误值;

  3. 用户接收到错误值并不甘心,就会反复反复询问内核是否有数据准备好;

  4. 内核一直检查直到有数据准备完毕,进行数据的搬迁,这时用户会进入阻塞等待状态等待数据拷贝完毕;

  5. 数据提取到用户空间之后会返回一个成功状态通知用户数据完毕,这时用户就可以进行数据的处理了;


3. IO复用模型

    既然是复用,说明一次性可以管理或处理多个IO,主要是靠select函数或者poll(epoll)函数来完成;这些函数同样会阻塞进程,但是和阻塞式IO不同的是IO复用模型可以一次性阻塞多个IO操作,期间只要有一个IO接口的数据准备完毕就会返回通知用户并且进行系统调用获取数据:

wKiom1dCcRWQQalGAAAT2gIfcbg954.png

  1. 用户首先调用select或者poll函数对多个IO接口操作进行检测,同时会使进程或者线程阻塞;

  2. 当有至少一个IO接口响应的时候,系统就会通知内核调用相应的函数来获取数据;

  3. 这时内核将数据拷贝迁移至内核空间,进程或者线程仍然处于阻塞状态;

  4. 数据就绪,返回一个成功值告诉用户可以处理数据了;


4. 信号驱动IO模型

    用户首先注册一个处理IO信号的信号处理函数,当数据还没准备好的时候进程或者线程并不阻塞,当数据准备好的时候用户进程或者线程会收到一个信号SIGIO,这时候就会调用信号处理函数,在信号处理函数中调用IO函数操作数据,完成之后通知用户:

wKiom1dCjUPhFfEdAAA9vVkWRwc352.png

  1. 用户程序中事先注册好一个对于SIGIO的信号处理函数;

  2. 当数据准备完毕的时候会向用户进程或者线程发送一个SIGIO信号,这时信号就会被捕捉;

  3. 捕捉信号之后就会执行用户自定义的一个信号处理函数,并且在函数中调用系统函数去获取数据;

  4. 获取数据同样会将数据进行拷贝到用户空间,这时进程或者线程仍然会被阻塞;

  5. 当数据准备完毕同样会通知用户,之后就可以进行数据的处理了;


5. 异步IO模型

    对于异步的IO模型来说,数据的等待和搬迁都不由当前的进程或者线程来处理,调用相应系统函数之后就会直接返回继续执行,因此当前用户程并不会被阻塞,当数据已经在用户空间准备就绪之后会以状态、通知或者回调来告诉用户可以进行数据的处理了:

wKioL1dCkeaCpGXuAAARbVOCoR4737.png

  1. 用户程序调用aio_read函数,告诉内核描述字、缓冲区指针、缓冲区大小、文件偏移以及通知的方式,之后便立即返回;

  2. 这时内核相应的数据操作组件会进行数据的等待和搬迁,期间用户程序并不受影响继续执行;

  3. 当数据都已经在用户空间准备就绪之后就会通过在函数中预留的通知方式来通知用户程序处理数据;



    从上面的分析中不难发现,在对于数据的获取过程中都是进行了两个主要的部分:数据的等待和数据的搬迁;除此相同点之外,下面就总结一下各种IO模型的区别:

wKioL1dCm2vhdsMpAAAteL_WMKA853.png

   从上面的比较可以发现:前四种IO模型也就是阻塞IO、非阻塞IO、IO复用和信号驱动IO模型都是同步的,只有最后一种是异步的异步IO模型;默认情况下所创建出来的socket都是以阻塞的形式,比如网络通信中的recvfrom和sendto,或者read和write等函数都是以阻塞的方式来实现的。




《完》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章