五种IO模型-程序员宅基地

技术标签: c++  linux  后端  c/c++  

什么是IO

IO,即input/output,IO模型即输入输出模型,而比较常见且听说的便是磁盘IO,网络IO.

按照冯诺依曼结构的来看,假设我们把运算器、控制器、存储器三个设备看做一个整体(假设称为中转),那么输入设备、输出设备,和中转就构成一个中转IO,也就是说IO是以某一个核心为主体的,而涉及计算机核心与其他核心之间数据迁移的过程我们就成为一个IO.

在这里插入图片描述


操作系统的IO

如果要将内存中的数据写入到磁盘,那么其IO主体是什么呢?主体很可能是一个应用程序.

但我们电脑上所跑起来的应用程序是无法直接进行一些特殊操作(IO)的,比如内存读写,磁盘读写,因为用户可能会利用此程序直接或者间接的对计算机造成破坏,应用程序想要进行这些特殊操作,只能交给底层软件—操作系统.也就是说应用程序想要将数据从内存写入磁盘或者反过来说从磁盘读取内存,只能通过操作系统对上层开放的API来进行.

而在任何一个应用程序里面,都会有进程地址空间,该空间分为两部分,一部分称为用户空间(允许应用程序进行访问的空间),另一部分称为内核空间,是只能留给操作系统进行访问的空间,也就是说它受到保护.

因此,一个应用程序想要进行一次IO操作将会分为两个阶段:

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

而操作系统完成一次IO操作也包括两个过程:

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

在这里插入图片描述

因此一个完整的IO过程包括以下几个步骤:

  1. 应用程序进程向操作系统发起IO调用请求
  2. 操作系统准备数据,把IO外部设备的数据,加载到内核缓冲区
  3. 操作系统拷贝数据,即将内核缓冲区的数据,拷贝到用户进程缓冲区

而一次IO的本质其实就是: 等待 + 拷贝

五种IO模型

在了解IO模型之前,我们先回顾一下网络应用程序之间,是怎么进行数据发送和接收的.按照一下两个应用程序为例:
在这里插入图片描述

应用A把消息发送到 TCP发送缓冲区,TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器的TCP接收缓冲区,B再从TCP接收缓冲区去读取属于自己的数据,同理,当B对A发送数据时,路径类似.

现在我们看下什么是IO模型

阻塞IO

参考上图流程,我们思考一个问题,TCP缓冲区还没有完全接收(假设数据量为1,目前接收数据量为0或小于1)到属于应用B该读取的消息时,应用B向TCP缓冲区发起读取申请,TCP接收缓冲区是应该马上告诉应用B 现在没有你的数据,你去做别的事吧,还是说让应用B在这里等着,直到有数据再把数据交给应用B。

在这里插入图片描述

同理,应用A在向TCP发送缓冲区发送数据时,如果TCP发送缓冲区已经满了,那么是告诉应用A现在没空间了,还是让应用A等待着,等TCP发送缓冲区有空间了再把应用A的数据访拷贝到发送缓冲区。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWd88p6W-1668785543910)(13五种IO模型.assets/image-20221118225747153.png)]

而阻塞IO就是当应用B发起读取数据申请时,在内核数据没有准备好之前,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。

学术语言就是:在应用调用recvfrom读取数据时,其系统调用直到数据包到达且被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的称为阻塞IO;在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式

流程和流程图:

流程描述:

1、应用进程向内核发起recfrom读取数据

2、内核进行准备数据报(此时应用进程阻塞)

3、内核将数据从内核负复制到应用空间。

4、复制完成后,返回成功提示

在这里插入图片描述

代码模拟阻塞IO过程

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main(){
    char buf[1024];
    printf("等待您的数据准备:");fflush(stdout);
    ssize_t Rea = read(0,buf,sizeof(buf)-1);  //等待键盘键入数据
    if(Rea>0){
        buf[Rea]=0;
        printf("数据准备完毕,开始写进屏幕\n");
        sleep(1);
        write(1,buf,strlen(buf));
    }
    return 0;
}

非阻塞IO

非阻塞IO就是当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待,如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码;

非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一 般只有特定场景下才使用

流程和流程图:

1、应用进程向内核发起recvfrom读取数据。

2、内核数据报没有准备好,即刻返回EWOULDBLOCK错误码。

3、应用进程再次向内核发起recvfrom读取数据。

4、内核倘若已有数据包准备好就进行下一步骤,否则还是返回错误码,执行第三步骤

5、内核将数据拷贝到用户空间。

6、完成后,返回成功提示。

在这里插入图片描述


fctnl函数用来对文件描述符进行处理,他有五种功能:

//函数声明
int fcntl(int fd, int cmd, ... /* arg */ );
  • 复制一个现有的描述符(cmd=F_DUPFD).

  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).

  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).

  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).

  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).

我们此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞

void SetNonBlock(int fd) {
    int fl = fcntl(fd, F_GETFL);  
    if (fl < 0) {
    	perror("fcntl");  return;
    }
	fcntl(fd, F_SETFL, fl | O_NONBLOCK);  //设置为非阻塞
}

代码模拟非阻塞:

int main()
{
    char buf[1024];
    SetNonBlock(0);
    while (true)
    {
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            write(1,buffer,strlen(buffer));
            printf("读取成功!");
        }
        else
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                sleep(1);
                std::cout << "当前没有出错,仅仅底层数据没有就绪罢了..." << std::endl;
                continue;
            }
            if(errno == EINTR){
                std::cout << "读取被信号中断" << std::endl;
                continue;
            }
            std::cout << "read error: " << s << std::endl;
            break;
        }
    }
    return 0;
}

多路转接IO(复用IO)

假设在并发的环境下,可能会N个人向应用B发送消息,这种情况下我们的应用就必须创建多个线程去读取数据,每个线程都会自己调用recvfrom 去读取数据。那么此时情况可能如下图:
在这里插入图片描述

倘若服务器在上百万请求下,应用B就需要创建上百万的线程去读取数据,同时又因为应用线程是不知道数据是否准备好,为了保证消息能及时读取到,那么这些线程自己必须不断的向内核发送recvfrom 请求来读取数据,而如此庞大的线程资源仅仅只是用来等待读取数据,那么就意味着能做其它事情的线程就会少,这将造成极其庞大的浪费.

多路转接模型: 所以有人就提出了一个思路,由一个线程监控多个网络请求(我们后面将称为fd文件描述符,linux系统把所有网络请求以一个fd来标识),来完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,这样就可以节省出大量的线程资源出来

在这里插入图片描述

上图可以看出多路复用就是系统提供了一种函数可以同时监控多个fd的操作,这个函数就是我们常说到的select、poll、epoll函数,可以通过它们同时监控多个fd,只要有任何一个数据状态准备就绪了,就返回可读状态,这时询问线程再去通知处理数据的线程,对应线程此时再发起recvfrom请求去读取数据.

虽然从流程图上看起来和阻塞IO类似.,实际上最核心之处在于IO多路转接能够同时等待多个文件 描述符的就绪状态,来达到不必为每个fd创建一个对应的监控线程,从而减少线程资源创建的目的。

流程图:

在这里插入图片描述

信号驱动IO

多路转接解决了一个线程可以监控多个fd的问题,但是select采用无脑的轮询就显得有点暴力,因为大部分情况下的轮询都是无效的,所以有人就想,别让我总去问数据是否准备就绪,而是等你准备就绪后主动通知我,这边是信号驱动IO.

信号驱动IO是在调用sigaction时候建立一个SIGIO的信号联系,当内核准备好数据之后再通过SIGIO信号通知线程,此fd准备就绪,当线程收到可读信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下,应用线程在发出信号监控后即可返回,不会阻塞,所以一个应用线程也可以同时监控多个fd。

在这里插入图片描述

内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作.

在这里插入图片描述

多路转接IO里面的select虽然可以监控多个fd了,但select其实现的本质上还是通过不断的轮询fd来监控数据状态, 因为大部分轮询请求其实都是无效的,所以信号驱动IO意在通过这种建立信号关联的方式,实现了发出请求后只需要等待数据就绪的通知即可,这样就可以避免大量无效的数据状态轮询操作。

异步IO

多路转接IO和信号驱动IO虽然相比于单纯的阻塞IO和非阻塞IO来说,提升了一些效率,但它们都有着两次操作,即先发送select让其等待,然后再recv进行读取,但我们网络需求是直接读取,所以有人在前面基础上提出了一种方法:

应用只需要向内核发送一个读取请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,我们称这种模式为异步IO模型

在这里插入图片描述

异步IO的优化思路是解决应用程序需要先后发送询问请求、接收数据请求两个阶段的模式,在异步IO的模式下,只需要向内核发送一次请求就可以完成状态询问和数拷贝的所有操作

同步异步

同步和异步关注的是消息通信机制.

所谓同步,就是在发出一个调用时,自己需要参与等待结果的过程,则为同步,前面四个IO都自己参与了,所以也称为同步IO.

异步IO,则指出发出调用以后,到数据准备完成,自己都未参与,则为异步.

另外, 在讲多进程多线程的时候, 也有提到同步和互斥. 这里的同步和今天所讲的同步异步是完全不相干的概 念.

进程/线程同步:是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、 传递信息所产生的制约关系.,尤其是在访问临界资源的时候.

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_51723227/article/details/127931476

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法