Linux之高级IO

news2024/11/24 17:44:26

目录

  • IO基本概念
  • 五种IO模型
    • 钓鱼人例子
    • 五种IO模型
    • 高级IO重要概念
    • 同步通信 VS 异步通信
    • 阻塞 VS 非阻塞
    • 其他高级IO
    • 阻塞IO
    • 非阻塞IO

IO基本概念

I/O(input/output)也就是输入和输出,在著名的冯诺依曼体系结构当中,将数据从输入设备拷贝到内存就叫做输入,将数据从内存拷贝到输出设备就叫做输出。

  • 对文件进行的读写操作本质就是一种IO,文件IO对应的外设就是磁盘。
  • 对网络进行的读写操作本质也是一种IO,网络IO对应的外设就是网卡。

IO存在最主要的问题就是效率问题,IO的效率极为低下的,我们以读取数据为例:

  • 当我们read/recv的时候,如果底层缓冲区中没有数据,read/recv就会阻塞等待;
  • 当我们read/recv的时候,如果底层缓冲区中有数据,read/recv就会进行拷贝,在学习TCP的时候我们知道read/recv等一系列接口本质就是拷贝函数。

由此我们就可以知道IO的本质就是等待 + 数据拷贝,只要缓冲区中没有数据,read/recv就会一直阻塞等待,直到缓冲区中出现数据,然后进行拷贝,所以说read/recv就会花费大量时间在等这一操作上面,这就是一种低效的IO模式。

我们如果想要解决这个问题,就需要让等的比重降低,这样,IO的效率就提高了,接下来我们以钓鱼人的例子来理解一下IO模型。

五种IO模型

钓鱼人例子

IO的过程其实跟钓鱼是非常相似的,IO中等的过程其实就相当于钓鱼等待鱼上钩的过程,而拷贝到过程就相当于把鱼从水里装进桶里的过程。

我们来看下面这五个人的钓鱼方式:

  • 张三:1根鱼竿,将鱼钩扔进水里以后,就一直盯着浮标一动不动,不理会外界的任何动静,直到鱼上钩;
  • 李四:1根鱼竿,将鱼钩扔进水里以后,可以干其他的事情,定期观察浮标的动静,如果鱼上钩就将鱼钓上来,没有就继续干其他事情;
  • 王五:1根鱼竿,但是在鱼竿上绑了一个铃铛,将鱼钩扔进水里以后,可以干其他的事情,铃铛一响就知道鱼上钩了,将鱼钓上来;
  • 赵六:100根鱼竿,将100根鱼竿都放置好,然后定期观察着100根鱼竿的状态,如果某个鱼竿有鱼上钩就将鱼钓上来;
  • 田七:田七是一个领导,带了一个司机,此时田七也想钓鱼,但是他要回公司开会,所以他拿来一根鱼竿,让自己的司机去钓鱼,让司机把桶装满了给他打电话来接他。

张三,李四和王五钓鱼的效率一样吗?

张三,李四和王五钓鱼的效率钓鱼的效率本质上是一样的,因为他们都是拿着一根鱼竿,在等待鱼上钩,鱼咬钩的概率是一样的。

他们只不过是等待鱼上钩的方式不一样,张三是死等,李四是定期检查浮标,王五则是通过铃铛的提示来判断鱼是否上钩。

谁的效率更高?

显而易见,赵六的效率是最高的,因为赵六有100根鱼竿,上鱼的概率是最大的,单位时间内,赵六鱼上钩的效率远远大于张三,李四和王五。

因为赵六减少了等待的概率发生,增加了拷贝的时间,所以效率是最高的。

如何看待田七钓鱼方式?

田七是将钓鱼这件事交给自己的司机去做了,自己就可以去干其他事情了,他并不关心司机是怎么钓鱼的,司机可以采用张三,李四,王五和赵六中的任意一种方式,田七只关心最后将桶装满了没。

田七并没有参与钓鱼的过程,他将钓鱼的任务安排给了司机,在司机钓鱼期间他可以做任何事情,如果将钓鱼看作是一种IO的话,那田七的这种钓鱼方式就叫做异步IO。

而对于张三、李四、王五、赵六来说,他们都需要自己等鱼上钩,当鱼上钩后又需要自己把鱼从河里钓上来,对应到IO当中就是需要自己进行数据的拷贝,因此他们四个人的钓鱼方式都叫做同步IO。

五种IO模型

这五个人的钓鱼方式对应了五种IO模型:

  • 张三这种死等方式叫做阻塞IO;
  • 李四这种定时检测的方式叫做非阻塞IO;
  • 王五这种通过设置铃铛得知事件是否就绪的方式就是信号驱动IO;
  • 王五这种一次等待多个鱼竿上有鱼的钓鱼方式就是IO多路转接;
  • 田七这种让别人帮自己钓鱼的钓鱼方式就是异步IO。

阻塞IO

阻塞IO就是在内核将数据准备好之前,系统调用会一直等待。

图示如下:

在这里插入图片描述

所有的套接字,默认的都是阻塞方式;

  • recvform读取数据时,由于底层的某些数据还没有准备就绪,此时就需要等待数据就绪,当数据就绪后就会将数据从内核拷贝到应用空间,最终 recvform函数返回成功;
  • recvform函数在等待过程中,本质上还是操作系统将该进程或者线程设置为某种非R状态,将其放入等待队列之中,而用户所看见的就是进程或者是线程阻塞住了,当数据就绪后操作系统就将等待的进程或线程唤醒,进而将数据从内核拷贝到应用空间;

非阻塞IO

非阻塞IO就是,如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码。

图示如下:

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

  • 调用recvform函数时,如果底层数据没有准备好,此时就不会等待数据就绪,而是直接返回EWOULDBLOCK错误码,如果一直没有数据就绪,就会一直返回EWOULDBLOCK错误码,直到底层数据就绪,将数据拷贝到应用程序;
  • 每次recvform函数读取数据是,就算底层数据没有成功,依然会立马返回,在用户看来进程或线程就没有被阻塞住,我们就称之为非阻塞IO;

阻塞IO与非阻塞IO的最大区别就在于阻塞IO是操作系统识别到数据就绪后唤醒进程或线程,而非阻塞IO是用户一直进行检测,直到数据准备就绪。

信号驱动IO

信号驱动IO就是当内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。

图示如下:

在这里插入图片描述
当底层数据就绪的时候会向当前进程或线程递交SIGIO信号,因此可以通过signal或sigaction函数将SIGIO的信号处理程序自定义为需要进行的IO操作,当底层数据就绪时就会自动执行对应的IO操作。

  • 调用recvform函数从套接字上读取数据时,可以将该操作定义为SIGIO的信号处理程序,当底层数据就绪时,操作系统就会递交SIGIO信号,此时就会自动执行我们定义的信号处理程序,进程将数据从内核拷贝到用户空间;
  • 信号的产生是异步的,但信号驱动IO是同步IO的一种。信号在任何时刻都可能产生,但信号驱动IO是同步IO的一种,因为当底层数据就绪时,当前进程或线程需要停下正在做的事情,转而进行数据的拷贝操作,因此当前进程或线程仍然需要参与IO过程。

IO多路转接

IO多路转接也叫做IO多路复用,能够同时等待多个文件描述符的就绪状态。

在这里插入图片描述

  • IO的过程实际上是“等 + 拷贝的过程”, 调用recvform函数之后,数据未就绪就等,数据就绪了以后就进行数据的拷贝,但是尽管recvform可以实现“等这一操作”,但是一次只能等待一个文件描述符,效率太低了;
  • 所以系统为我们提供了select/epoll/poll三组接口,这些接口的核心工作就是“等”,我们可以将所有“等”的工作都交给这些多路转接接口;
  • 因为这些多路转接接口是一次“等”多个文件描述符的,因此能够将“等”的时间进行重叠,当数据就绪后再调用对应的recvfrom等函数进行数据的拷贝,此时这些函数就能够直接进行拷贝,而不需要进行“等”操作了。

异步IO

异步IO就是由内核在数据拷贝完成时,通知应用程序。

图示如下:

在这里插入图片描述

进行异步IO需要调用一些异步IO的接口,异步IO接口调用后会立马返回,因为异步IO不需要你进行“等”和“拷贝”的操作,这两个动作都由操作系统来完成,你要做的只是发起IO,当IO完成后操作系统会通知应用程序,因此进行异步IO的进程或线程并不参与IO的所有细节。

高级IO重要概念

同步通信 VS 异步通信

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

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

为什么非阻塞IO在没有得到结果之前就返回了?

  • IO分为“等”和"拷贝”两步,当数据没有准备就绪的时候,recvform调用进行非阻塞IO时,就会直接返回,但是此时返回的并不是一个完整的IO过程,而是一个错误的返回;
  • 因此该进程或线程后续还需要继续调用recvfrom,轮询检测数据是否就绪,当数据就绪后最后再把数据从内核拷贝到用户空间,这才是一次完整的IO过程。

因此,在进行非阻塞IO时,在没有得到结果之前,虽然这个调用会返回,但后续还需要继续进行轮询检测,因此可以理解成调用还没有返回,而只有当某次轮询检测到数据就绪,并且完成数据拷贝后才认为该调用返回了。

同步通信 VS 同步与互斥

在多进程与多线程里面有同步与互斥的概念,IO中也存在同步的概念,但是这两个同步是完全不相干的。

  • 多进程与多线程下同步是指,在保证数据安全的前提下,让进程或线程按照某种特定的方式访问临界资源,从而有效的避免了饥饿问题,讨论的是线程/进程间的工作关系;
  • 而同步IO指的是进程/线程与操作系统之间的关系,谈论的是进程/线程是否需要主动参与IO过程。

阻塞 VS 非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息、返回值)时的状态。

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

其他高级IO

非阻塞IO,记录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射IO(mmap),这些统称为高级IO。

阻塞IO

我们可以用read函数从标准输入当中读取数据为例:

#include <iostream>
#include <unistd.h>

int main()
{
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "echo# " << buffer << std::endl;
        }
        else
        {
            std::cout << "read error" << std::endl;
        }
    }
    return 0;
}

程序运行以后,我们会发现,如果我们不进行数据的输入操作,程序就会一直阻塞住,根本原因就是底层数据没有就绪,read函数在阻塞式等待。

在这里插入图片描述
当我们输入数据以后,此时read函数就会检测到底层的数据已经就绪了,就会将缓冲区中的数据拷贝到我们的buffer数组中,并且将读取到的数据输出到显示器上面,最后我们就看到了我们输入的字符串。

在这里插入图片描述

非阻塞IO

打开文件时默认都是以阻塞的方式打开的,如果要以非阻塞的方式打开某个文件,需要在使用open函数打开文件时携带O_NONBLOCKO_NDELAY选项,此时就能够以非阻塞的方式打开文件。

在这里插入图片描述
我们一般用统一的方式来进行非阻塞设置,就是fcntl函数。

fcntl函数

fcntl函数的原型如下:

int fcntl(int fd, int cmd, ... /* arg */ );

参数说明:

  • fd:已经打开的文件描述符。
  • cmd:需要进行的操作。
  • :可变参数,传入的cmd值不同,后面追加的参数也不同。

fcntl函数常用的5种功能与其对应的cmd取值如下:

  • 复制一个现有的描述符(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)。

返回值说明:

  • 如果函数调用成功,则返回值取决于具体进行的操作。
  • 如果函数调用失败,则返回-1,同时错误码会被设置。

实现函数SetNoBlock

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

bool SetNoBlock(int fd)
{
    // 在底层获取fd对应文件描述符的标志位
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
        return false;

    // 设置非阻塞IO
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);

    return true;
}

此时我们在以非阻塞轮询方式读取标准输入。

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

bool SetNoBlock(int fd)
{
    // 在底层获取fd对应文件描述符的标志位
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
        return false;

    // 设置非阻塞IO
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);

    return true;
}
int main()
{
    SetNoBlock(0);
    char buffer[1024];
    while (true)
    {
        sleep(1);
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "echo# " << buffer << std::endl;
        }
        else
        {
            std::cout << "read error "
                      << "errno: " << errno << "errstring: " << strerror(errno) << std::endl;
            if (errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "当前0号fd数据未就绪,请再试一次" << std::endl;
                continue;
            }
            else if (errno == EINTR)
            {
                std::cout << "当前IO信号可能被中断,请再试一次" << std::endl;
                continue;
            }
        }
    }
    return 0;
}

需要注意的是,调用read函数以后,如果底层数据没有就绪,就会立马返回一个错误信息,但是此时我们是需要对返回的的错误信息进行甄别的,我们需要知道是真的出错了还是只是底层数据没有就绪。如果错误码的值是EAGAINEWOULDBLOCK,说明本次调用read函数出错是因为底层数据还没有就绪,因此后续还应该继续调用read函数进行轮询检测数据是否就绪,当数据继续时再进行数据的读取。

此外,调用read函数在读取到数据之前可能会被其他信号中断,此时read函数也会以出错的形式返回,此时的错误码会被设置为EINTR,此时应该重新执行read函数进行数据的读取。

程序运行以后,底层数据如果没有就绪,此时read函数就会轮询进行检测:

在这里插入图片描述
一旦我们进行了输入操作,此时read函数就会在轮询检测时检测到,紧接着立马将数据读取到从内核拷贝到我们传入的buffer数组当中,并且将读取到的数据输出到显示器上面,然后继续进行轮询检测。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1258597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023亚马逊云科技re:Invent,与全球合作伙伴探索更多发展可能

一年一度的全球云计算、科技圈的狂欢“Party”又双叒叕要来了&#xff01;2023年11月27日&#xff0c;2023亚马逊云科技re:Invent正式向全球云计算从业者、合作伙伴发出邀请&#xff0c;相聚拉斯维加斯&#xff0c;共同开启一场创新探索之旅&#xff01; 全球合作伙伴相约拉斯维…

ffmpeg开发 环境配置

ffmpeg开发简图 1 下载ffmpeg开发包 https://ffmpeg.org/download.html 包含三个版本&#xff1a;Static、Shared以及Dev Static --- 包含3个应用程序&#xff1a;ffmpeg.exe , ffplay.exe , ffprobe.exe&#xff0c;体积都很大&#xff0c;相关的DLL已经被编译到exe里面去…

【Java】ThreadPoolExecutor类参数简述

ThreadPoolExecutor类继承自AbstractExecutorService类&#xff0c;而AbstractExecutorService实现了ExecutorService接口 ThreadPoolExecutor类是Executor类中重要的实现类 1、ThreadPoolExecutor构造方法参数 在手册中&#xff0c; 一共有四种参数列表不同的构造方法。我们…

【文末送书】程序员如何化解35岁危机?

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

网易云音频数据如何爬取?

在当今数字化时代&#xff0c;音频数据的获取和处理变得越来越重要。本文将详细介绍如何使用Objective-C语言构建音频爬虫程序&#xff0c;以爬取网易云音乐为案例。我们将从Objective-C的基础知识开始&#xff0c;逐步深入到爬取思路分析、构建爬虫框架、完整爬取代码等方面&a…

android trace文件的抓取与查看方法

本地手机抓取trace 解压android trace文件的抓取与查看方法 找到config.pbtx文件&#xff0c;连接手机push进去 # push config.pbtx &#xff0c;/data/local/tmp/为自定义push到的目录 adb push config.pbtx /data/local/tmp/ adb shell # 抓取trace&#xff0c; /data/loc…

Redis原理之五种数据类型笔记

目录 String List Set ZSet ​ Hash String List Set ZSet Hash

CTF图片隐写

1.题目给出的zip文件给出提示如下。 2.用 ARCHPR爆破出密码。 3.解压后发现1.png&#xff0c;为图片隐写。 4.使用010editor打开图片&#xff0c;发现缺少png文件头。 010editor官方下载链接&#xff1a;sweetscape.com/download/010editor/ 5.添加文件头保存。 6.使用图片隐写…

vue3+highlight.js代码高亮插件使用

先安装 npm install highlight.jsmain.js中引入&#xff0c;并注册自定义指令 ..... import hljs from highlight.js window.hljs hljs import highlight.js/styles/atom-one-light.css import highlight.js/lib/common import mitt from mitt .....app.directive(highlight…

HTML5+CSS3+JS小实例:九宫格图片鼠标移入移出方向感知特效

实例:九宫格图片鼠标移入移出方向感知特效 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport&…

鲲鹏测评:电视盒子什么品牌好?双十二口碑电视盒子品牌排行榜

最近我们收到很多网友的私信不知道电视盒子怎么选&#xff0c;想要我们推荐双十二值得入手的电视盒子&#xff0c;本年度我们已经共计测评过18款电视盒子&#xff0c;通过对比测评结果&#xff0c;我们整理了电视盒子品牌排行榜&#xff0c;双十二想买电视盒子的朋友们可不要错…

充电桩绝缘检测原理与示例

1、背景 充电桩绝缘检测是保证电动车充电安全的重要环节&#xff0c;通过对充电桩绝缘检测单租的测量和评估&#xff0c;来判断充电桩是否存在漏电等安全隐患&#xff0c;从而保证用户及周围环境的电器安全。 绝缘电阻&#xff1a;是指在特定的条件下&#xff0c;电气设备与接…

【Python数据结构与算法】--- 递归算法的应用 ---[乌龟走迷宫] |人工智能|探索扫地机器人工作原理

&#x1f308;个人主页: Aileen_0v0 &#x1f525;系列专栏:PYTHON数据结构与算法学习系列专栏&#x1f4ab;"没有罗马,那就自己创造罗马~" 目录 导言 解决过程 1.建立数据结构 2.探索迷宫: 算法思路 递归调用的“基本结束条件” 3.乌龟走迷宫的实现代码: …

【兔子王赠书第9期】ChatGPT进阶:提示工程入门

文章目录 写在前面ChatGPT推荐图书关键点编辑推荐内容简介推荐理由 粉丝福利写在后面 写在前面 人类一直在寻找、制造并使用工具&#xff0c;以扩展我们的能力&#xff0c;适应我们的环境&#xff0c;甚至超越我们的生物限制。现在&#xff0c;我们正站在一个历史性的分水岭之…

Doris-Stream Load(二十六)

Stream load 是一个同步的导入方式&#xff0c;用户通过发送 HTTP 协议发送请求将本地文件或数据流导入到 Doris 中。Stream load 同步执行导入并返回导入结果。用户可直接通过请求的返回体判断本次导入是否成功。 适用场景 Stream load 主要适用于导入本地文件&#xff0c;或…

【VUE】There are multiple modules with names that only differ in casing.

报错 There are multiple modules with names that only differ in casing. This can lead to unexpected behavior when compiling on a filesystem with other case-semantic. Use equal casing. Compare these module identifiers: 图示原因&#xff1a;大小写&#xff0c;有…

如何使用cpolar+Jellyfin自建私人影音平台【内网穿透】

&#x1f3a5; 个人主页&#xff1a;深鱼~ &#x1f525;收录专栏&#xff1a;cpolar &#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpo…

接口自动化测试很难掌握吗?不!一小时学完

一. 什么是接口测试 接口测试是一种软件测试方法&#xff0c;用于验证不同软件组件之间的通信接口是否按预期工作。在接口测试中&#xff0c;测试人员会发送请求并检查接收到的响应&#xff0c;以确保接口在不同场景下都能正常工作。 就工具而言&#xff0c;常见的测试工具有…

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之Linux 进程管理 6》(10)

《Linux操作系统原理分析之Linux 进程管理 6》&#xff08;10&#xff09; 4 Linux 进程管理4.6 Linux 管道4.6.1 管道的概念4.6.2 无名管道1.终端使用2.程序中使用 4.6.2 命名管道1.终端使用2.程序中使用 4 Linux 进程管理 4.6 Linux 管道 4.6.1 管道的概念 1、管道是 linu…

浅谈智能配电房电力运维平台的开发与应用

安科瑞 华楠 摘 要&#xff1a;近年来&#xff0c;我国对电能的需求不断增加&#xff0c;智能电网建设越来越多。为实现对智能配电房设备运行状态的实时监测、态势觉察和态势可视化集中显示&#xff0c;基于智能配电房传感器和配电自动化站所终端单元&#xff08;DTU&#xf…