Linux——高级IO

news2024/11/23 14:52:47

目录

IO

五种IO模型

阻塞式IO

非阻塞式IO

信号驱动IO

多路转接

异步IO

阻塞IO VS 非阻塞IO


IO

        网络的知识我们已经介绍完了,网络通信的本质就是IO,一方要发送数据,还要接收数据,这就是一次IO,所以我们原来说过的IO至少是在一个机器上进行的,虽然向磁盘写入的效率相比于CPU是很慢的,但是网络通信中的IO效率要比这些更低的。

        为了解决IO低效的问题,我们得先知道它为什么低效。以读取为例:

  • 当调用read/recv这样的拷贝函数时,如果缓冲区没有数据,那就会阻塞,说白了就是等。
  • 如果缓冲区中有数据,那就把缓冲区中的数据拷贝到上层。
  • 所以一次IO操作就可以理解为:等待 + 数据拷贝

        当要读取磁盘中的某个文件时,首先要打开该文件,打开文件就是为这个文件创建内核数据结构,此时可能并没有加载到内存,此时这个进程就只能阻塞等待,等操作系统把外设的数据换入到内存中,之后才能进行拷贝。

        现在就可以知道,低效的IO就是:单位时间内,IO接口等待的比重高。所以提高IO效率就是想办法在单位时间内,IO接口等待的比重降低


五种IO模型

先来说一下五种IO模型:

  • 阻塞式IO
  • 非阻塞式IO
  • 信号驱动IO
  • 多路转接
  • 异步IO

        IO模型说完,我们也先来说一下IO的相关概念,还是以读取数据为例:

  1. 是谁读取数据?肯定是一个执行流在读取,也就是进程或者线程
  2. 从哪里读读取?从特定的文件描述符中读取。
  3. 要读取的数据放在哪里?放在了内核的缓冲区中。
  4. 读取后放到哪里?放到了用户缓冲区中。

        之后我们都以读取为例来说明这五个IO模型。

阻塞式IO

        阻塞式IO就是在内核缓冲区的数据准备好之前,系统调用会一直等待,改变进程的状态;数据准备好后,执行拷贝操作。

        阻塞IO是最常见的IO模型,也是使用最多的,因为它简单。

  • 在等待的过程中,操作系统也在不停的检测,检测条件是否就绪,这个条件就是某个文件描述符上是否有数据,如果没有数据,操作系统就会把进程状态设置为非运行状态(例如S状态),并把这个进程放到对应的等待队列中。
  • 当条件就绪时,操作系统识别后,把该进程的状态调整为运行状态(R状态),放入运行队列。

        阻塞式IO一定参与了此次IO,因为它不仅有等待的过程,还有拷贝的过程

非阻塞式IO

        非阻塞式IO就是如果内核缓冲区还未将数据准备好,系统调用会直接返回,并且返回EWOULDBLOCK错误码,也就是在数据未准备好时,操作系统不将进程阻塞,让进程自行处理,此时进程可以先处理其他事,所以通常在检测条件就绪时采用循环的方式,这也叫做非阻塞轮询式

        阻塞IO和非阻塞IO的区别在于,两种方式都进行了等待,但是等的方式不一样,最后两种方式都要从内核缓冲区拷贝到用户缓冲区

信号驱动IO

        信号驱动IO就是当内核将数据准备好的时候,操作系统向目标进程发送SIGIO信号,将信号集中的位图结构的第29号位置由0置1,通知进程进行拷贝操作。

  • 信号的产生是异步的,因为信号在任何时刻都可能产生。
  • 信号驱动IO是同步,因为当底层数据就绪时,执行流需要停下正在做的事情,进行数据拷贝,所以当前执行流仍然需要参与IO过程。

        如何区分一个IO过程是同步还是异步,其实就是看当前进程或线程是否亲自参与IO,如果参与了IO,那就是同步的,反之就是异步。

多路转接

        多路转接也叫多路复用,能够同时检测多个文件描述符的状态,支持多路转接的操作系统都要提供一些接口,比如下面的select,它的工作就是等待,运行向其中添加多个描述符,一次就可以等待多个文件描述符。

  • 一次IO操作需要等待+数据拷贝,但是使用read/recvfrom这样的系统调用接口一次只能等待一个文件描述符,但是这样IO效率太低。
  • 所以系统提供了三组接口,分别是select、poll 和 epoll,他们的工作就是等待
  • 这些多路转接的接口一次性等待多个文件描述符,将等待的时间重叠,当数据就绪时就可以调用recvfrom等函数进行数据拷贝,就不需要再等了。

异步IO

        异步IO会在内核数据拷贝完成后,通知应用程序,这是不同于信号驱动的。

  • 进行异步IO需要调用异步IO接口,比如aio_read,调用时预先给操作系统提供一块缓冲区,调用后会立刻返回。
  • 当数据就绪时不需要告知进程,直接包内核缓冲区的数据拷贝到用户缓冲区,之后通过某种特定的信号告知该进程。

        最后我们再来总结一下,这五种IO模型哪种的效率是最高的呢?前面也说过,只要等待的比重低,那么它的效率一定高,所以一定是多路转接这种方式的效率是最高的。

阻塞IO VS 非阻塞IO

        系统中大部分的接口都是阻塞式的接口,我们之前使用的read或者是recvfrom,这些都是阻塞式的,所以我们下面就来谈一谈非阻塞IO。

        我们之前使用系统调用打开文件时使用的open函数,函数的参数可以设置选项,其中就有打开方式,可以设置O_CREAT、O_RDONLY、O_WRONLY、O_APPEND 和 O_TRUNC等选项,还可以设置O_NONBLOCK 或 O_NDELAY选项,设置为非阻塞打开。

        在套接字编程篇,设置socket也可以设置为非阻塞的,上面两个就是设置为字节流还是数据报。

        所以在进行IO的时候,打开文件的时候就可以设置阻塞或非阻塞,但是我们不这样做,我们使用统一的方式来进行设置,使用 fcntl()函数。

        一个文件的属性中一定有这个文件是否是阻塞的,当使用系统调用时,操作系统要检查struct_file中的属性,如果是阻塞,那么就会直接挂起;如果当前设置非阻塞,操作系统不挂起,那就直接返回。

        我们使用的函数就是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)

        其中可以设置cmd为 F_GETFL 或 F_SETFL 来获取和设置文件读写标志位,这些大写字母就是使用位图方式,最后的可变参数可以使用按位或(|)来传参。

返回值:

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

        我们使用的标准输入本来就是一个阻塞式,当我们调用read,从0号文件描述符中读取数据,如果不输入,那就会阻塞住,原因就是底层数据不就绪,read需要阻塞等待。

        下面我们就实现一个函数,向该函数传入指定的文件描述符,设置为非阻塞状态。

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

// 对指定的文件描述符设置为非阻塞
bool SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL); // 在底层获取当前fd对应的文件读写标志位
    if (fl < 0)
    {
        std::cout << "fcntl error" << std::endl;
        return false;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 要设置一个新的读写标志位,而且还要将非阻塞的选项传入
}

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

int main()
{
    SetNonBlock(0); // 只要设置一次,0号文件描述符就是非阻塞的了
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s - 1] = 0;
            std::cout << "echo# " << buffer << std::endl;
        }
        else
        {
            std::cout << "read \"error\": " << s << std::endl;
        }
        sleep(1);
    }
    return 0;
}

        我们看到的现象就是,如果我们不输入,read的返回值一直是错误,所以非阻塞的时候是以出错的形式返回,告知上层数据没有就绪;如果数据就绪,那就正常读取。

        但是如何甄别是真的出错了,还是没有数据就绪呢,这时就要使用cerrno这个库中的errno,出错了,返回错误码,并且errno被设置,标明出错原因,使用strerror就可以打印错误信息。

int main()
{
    SetNonBlock(0); // 只要设置一次,0号文件描述符就是非阻塞的了
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s - 1] = 0;
            std::cout << "echo# " << buffer << ", erron: " << errno << ", errorstring: " << strerror(errno) << std::endl;
        }
        else
        {
            std::cout << "erron: " << errno << ", errorstring: " << strerror(errno) << std::endl;
        }
        sleep(1);
    }
    return 0;
}

        这里不管成功与否,errno都是11,原因就是如果数据就绪,errno没有被设置,如果想要看到errno为0就在循环开始设置errno为0即可。

        所以errno被设置为11就不能叫出错,所以前面我们说过非阻塞如果数据未准备好返回的是EWOULDBLOCK。

#define	EAGAIN		11	    /* Try again */
#define	EWOULDBLOCK	EAGAIN	/* Operation would block */

        还有一种也不能叫做错误,就是返回的是以EINTR,这个就是当阻塞式读取的时候,进程收到一个信号,此时该进程就要被操作系统唤醒处理信号,处理完信号后就不是挂起状态了,因为没有再调用read,所以这个也要处理一下。

int main()
{
    SetNonBlock(0); // 只要设置一次,0号文件描述符就是非阻塞的了
    char buffer[1024];
    while (true)
    {
        sleep(1);
        errno = 0;
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s - 1] = 0;
            //std::cout << "echo# " << buffer << std::endl;
            std::cout << "echo# " << buffer << ", erron: " << errno << ", errorstring: " << strerror(errno) << std::endl;
        }
        else
        {
            if (errno == EWOULDBLOCK || errno == EAGAIN)
            {
                std::cout << "当前0号fd数据没有就绪, 请再试一次" << std::endl;
                continue;
            }
            else if (errno == EINTR)
            {
                std::cout << "当前IO可能被信号中断, 请再试一次" << std::endl;
                continue;
            }
            else 
            {
                // 差错处理
            }
        }
    }
    return 0;
}

        所以底层没有数据就绪的时候就是非阻塞式,如果有数据就依次读取。

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

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

相关文章

解决VSCode中导入PyTorch时报错的HTTP错误与Channel冲突

问题描述与解释 在Anaconda中成功安装PyTorch&#xff0c;并进行了验证&#xff1a; (base) C:\Users\Hui>conda activate pytorch(pytorch) C:\Users\\Hui>python Python 3.8.19 (default, Mar 20 2024, 19:55:45) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on …

安装windows服务,细节

1、选中服务代码&#xff0c;右键添加安装程序。 2、安装程序的权限一定改为local,否则安装时会提示null错误。 3、安装服务 InstallUtil D:\vs2022work\testFW\testFW\bin\Debug\testFW.exe p:InstallUtil 需要新建环境变量才能直接使用&#xff08;找到InstallUtil 工具所在…

沃尔核材:价值重估

当英伟达这个曾经的GPU行业龙头&#xff0c;伴随AI的发展成为AI芯片架构的供应商时&#xff0c;他就跳出了原本行业的竞争格局&#xff0c;曾经还能与之一战的超威半导体被远远甩在身后&#xff0c;成为宇宙第一公司。 这说的就是一家公司价值的重估。今天给大家聊的也是这样一…

【C++】相机标定源码笔记- RGB 相机与 ToF 深度传感器校准类

类的设计目标是为了实现 RGB 相机与 ToF 深度传感器之间的高精度校准&#xff0c;从而使两种类型的数据能够在同一个坐标框架内被整合使用。这在很多场景下都是非常有用的&#xff0c;比如在3D重建、增强现实、机器人导航等应用中&#xff0c;能够提供更丰富的场景信息。 -----…

学习笔记(linux高级编程)11

进程间通信 》信号通信 应用&#xff1a;异步通信。 中断&#xff0c;&#xff0c; 1~64&#xff1b;32应用编程。 如何响应&#xff1a; Term Default action is to terminate the process. Ign Default action is to ignore the signal. wait Core Default action is …

Eclipse运行main函数报 launch error

右键run as java application&#xff0c;运行main函数的时候报launch error 解决方式&#xff1a;文件右键run configurations 旧的是Project JRE&#xff0c;改成下图这个样子

视频去水印在线工具,视频去水印网站在线使用

在数字媒体时代&#xff0c;视频创作已成为越来越多人热衷的活动。然而&#xff0c;当我们想要对下载的视频进行二次创作或分享时&#xff0c;视频上的水印常常成为一个头疼的问题。本文将为您介绍几种免费且高效的去水印方法&#xff0c;让您在视频制作中游刃有余&#xff01;…

Studying-代码随想录训练营day28| 122.买卖股票的最佳时机II、55. 跳跃游戏、45.跳跃游戏II、1005.K次取反后最大化的数组和

第28天&#xff0c;贪心算法part02&#xff0c;题目难度在增加&#xff0c;要锻炼贪心思维能力(ง •_•)ง&#xff0c;编程语言&#xff1a;C 目录 122.买卖股票的最佳时机II 55. 跳跃游戏 45.跳跃游戏II 1005.K次取反后最大化的数组和 总结&#xff1a; 122.买卖股票的…

npm install puppeteer 报错 npm ERR! PUPPETEER_DOWNLOAD_HOST is deprecated解决办法

npm install puppeteer 报错如下&#xff1a; npm ERR! PUPPETEER_DOWNLOAD_HOST is deprecated. Use PUPPETEER_DOWNLOAD_BASE_URL instead. npm ERR! Error: ERROR: Failed to set up Chrome v126.0.6478.126! Set "PUPPETEER_SKIP_DOWNLOAD" env variable to sk…

绝区零国际服下载 一键下载绝区零国际服教程

绝区零是一款米哈游倾情打造的全新都市幻想动作角色扮演游戏。在游戏中&#xff0c;我们将扮演一名绳匠&#xff0c;这是为出于各种原因需要进入危险空洞的人提供指引的专业人士。您将与独特的角色一起踏上冒险之旅&#xff0c;携手探索空洞&#xff0c;对战强大敌人&#xff0…

每周题解:最大半连通子图

题目链接 最大半连通子图 题目描述 一个有向图 G ( V , E ) G\left(V,E\right) G(V,E) 称为半连通的 (Semi-Connected)&#xff0c;如果满足&#xff1a; ∀ u , v ∈ V \forall u,v\in V ∀u,v∈V&#xff0c;满足 u → v u\to v u→v 或 v → u v\to u v→u&#xff0…

番外篇 | 斯坦福提出即插即用二阶优化器Sophia :相比Adam实现2倍加速,显著节省大语言模型训练成本

前言:Hello大家好,我是小哥谈。大模型的预训练成本巨大,优化算法的改进可以加快模型的训练时间并减少训练开销。目前大模型的训练优化器基本上都采用Adam及其变体,并且Adam的应用已经有9个年头了,在模型优化方面相当于霸主的地位。但是能否够在优化器方面提高模型预训练效…

【每日刷题】Day79

【每日刷题】Day79 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 1619. 删除某些元素后的数组均值 - 力扣&#xff08;LeetCode&#xff09; 2. 1365. 有多少小于当前…

python3.8安装详细教程

python3.8下载及安装详细教程 Python 3.8 是一个重要的Python版本&#xff0c;它引入了一系列新功能和改进。以下是对Python 3.8的详细概述&#xff0c;包括其关键特性、安装方法以及版本状态等信息。 Python 3.8的关键特性 海象运算符&#xff08;Walrus Operator&#xff09…

保障性住房数字化运营平台助力租赁住房智能化管理

保障性住房能提供合理的价格、良好的配套设施和优越的租住体验&#xff0c;租赁将不是问题。 一、发力租赁型保障房建设 随着城镇化进程的加速和流动人口规模的扩大&#xff0c;进城务工人员、新就业大学生等新市民、青年人的住房困难问题日益凸显。加快发展租赁型保障性住房…

深圳技术大学oj C : 生成r子集

Description 输出给定序列按字典序的 &#xfffd; 组合&#xff0c;按照所有 &#xfffd; 个元素出现与否的 01 标记串 &#xfffd;&#xfffd;&#xfffd;&#xfffd;−1,...,&#xfffd;1 的字典序输出. 此处01串的字典序指&#xff1a;先输入的数字对应低位&#x…

使用高斯混合模型识别餐厅热点

使用 GMM 识别加拿大多伦多的直观餐厅集群&#xff08;附 Python 代码&#xff09; 聚类算法&#xff08;例如 GMM&#xff09;是一种有用的工具&#xff0c;可帮助识别数据中的模式。它们使我们能够识别数据集中的子组&#xff0c;从而提高你的理解或增强预测模型。在本文中&a…

中国国产AI芯片的崛起

一、CUDA的垄断 当讨论半导体行业面临的挑战时&#xff0c;你首先想到的是什么&#xff1f;光刻机&#xff1f;3纳米或者5纳米技术&#xff1f;我们无法生产的完美方形芯片&#xff1f;是的&#xff0c;但也不完全是。 人们经常把半导体芯片归类为硬件产业&#xff0c;但实际上…

mmcv安装失败及解决方案

假如想安装的版本是mmcv1.4.0, 但是pip install mmcv1.4.0总是失败&#xff0c;若是直接pip install mmcv会安装成功&#xff0c;但是安装的就是最新版本&#xff0c;后面代码跑起来还会报错&#xff0c;怎么办呢&#xff1f; 接下来分享一个mmcv指定版本安装的方式。 网页&a…

PCL小笔记

一、常用概念 1&#xff0c;过滤器Filters 消除噪音 2&#xff0c;特征Features 集合点属性&#xff1a;曲面的曲率估计和查询点的法线 通过k-neighborhood计算得到这两个属性作为特征 查找方法&#xff1a;KD-tress、八叉树等 3&#xff0c;关键点Keypoints 可以利用明确标…