• 高效IO模型


    前言

    IO过程总体分为两步,第一步:接收端->接受缓冲区等待数据的递达,发送端->等待发送缓冲区有可用空间,将发送数据放入发送缓冲区;第二步拷贝:接收端->将递达的数据从内核空间拷贝到用户空间的接受缓冲区,发送端->将发送的数据从用户空间的发送缓冲区拷贝至内核缓冲区;

    所以高效IO本质是单位时间内拷贝的频率很高,重点是缩短等待的时间

    一、五种IO模型

    1.1 阻塞IO

    阻塞IO是最常见的IO模型(在内核将数据准备好之前,系统调用会一直等待,所有的套接字,默认都是阻塞方式)
    在这里插入图片描述

    阻塞:本质是由OS发起,由OS执行,进程状态R->(S or T or D),进入等待队列当中,数据就绪后,进程被操作系统唤醒,再去执行recvfrom

    1.2 非阻塞IO

    如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK

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

    在这里插入图片描述

    非阻塞轮询的本质是:由用户发起,OS执行,做事件就绪(OS有数据)的检测工作

    1.3 信号驱动IO

    内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作
    在这里插入图片描述

    1.4 异步IO

    由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)

    在这里插入图片描述

    1.5 IO多路转接

    虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件
    描述符的就绪状态
    在这里插入图片描述

    二、同步通信与异步通信

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

    • 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回,就得到返回值;换句话说,就是由调用者主动等待这个调用的结果;拷贝需要用户参与
    • 异步则是相反,调用在发出之后,这个调用就直接返回了,所有没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或者通过回调函数处理这个调用。拷贝不需要用户参与,操作系统拷贝

    这里的同步通信和进程之间的同步是完全不相干的概念

    三、非阻塞IO

    非阻塞IO可以open打开时设置flag为非阻塞,这里主要介绍打开后的fd如何设置为非阻塞

    3.1 fcntl

    一个文件描述符,默认都是阻塞IO,fcntl函数用于已经打开的fd

    该函数是一个系统调用函数

    man  2  fcntl
    
    • 1
    #include 
    #include 
    
    int fcntl(int fd, int cmd, ... /* arg */ );
    
    • 1
    • 2
    • 3
    • 4

    fcntl函数有5种功能:

    • 复制一个现有的描述符 ---- cmd=F_DUPFD
    • 获得/设置文件描述符标记–cmd=F_GETFD或F_SETFD
    • 获得/设置文件状态标记-----cmd=F_GETFL或F_SETFL
    • 获得/设置异步IO所有权-----cmd=F_GETOWN或F_SETOWN
    • 获得/设置记录锁--------------cmd=F_GETLK,F_SETLK或F_SETLKW

    传入的cmd的值不同,后面追加的参数也不相同

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

    3.2 实现函数SetNoBlock

    基于fcntl,实现一个SetNoBlock函数,将文件描述符设置为非阻塞

    void SetNoBlock(int fd){
    	int fl = fcntl(fd,F_GETFL);   //获取fd的状态标记
    	if(fl < 0){
    		perror("fcntl");
    		return ;
    	}
    	fcntl(fd, F_SETFL, fl | O_NONBLOCK);  //将fl状态标记设置为非阻塞 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.3 如何使用函数SetNoBlock

    以轮询的方式读取标准输入

    #include                                                    
    #include
    #include
    
    bool SetNonBlock(int fd){
    	   int fl = fcntl(fd,F_GETFL);
    	   if(fl < 0){
    	    std::cerr << "fcntl error" << std::endl;
    	    return false;
    	  }
    	  fcntl(fd,F_SETFL, fl | O_NONBLOCK);
    	  return true;
     }
    int main()
    {
    #define NUM 1024
       SetNonBlock(0);
       while(true){
          char buffer[NUM];
          ssize_t size = read(0,buffer,sizeof(buffer)-1);
          if(size < 0){
              //不一定是出错了,有可能是底层没有数据
    	     if(errno == EAGAIN || errno  == EWOULDBLOCK ){
    	         std::cout << "底层的数据没有就绪,你在轮询检测一下,try again " << std::endl;
    	         continue;
              }
    	     if(errno == EINTR){
    		      std::cout << "底层数据就绪未知,被信号中断 " << std::endl;
    		     continue;
              }
    	    else{
    	  		  std::cerr << "read error: " << size << " errno: "<< errno << std::endl;  
    	          break;
    	     }
    	  }
    
    	  buffer[size] = 0;  //主动给字符串最后添加'\0'
    	  std::cout << "#echo: " <<buffer <<  std::endl;  
    	  
        }
         
         return 0;
    }       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    如果以非阻塞读取数据时,如果数据没有就绪,read是以出错的形式返回
    如何判断是read真的出错?or 底层数据没有就绪 or read被信号中断

    • errno == EAGAIN || errno == EWOULDBLOCK 时,说明数据没有就绪
    • errno == EINTR ,说明被信号中断
     grep -ER 'EAGAIN | EWOULDBLOCK'   /usr/include/  查看宏定义
    
    • 1

    在这里插入图片描述

  • 相关阅读:
    项目中用的网关Gateway及SpringCloud
    C++从零实现神经网络
    umich cv-4-2 经典卷积网络架构
    <Babel> 前端语言的巴别塔
    @react-google-maps/api实现谷歌地图中添加多边围栏,并可编辑,编辑后可获得围栏各个点的经纬度
    Python--使用selenium通过chromedriver调用谷歌浏览器
    Tomcat 部署与优化
    仅做笔记用:Stable Diffusion 通过 ControlNet 扩展图片 / 扩图
    gem5 & GPGPU-Sim 安装踩坑笔记
    【.Net】Linq的使用
  • 原文地址:https://blog.csdn.net/weixin_54792212/article/details/126051186