五种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

智能推荐

【杂七杂八】excel中根据RTL信号位宽生成拼接取位_rtl语法 位宽拼接-程序员宅基地

文章浏览阅读398次。前言作为一个不务正业的芯片前端,总会遇到掉奇奇怪怪的需求,就比如题目这个啊,我写完之后就觉得非常的拗口。那么具体的需要是啥呢?就是比如说有了下面这个excel表:信号名 width sig0 3 sig1 10 sig2 14 sig3 20 sig4 8 要直接做一列生成前面几个信号在整体信号中的取位信息,简单来说就是这样:信号名 width local sig0 3 [2:0] sig1 10_rtl语法 位宽拼接

go WaitGroup的坑-程序员宅基地

文章浏览阅读2.3k次。go WaitGroup的使用请参考笔者的另外一篇博客go WaitGroup的使用示例这里重点讲一下WaitGroup的注意点,以免被坑示例代码如下:package mainimport ( "log" "sync")func main() { wg := sync.WaitGroup{} for i := 0; i < 5; i++ { wg.Add(..._waitgroup的坑

升级libc.so.6和libstdc++.so.6方法_deepin 升级libstdc++6-程序员宅基地

文章浏览阅读8.2k次。解决"libc.so.6: version `GLIBC_2.14' not found"问题转载自https://www.cnblogs.com/Mrhuangrui/p/7766554.html试图运行程序,提示"libc.so.6: version `GLIBC_2.14' not found",原因是系统的glibc版本太低,软件编译时使用了较高版本的glibc引起的:问题Ce..._deepin 升级libstdc++6

快速学习STL-程序员宅基地

文章浏览阅读386次。STL概述STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但这种分离确实使得STL变得非常通用。例如,由于STL的sort()函数是完全通用的,你可以用它来操作几乎任何数据集合,包括链表,容器和数组。要点STL算法作为模板函数提供。为了和其他组件相区别,在本书中STL算法以后接一对圆括弧的方式表示,例如sort()。STL另一个重要特性是它不是面向_学习stl

React-基础语法学习-程序员宅基地

文章浏览阅读1.0k次,点赞21次,收藏29次。1、教程:井字棋游戏1、教程:井字棋游戏本教程将引导你逐步实现一个简单的井字棋游戏,并且不需要你对 React 有任何了解。在此过程中你会学习到一些编写 React 程序的基本知识,完全理解它们可以让你对 React 有比较深入的理解。1.1、教程分成以下几个部分:配置是一些准备工作。概览介绍了 React 的:组件、props 和 state。完成游戏介绍了 React 开发中。添加时间旅行可以让你更深入地了解 React 的独特优势。1.2、实现的是什么程序?

python少儿图形编程软件_现在最好的少儿编程软件是什么?-程序员宅基地

文章浏览阅读3k次。目前使用最多的少儿编程软件就是Scratch;但是Scratch被禁了,推荐几款国内现在使用最广泛最好用的几款少儿编程软件。1、Mind+Mind+是一款拥有自主知识产权的国产图形化编程软件,诞生于2013年,由DFRobot开发。主要就是针对国内科技创新教育需求而开发的图形化编程软件,也是国内最早的图形化编程软件工具之一。在Scratch平台不能使用后,惊喜发现它完全可以兼容sb3格式保存的编程..._少儿图形编程软件

随便推点

计算机三级网络技术知识点大全(九)_知识点三级-程序员宅基地

文章浏览阅读6.2k次,点赞3次,收藏22次。网络安全技术1、 网络安全的基本要素主要包括:保密性、完整性、可用性、可鉴别性和不可否认性。2、 信息泄露与篡改:截获信息、窃听信息、篡改信息和伪造信息。3、 在Internet中对网络的攻击可以分为2种基本类型,即服务攻击(造成拒绝服务Dos,典型的是SYN)与非服务攻击(对网络层等底层协议进行攻击)。4、 信息的安全传输包括两个基本部分:(1)对发送的信息进行安全转换(如信息加密),实现信息的保密性。或者附加一些特征信息,以便进行发送方身份验证。(2)发送和接收双方共享的.._知识点三级

已解决java.util.concurrent.CompletionException异常的正确解决方法,亲测有效!!!-程序员宅基地

文章浏览阅读3k次,点赞23次,收藏27次。已解决java.util.concurrent.CompletionException异常的正确解决方法,亲测有效!!!_completionexception

Android ConstraintLayout 约束布局 1-程序员宅基地

文章浏览阅读987次,点赞8次,收藏24次。对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

电机控制算法FOC-研究英飞凌AP32013i_Inverter_TC27xC_1_22心得(一)-程序员宅基地

文章浏览阅读7.6k次,点赞8次,收藏119次。本文参照英飞凌AP32013i_Inverter_TC27xC_1_22相关的文档,研究FOC算法的原理,模块组成和调试过程。_电机控制算法

iOS制作framework_ios framework-程序员宅基地

文章浏览阅读6.8k次,点赞4次,收藏17次。ios 制作SDK , ios图文详解制作.framework_ios framework

基于ThinkPhp6+Vue+ElementUI通用后台管理系统_thinkphp6+layui后台管理系统-程序员宅基地

文章浏览阅读7.9k次,点赞2次,收藏24次。一款 PHP 语言基于ThinkPhp6、Vue、ElementUI等框架精心打造的一款模块化、插件化、高性能的前后端分离架构敏捷开发框架,可用于快速搭建前后端分离后台管理系统,本着简化开发、提升开发效率的初衷,目前框架已集成了完整的RBAC权限架构和常规基础模块,前端Vue端支持多主题切换,可以根据自己喜欢的风格选择想一个的主题,实现了个性化呈现的需求;_thinkphp6+layui后台管理系统

推荐文章

热门文章

相关标签