Linux下IO模型之一简介

参考资料:

“简述linux同步与异步,阻塞与非阻塞概念以及五种IO模型” https://www.cnblogs.com/chaser24/p/6112071.html

“聊聊Linux中的五种IO模型” http://www.360doc.com/content/18/1127/17/60934035_797610428.shtml

“Linux异步IO操作” https://blog.csdn.net/gotosola/article/details/7411688

“深入浅出:Linux设备驱动之异步通知和异步I / O” http://blog.jobbole.com/86493/

“linux下aio异步读写详解与实例” https://blog.csdn.net/Shreck66/article/details/48765533

“聊聊IO多路复用之选,民意调查,epoll详解” https://www.jianshu.com/p/dfd940e7fca2

 

说明:本文的内容大部分均摘自上述文章,并非原创。

-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ---------

目录

一,基本概念

 1.1用户空间与内核空间

1.2同步与异步 

1.3阻塞与非阻塞

1.4缓存IO

二,LINUX下IO模型

2.1同步阻塞IO(阻塞IO)

2.2同步非阻塞IO(非阻塞IO)

2.3 IO多路复用(IO多路复用) 

 2.4信号驱动式IO(信号驱动IO)

 2.5异步非阻塞IO(异步IO)


一,基本概念

 1.1用户空间与内核空间

的Linux的系统采用的是虚拟地址,对32位的操作系统,寻址空间2的32次方,一共4G的虚拟存储空间。这4G空间被分为内核空间和用户空间两部分。

内核空间:(0xC0000000的的~0xFFFFFFFF的),1G大小,供内核使用,称为内核空间。

用户空间:(00000000~0xBFFFFFFF),3G大小,供用户程序使用,称为用户空间。

1.2同步与异步 

同步就是阻塞,异步就是非阻塞这种观点是错误的,同步与异步,阻塞与非阻塞他们描述的不是同一个对象。

同步:指的是调用一个功能时,调用者会主动的等待结果,没有得到结果该调用不会返回必须按顺序的一件一件的做事情。

异步:指的是调用一个功能是,调用者不需要立刻得到结果,它就返回了,返回后可以继续做其他事情当该调用执行处理完成后,它会通过状态,通知,回调来通知调用者。

同步与异步的根本性区别:同步是需要主动等待消息通知,而异步则是被动接收消息通知,通过回调,通知,状态等方式来被动获取消息。

1.3阻塞与非阻塞

 阻塞:指调用结果返回之前,当前线程会被挂起,不再占用CPU(CPU不会给它分配时间片,线程被暂停执行),函数在得到结果之后才会返回。

非阻塞:与阻塞相对应,指不管结果是否成功,该函数不会阻塞当前线程,而会立刻返回,当前线程不会被挂起。

同步与阻塞的区别:对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回,它还会抢占CPU去执行其他逻辑,也会主动检测IO是否准备好但阻塞是不占用CPU的。

这里有一段总结:

摘要:“简述linux同步与异步,阻塞与非阻塞概念以及五种IO模型” https://www.cnblogs.com/chaser24/p/6112071.html

个人觉得,这里第1点说的也不太准确,这里的关键点不是死等结果,而应该理解为主动的去查询或者等待查询查询查询查询结果。

1.4缓存IO

缓存IO又被称作标准IO,大多数文件系统的默认IO操作都是缓存IO。在Linux的的缓存IO机制中,操作系统会将IO的数据缓存在文件系统的页面缓存(页面缓存)中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存IO的缺点:数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的CPU以及内存开销是非常大的。

摘自:“聊聊Linux中的五种IO模型”http://www.360doc.com/content/18/1127/17/60934035_797610428.shtml

二,LINUX下IO模型

IO指的是输入输出,除了普通的文件读写外,更多地用于描述网络IO.Linux下一切皆文件,对所有的打开的文件,都有与之对应的文件描述符。网络IO的本质是插座的读取,插座在Linux的系统被抽象为流,IO可以理解为对流的操作。

对于一次IO访问(以读举例),会数据先被拷贝产品到操作系统内核的缓冲区中,才会然后从操作系统内核的缓冲区拷贝产品到应用程序的地址空间。所以说,当一个读操作发生时,它会经历两个阶段:

第一阶段:等待数据准备(等待数据准备好)。
第二阶段:将数据从内核拷贝到进程中(将数据从内核复制到进程)。

对于插座流而言:

第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
第二步:把数据从内核缓冲区复制到应用进程缓冲区。

 

先看看基本的Linux I / O模型的简单矩阵

Linux的下的IO模型主要有以下5种: 

  • 阻塞I / O(阻塞I / O)
  • 非阻塞I / O(非阻塞I / O)
  • 多路复用IO(多路复用IO)
  • 信号驱动I / O(信号驱动IO)
  • 异步IO(异步IO)

2.1同步阻塞IO(阻塞IO)

场景引入:我去银行办理一项业务,看见人很多,我需要等待,在等待的时候,我休息,什么都不做,直到有窗口可以为我服务这个过程就是同步阻塞的。

 

同步阻塞I / O模型的典型流程:

 在这个模型中,用户空间的应用程序执行一个系统调用,这导致应用程序直阻塞,直到系统调用完成为止(数据传输完成或发生错误)。调用应用程序处于挂起的状态,不占用CPU,只是简单等待响应的状态。比如:在调用读系统调用时,应用程序会阻塞并对内核进行上下文切换。然后会触发读操作,当响应返回时(从我们正在从中读取的设备中返回),数据就被移动到用户空间的缓冲区中。然后应用程序就会解除阻塞(read调用返回)。

 

以下是网络的同步阻塞IO模型:

è¾å¥å¾çè¯'æ

 

流程描述:

  • 用户进程调用了的recv()/ recvfrom的()系统调用
  • 第一个阶段:kernel准备数据,等待数据被拷贝到操作系统内核的缓冲区(对于网络IO来说,很多时候数据在一开始还没有到达,比如:还没有收到一个完整的UDP包)。用户进程自己选择被阻塞。
  • 第二个阶段:当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后内核返回结果,用户进程才解除块的状态,重新运行起来。
优点效率高,指调用者能够及时返回数据,无延迟;对内核开发者来说省事。
缺点对用户来说处于等待就要付出性能的代价,浪费掉了等待的时间。

 

 

 

2.2同步非阻塞IO(非阻塞IO)

场景引入:我去银行办一项业务,发现人很多,需要等待,但我不想在等待的过程中休息,白白浪费掉这段时间,还想看一会小说,于是我就不断的询问客服,轮到我没有,如果没有,那我再看一会小说,接着再询问一遍客服好了没有,没有好的话我再看一会小说,直到客服告诉我好了。

 

同步非阻塞I / O模型的典型流程:

同步非阻塞就是“每隔一会儿瞄一眼进度条”的轮询(polling)方式,它的效率比同步阻塞IO低(因为它轮询是有时间间隔的,它牺牲掉了那个时间间隔,不能一有数据结果就立刻返回,这会导致整体数据吞吐量的降低)。在这种模型中,设备是以非阻塞的形式打开的。这意味着IO操作不会立即完成,读操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN或EWOULDBLOCK)。

以下是网络的同步非阻塞IO模型:

è¾å¥å¾çè¯'æ

流程描述:

 

  • 用户程序调用非阻塞的recvform系统调用,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个错误。
  • 进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。
  • 重复上面的过程,循环往复的进行recvform系统调用,直到数据准备好,再拷贝数据到用户进程空间,进行数据处理。

需要注意的是,默认的套接字是阻塞的,可以通过设置参数来使其变为非阻塞。

优点用户程序性能较高,能够在等待任务完成的时间里干其他活,也就是“后台”可以有多个任务在同时执行。
缺点任务完成的响应延迟增大了,因为每过一段时间才去轮询一次读操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低

 

 

 

 

 

2.3 IO多路复用(IO多路复用) 

场景引入:我去银行要办理多项业务,这多项业务需要到不同的窗口办理,如果按照同步非阻塞的模型,我得不断地询问客服,累死我了。为了提升客户体验,银行也推出在一个通知的APP,但这个APP的开发人员有点渣,我在上边申请多个窗口的排号,然后就可以休息了,只要某个窗口空闲,它就提示“哔”的一声告诉我可以去办理某个业务了,而不提示让我到具体的窗口办理具体的业务,这时我并不知道具体是哪个窗口,还得通过APP轮询查看一遍我的窗口排号状态,看看到底是哪个窗口空闲了再去办理业务。

同步非阻塞方式需要不断主动轮询,进程不会被挂起,轮询会消耗大量的CPU时间。而且后台可能有多个任务在同时进行,同步非阻塞的方式一个线程只能监听一个IO,我们希望能够一个线程轮询多个任务的完成状态,只要有任何一个任务完成,就去处理它。这里的轮询不是进程的用户态的,而是内核完成的。这就是所谓的“IO多路复用“.UNIX / Linux下的select,poll,epoll就是干实现这个的。有些地方又把这种IO方式为事件驱动的IO。

IO多路复用有几个特别的系统调用:select,poll,epoll函数。

选择调用是内核级别的,选择轮询相对非阻塞的轮询的区别在于 - 前者可以等待多个插座,能实现同时对多个IO端口进行监听,当其中任何一个插座的数据准好了,就能返回进行可读,然后进程再进行recvform系统调用,将数据由内核拷贝到用户进程,当然这个过程是阻塞的。

选择或轮询调用之后,会阻塞进程,与阻止IO阻塞不同在于,此时的选择不是等到socket数据全部到达再处理,而是有了一部分数据就会调用用户进程来处理。如何知道有一部分数据到达了呢?监视的事情交给了内核,内核负责数据到达的处理。

I / O复用模型会用到选择,民意调查显示,epoll的函数,这几个函数也会使进程阻塞,但是和阻塞I / O所不同的的,这两个函数可以同时阻塞多个I / O操作。而且可以同时对多个读操作,多个写操作的I / O函数进行检测,直到有数据可读或可写时(注意不是全部数据可读或可写),才真正调用I / O操作函数。

以下是网络的多路复用IO模型:

è¾å¥å¾çè¯'æ

 流程描述:

  • 用户进程调用选择,进程被块。
  • 内核监测所有选择负责的插座,当任何一个插座中的数据准备好了,选择就会返回。
  • 用户进程调用读取操作,将数据从内核拷贝到用户进程。

多路复用IO模型与同步阻塞IO模型的区别:前者能够同时监测多个IO,而后者只能监听一个如果只是对一个IO进行检测,前者的效果不比后者好,因为多路复用IO需要使用两个系统调用(select和recvfrom),而阻止IO只调用了一个系统调用(recvfrom)。多路复用IO的优势在于它可以同时处理多个连接。所以,如果处理的连接数不是很高的话,使用select / epoll的web服务器不一定比使用多线程+阻塞IO的web服务器性能更好,可能延迟还更大。(select / epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

IO多路复用在阻塞到选择阶段时,用户进程是主动等待并调用选择函数获取数据就绪状态消息,并且其进程状态为阻塞。所以,把IO多路复用归为同步阻塞模式。

高并发的程序一般使用同步非阻塞方式而非多线程+同步阻塞方式。要理解这一点,首先要扯到并发和并行的区别。比如去某部门办事需要依次去几个窗口,办事大厅里的人数就是并发数,而窗口个数就是并行度。也就是说并发数是指同时进行的任务数(如同时服务的HTTP请求),而并行数是可以同时工作的物理资源数量(如CPU核数通过合理调度任务的不同阶段,并发数可以远远大于并行度,这就是区区几个CPU可以支持上万个用户并发请求的奥秘。在这种高并发的情况下,为每个任务(用户请求)创建一个进程或线程的开销非常大。而同步非阻塞方式可以把多个IO请求丢到后台去,这就可以在一个进程里服务大量的并发IO请求。

 2.4信号驱动式IO(信号驱动IO)

信号驱动式I / O:首先我们允许插座进行信号驱动IO,并安装一个信号处理函数,进程继续运行并不阻塞当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I / O操作函数处理数据过程如下图所示:

è¾å¥å¾çè¯'æ

 2.5异步非阻塞IO(异步IO)

相对于同步IO,异步IO不是顺序执行。用户进程进行的aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到插座数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知.IO两个阶段,进程都是非阻塞的。

异步非阻塞I / O模型的典型流程:

 在一个进程中为了执行多个I / O请求而对计算操作和I / O处理进行重叠处理的能力利用了处理速度与I / O速度之间的差异。当一个或多个I / O请求挂起时,CPU可以执行其他任务;或者更为常见的是,在发起其他I / O的同时对已经完成的I / O进行操作。

以下是网络的异步非阻塞I / O模型:

è¾å¥å¾çè¯'æ

流程描述:

  • 用户进程发起的aio_read操作之后,立刻就可以开始去做其它的事。
  • kernel接收到一个异步读后,立刻返回,不会对用户进程产生任何阻塞。
  • 内核等待数据准备完成,然后将数据拷贝到用户内存。
  • 内核给用户进程发送一个信号或执行一个基于线程的回调函数来完成这次IO处理过程,告诉它阅读操作完成了。
  • 如果这个进程正在用户态忙着做别的事(例如在计算两个矩阵的乘积),那就强行打断之,调用事先注册的信号处理函数,这个函数可以决定何时以及如何处理这个异步任务。
  • 如果这个进程正在内核态忙着做别的事,例如以同步阻塞方式读写磁盘,那就只好把这个通知挂起来了,等到内核态的事情忙完了,快要回到用户态的时候,再触发信号通知。
  • 如果这个进程现在被挂起了,例如无事可做睡了,那就把这个进程唤醒,下次有CPU空闲的时候,就会调度到这个进程,触发信号通知。

总结一下:  

 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值