Linux中5种IO模型

在了解IO模型时需要清楚什么是同步和异步,什么是阻塞和非阻塞

同步/异步 阻塞/非阻塞

当IO操作发生时,一定是两方参与的,分别是调用方和被调用方。阻塞和非阻塞相对于的事调用方,同步和异步相对于的好似被调用方。
请添加图片描述
A去调用B

  • 阻塞 当A发出调用命令后,要一直等待B返回的结果
  • 非阻塞 当A发出调用命令后,不需要等待,可以做自己的事情
  • 同步 当B收到A调用的命令后,会立即执行要做的事情,A的本次调用会得到结果
  • 异步 当B收到A调用的命令后,不保证立即执行,但保证一定会做,A本次不会得到结果,等B执行好之后会通知A。

同步不一定阻塞,同样异步不一定非阻塞,只不过是参照物不同,处理方式不同。清楚这些后下面了解一下Linux中5种IO模型

Linux中5种IO模型

在这里插入图片描述

一次完整的IO操作

我们常说IO是指文件的输入和输出,但是在操作系统是如何定义IO的呢?到底操作系统的一次IO过程是什么呢?

操作系统负责计算机的资源管理和进程的调度。我们电脑上跑着的应用程序,其实是需要经过操作系统,才能做一些特殊操作,如磁盘文件读写、内存的读写等等。因为这些都是比较危险的操作,不可以由应用程序乱来,只能交给底层操作系统来。也就是说,你的应用程序要把数据写入磁盘,只能通过调用操作系统开放出来的API来操作。

我们应用程序是跑在用户空间的,它不存在实质的IO过程,真正的IO是在操作系统执行的。即应用程序的IO操作分为两种动作:IO调用和IO执行。IO调用是由进程(应用程序的运行态)发起,而IO执行是操作系统内核的工作。此时所说的IO是应用程序对操作系统IO功能的一次触发,即IO调用。

一次IO过程

应用程序发起的一次IO操作包含两个阶段:

IO调用:应用程序进程向操作系统内核发起调用。
IO执行:操作系统内核完成IO操作。

操作系统内核完成IO操作还包括两个过程:

准备数据阶段:内核等待I/O设备准备好数据
拷贝数据阶段:将数据从内核缓冲区拷贝到用户进程缓冲区

在这里插入图片描述
应用程序进程向操作系统发起IO调用请求
操作系统准备数据,把IO外部设备的数据,加载到内核缓冲区
操作系统拷贝数据,即将内核缓冲区的数据,拷贝到用户进程缓冲区

1、阻塞IO模型

这是一种比较传统的IO模型也是最简单的IO模型,即在读/写数据过程中会发生阻塞现象。简称(BIO)当进程或线程调用某个条件,当条件不满足则会一直等下去。如果条件满足则执行下一步
在这里插入图片描述

应用进程向操作系统内核,发起recvfrom读取数据,但是内核还没准备好,应用程序就会阻塞,直至内核准备好数据,recvfrom完成数据的复制工作,应用程序结束阻塞状态。

2、非阻塞IO模型

阻塞式IO的缺点非常明显。如果内核数据一直没准备好,那用户进程将一直阻塞,浪费性能,可以使用非阻塞IO优化 简称NIO
当应用进程与内核发起调用时,当目的未达到之前不再一味的等待,而是直接返回,然后通过轮询的方式进行访问内核数据有没有准备好,如果数据准备好了,则将数据复制到用户空间。
在这里插入图片描述

应用进程不断的通过recevfrom不停的与内核进行交互,直至内核数据准备好。如果数据没有准备好会返回error,应用进程在得到error后,过一段时间再发送recevfrom请求,在这期间进程可以在做其他事情。虽然大幅提升了性能,但是它依然存在性能问题,即频繁的轮询,导致频繁的系统调用,同样会消耗大量的CPU资源。

3、IO多路复用模型

既然NIO无效的轮询会导致CPU资源消耗,我们等到内核数据准备好了,主动通知应用进程再去进行系统调用,那不就好了嘛?

在这之前,我们先来复习下,什么是 文件描述符fd(File Descriptor), 它是计算机科学中的一个术语,形式上是一个非负整数。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符

IO复用模型核心思路: 系统给我们提供一类函数(如我们耳濡目染的 select、poll、epoll 函数),它们可以同时监控多个fd的操作,任何一个返回内核数据就绪,应用进程再发起recvfrom系统调用。
在这里插入图片描述
非阻塞IO模型(NIO)中,需要N(N>=1)次轮询系统调用,然而借助select的IO多路复用模型,只需要发起一次询问就够了,大大优化了性能。

但是呢,select有几个缺点:

监听的IO最大连接数有限,在Linux系统上一般为1024。

select函数返回后,是通过遍历fdset ,找到就绪的描述符fd。(仅知道有I/O事件发生,却不知是哪几个流,所以遍历所有流)

因为存在连接数限制,所以后来又提出了poll。与select相比,poll解决了连接数限制问题。但是呢,select和poll一样,还是需要通过遍历文件描述符来获取已经就绪的socket。如果同时连接的大量客户端,在一时刻可能只有极少处于就绪状态,伴随着监视的描述符数量的增长,效率也会线性下降。

因此经典的多路复用模型epoll诞生。

IO多路复用之epoll

epoll先通过epoll_ctl() 来注册一个fd(文件描述符),一旦基于某个fd就绪时,内核会采用回调机制,迅速激活这个fd,当进程调用epoll_wait() 时便得到通知。这里去掉了遍历文件描述符的操作,而是采用监听事件回调的机制。这就是epoll的亮点。

fd数据拷贝每次调用select,需要将fd数据从用户空间拷贝到内核空间每次调用poll,需要将fd数据从用户空间拷贝到内核空间使用内存映射(mmap),不需要从用户空间频繁拷贝fd数据到内核空间

epoll明显优化了IO的执行效率,但在进程调用epoll_wait()时,仍然可能被阻塞。属于同步IO,需要在读写事件准备就绪后,由系统调用进行阻塞式读写,能不能酱紫:不用我老是去问你数据是否准备就绪,等我发出请求后,你数据准备好了通知我就行了,这就诞生了信号驱动IO模型。

4、信号驱动IO模型

信号驱动IO不再用主动询问的方式去确认数据是否就绪,而是向内核发送一个信号(调用sigaction的时候建立一个SIGIO的信号),然后应用用户进程可以去做别的事,不用阻塞。当内核数据准备好后,再通过SIGIO信号通知应用进程,数据准备好后的可读状态。应用用户进程收到信号之后,立即调用recvfrom,去读取数据。
在这里插入图片描述

应用进程预先往内核注册一个信号处理函数,然后用户进程不会阻塞直接返回,当内核数据准备就绪会发送一个信号给进程,用户进程在信号处理含函数中把数据复制到用户空间。

不管是BIO,还是NIO,还是信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的。还有没有优化方案呢?AIO(真正的异步IO)!

5、异步IO模型

前面讲的BIO,NIO和信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的,因此都不算是真正的异步。AIO实现了IO全流程的非阻塞,就是应用进程发出系统调用后,是立即返回的,但是立即返回的不是处理结果,而是表示提交成功类似的意思。等内核数据准备好,将数据拷贝到用户进程缓冲区,发送信号通知用户进程IO操作执行完毕。
在这里插入图片描述

用户发起aio_read操作之后,会给内核传递描述符,缓冲区指针、缓冲区大小,告诉内核当整个操作完成时,如何通知进程,然后就可以立即做其他事情了。让内核收到aio_read后,会立即返回,然后开始等待数据准备,数据准备好之后,直接把数据复制到用户控件,然后通知进程本次IO完成

BIO、NIO、AIO

同步阻塞(blocking-IO) 简称BIO 适用于连接数目小的架构,这种方式对服务器资源要求比较高,但BIO直观、简单、易理解。

同步非阻塞(non-blocking-IO)简称NIO 适用于连接数目多,且比较短的架构。比如聊天服务器

异步非阻塞(asynchronous-non-blocking-IO)简称AIO, 适用于连接数目多且连接比较长的架构。如相册服务器。

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值