linux 高级 I/O

news2024/11/26 20:42:50

高级 I/O

    • 1. 阻塞 I/O 与非阻塞 I/O
    • 2. 阻塞 I/O 所带来的困境
    • 3. 何为 I/O 多路复用以及原理
      • select()函数介绍
      • poll()函数介绍
      • 总结
    • 4. 何为异步 I/O 以及原理
    • 5. 存储映射 I/O
    • 7. 文件加锁

1. 阻塞 I/O 与非阻塞 I/O

这里举个例子,譬如对于某些文件类型(读管道文件、网络设备文件和字符设备文件),当对文件进行读操作时,如果数据未准备好、文件当前无数据可读,那么读操作可能会使调用者阻塞,直到有数据可读时才会被唤醒,这就是阻塞式 I/O 常见的一种表现;如果是非阻塞式 I/O,即使没有数据可读,也不会被阻塞、而是会立马返回错误!

普通文件的读写操作是不会阻塞的,不管读写多少个字节数据,read()或 write()一定会在有限的时间内返回,所以普通文件一定是以非阻塞的方式进行 I/O 操作,这是普通文件本质上决定的;但是对于某些文件类型,譬如上面所介绍的管道文件、设备文件等,它们既可以使用阻塞式 I/O 操作,也可以使用非阻塞式 I/O进行操作。

当对文件进行读取操作时,如果文件当前无数据可读,那么阻塞式 I/O 会将调用者应用程序挂起、进入休眠阻塞状态,直到有数据可读时才会解除阻塞;而对于非阻塞 I/O,应用程序不会被挂起,而是会立即返回,它要么一直轮训等待,直到数据可读,要么直接放弃!

所以阻塞式 I/O 的优点在于能够提升 CPU 的处理效率,当自身条件不满足时,进入阻塞状态,交出 CPU资源,将 CPU 资源让给别人使用;而非阻塞式则是抓紧利用 CPU 资源,譬如不断地去轮训,这样就会导致该程序占用了非常高的 CPU 使用率!

2. 阻塞 I/O 所带来的困境

 /* 读鼠标 */
 memset(buf, 0, sizeof(buf));
 ret = read(fd, buf, sizeof(buf));
 printf("鼠标: 成功读取<%d>个字节数据\n", ret);
 /* 读键盘 */
 memset(buf, 0, sizeof(buf));
 ret = read(0, buf, sizeof(buf));
 printf("键盘: 成功读取<%d>个字节数据\n", ret);

上述程序中先读了鼠标,在接着读键盘,所以由此可知,在实际测试当中,需要先动鼠标在按键盘(按下键盘上的按键、按完之后按下回车),这样才能既成功读取鼠标、又成功读取键盘,程序才能够顺利运行结束。因为 read 此时是阻塞式读取,先读取了鼠标,没有数据可读将会一直被阻塞,后面的读取键盘将得不到执行。这就是阻塞式 I/O 的一个困境。

我们可以使用非阻塞式 I/O 解决它。但是非阻塞的程序CPU占用太高不推荐,后面的方法可以解决该问题。

3. 何为 I/O 多路复用以及原理

I/O 多路复用(IO multiplexing)它通过一种机制,可以监视多个文件描述符,一旦某个文件描述符(也就是某个文件)可以执行 I/O 操作时,能够通知应用程序进行相应的读写操作。I/O 多路复用技术是为了解决:在并发式 I/O 场景中进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的I/O 系统调用。

select()函数介绍

系统调用 select()可用于执行 I/O 多路复用操作,调用 select()会一直阻塞,直到某一个或多个文件描述符成为就绪态(可以读或写)。

/* 同时读取键盘和鼠标 */
while (loops--)
{
    FD_ZERO(&rdfds);
    FD_SET(0, &rdfds);  // 添加键盘
    FD_SET(fd, &rdfds); // 添加鼠标
    ret = select(fd + 1, &rdfds, NULL, NULL, NULL);
    if (0 > ret)
    {
        perror("select error");
        goto out;
    }
    else if (0 == ret)
    {
        fprintf(stderr, "select timeout.\n");
        continue;
    }
    /* 检查键盘是否为就绪态 */
    if (FD_ISSET(0, &rdfds))
    {
        ret = read(0, buf, sizeof(buf));
        if (0 < ret)
            printf("键盘: 成功读取<%d>个字节数据\n", ret);
    }
    /* 检查鼠标是否为就绪态 */
    if (FD_ISSET(fd, &rdfds))
    {
        ret = read(fd, buf, sizeof(buf));
        if (0 < ret)
            printf("鼠标: 成功读取<%d>个字节数据\n", ret);
    }
}

  1. 有一个或多个文件描述符就绪
    返回值:返回准备好的文件描述符的数量(即就绪的文件描述符数量)。
    就绪条件:任何一个文件描述符在指定的 readfds、writefds 或 exceptfds 中变为可读、可写或出现异常。
  2. 超时
    返回值:返回 0。
    超时条件:如果设置了 timeout 参数并且在指定的时间内没有任何文件描述符就绪,select() 会在超时后返回 0。
  3. 出错
    返回值:返回 -1。
    错误条件:当出现错误(如无效的参数、调用失败等)时,select() 会返回 -1,并设置 errno 来指示错误类型。常见的错误包括:
    EBADF:传入的文件描述符无效。
    EINTR:调用被信号中断。

poll()函数介绍

poll()函数返回值含义与 select()函数的返回值是一样的,有如下几种情况:
⚫ 返回-1 表示有错误发生,并且会设置 errno。
⚫ 返回 0 表示该调用在任意一个文件描述符成为就绪态之前就超时了。
⚫ 返回一个正整数表示有一个或多个文件描述符处于就绪态了,返回值表示 fds 数组中返回的 revents变量不为 0 的 struct pollfd 对象的数量。

总结

在使用 select()或 poll()时需要注意一个问题,当监测到某一个或多个文件描述符成为就绪态(可以读或写)时,需要执行相应的 I/O 操作,以清除该状态,否则该状态将会一直存在

4. 何为异步 I/O 以及原理

在 I/O 多路复用中,进程通过系统调用 select()或 poll()来主动查询文件描述符上是否可以执行 I/O 操作。而在异步 I/O 中,当文件描述符上可以执行 I/O 操作时,进程可以请求内核为自己发送一个信号。之后进程就可以执行任何其它的任务直到文件描述符可以执行 I/O 操作为止,此时内核会发送信号给进程。所以要使用异步 I/O,还得结合前面所学习的信号相关的内容,所以异步 I/O 通常也称为信号驱动 I/O。

要使用异步 I/O,程序需要按照如下步骤来执行:

  1. 通过指定 O_NONBLOCK 标志使能非阻塞 I/O。
  2. 通过指定 O_ASYNC 标志使能异步 I/O。
  3. 设置异步 I/O 事件的接收进程。也就是当文件描述符上可执行 I/O 操作时会发送信号通知该进程,通常将调用进程设置为异步 I/O 事件的接收进程。
  4. 为内核发送的通知信号注册一个信号处理函数。默认情况下,异步 I/O 的通知信号是 SIGIO,所以内核会给进程发送信号 SIGIO。在 8.2 小节中简单地提到过该信号。
  5. 以上步骤完成之后,进程就可以执行其它任务了,当 I/O 操作就绪时,内核会向进程发送一个 SIGIO信号,当进程接收到信号时,会执行预先注册好的信号处理函数,我们就可以在信号处理函数中进行 I/O 操作。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#define MOUSE "/dev/input/event3"
static int fd;
static void sigio_handler(int sig)
{
    static int loops = 5;
    char buf[100] = {0};
    int ret;
    if (SIGIO != sig)
        return;
    ret = read(fd, buf, sizeof(buf));
    if (0 < ret)
        printf("鼠标: 成功读取<%d>个字节数据\n", ret);
    loops--;
    if (0 >= loops)
    {
        close(fd);
        exit(0);
    }
}
int main(void)
{
    int flag;
    /* 打开鼠标设备文件<使能非阻塞 I/O> */
    fd = open(MOUSE, O_RDONLY | O_NONBLOCK);
    if (-1 == fd)
    {
        perror("open error");
        exit(-1);
    }
    /* 使能异步 I/O */
    flag = fcntl(fd, F_GETFL);
    flag |= O_ASYNC;
    fcntl(fd, F_SETFL, flag);
    /* 设置异步 I/O 的所有者 */
    fcntl(fd, F_SETOWN, getpid());
    /* 为 SIGIO 信号注册信号处理函数 */
    signal(SIGIO, sigio_handler);
    for (;;)
        sleep(1);
}

优化异步 I/O

⚫ 默认的异步 I/O 通知信号 SIGIO 是非排队信号。SIGIO 信号是标准信号(非实时信号、不可靠信号),所以它不支持信号排队机制,譬如当前正在执行 SIGIO 信号的处理函数,此时内核又发送多次 SIGIO 信号给进程,这些信号将会被阻塞,只有当信号处理函数执行完毕之后才会传递给进程,并且只能传递一次,而其它后续的信号都会丢失。
⚫ 无法得知文件描述符发生了什么事件。在示例代码的信号处理函数 sigio_handler()中,直接调用了 read()函数读取鼠标,而并未判断文件描述符是否处于可读就绪态,事实上,这种异步 I/O 方式并未告知应用程序文件描述符上发生了什么事件,是可读取还是可写入亦或者发生异常等。

1 使用实时信号替换默认信号 SIGIO

fcntl(fd, F_SETSIG, SIGRTMIN);

2 使用 sigaction()函数注册信号处理函数
在应用程序当中需要为实时信号注册信号处理函数,使用 sigaction 函数进行注册,并为 sa_flags 参数指定 SA_SIGINFO,表示使用 sa_sigaction 指向的函数作为信号处理函数,而不使用 sa_handler 指向的函数。

5. 存储映射 I/O

存储映射 I/O(memory-mapped I/O)是一种基于内存区域的高级 I/O 操作,它能将一个文件映射到进程地址空间中的一块内存区域中,当从这段内存中读数据时,就相当于读文件中的数据(对文件进行 read 操作),将数据写入这段内存时,则相当于将数据直接写入文件中(对文件进行 write 操作)。这样就可以在不使用基本 I/O 操作函数 read()和 write()的情况下执行 I/O 操作。

为了实现存储映射 I/O 这一功能,我们需要告诉内核将一个给定的文件映射到进程地址空间中的一块内存区域中,这由系统调用 mmap()来实现。

在这里插入图片描述
mmap()函数
对于 mmap()函数,参数 addr 和 offset 在不为 NULL 和 0 的情况下,addr 和 offset 的值通常被要求是系统页大小的整数倍,可通过 sysconf()函数获取页大小。

在使用 mmap() 创建内存映射时,与映射区相关的两个信号是 SIGSEGV 和 SIGBUS。这两个信号通常会在特定条件下触发,表示访问映射区域时发生了错误。下面是对这两个信号的详细解释:

  1. SIGSEGV (Segmentation Fault)
    触发条件:
    当进程尝试写入一个只读的映射区域时,会产生 SIGSEGV 信号。例如,如果在调用 mmap() 时指定了 PROT_READ(只读保护)而不包括 PROT_WRITE(可写保护),然后进程试图向该区域写入数据,就会引发此信号。

  2. SIGBUS (Bus Error)
    触发条件:
    当进程试图访问一个不存在的内存地址时,会触发 SIGBUS 信号。例如,假设你使用 mmap() 映射一个文件,并且在映射后,另一个进程通过 ftruncate() 函数截断了该文件。如果进程随后尝试访问映射区域中与被截断部分对应的地址,就会引发 SIGBUS 信号。
    munmap()
    通过 open()打开文件,需要使用 close()将将其关闭;同理,通过 mmap()将文件映射到进程地址空间中
    的一块内存区域中,当不再需要时,必须解除映射,使用 munmap()解除映射关系。

mprotect()函数
使用系统调用 mprotect()可以更改一个现有映射区的保护要求

msync()函数
对于存储 I/O 来说亦是如此,写入到文件映射区中的数据也不会立马刷新至磁盘设备中,而是会在我们将数据写入到映射区之后的某个时刻将映射区中的数据写入磁盘中。所以会导致映射区中的内容与磁盘文件中的内容不同步。我们可以调用 msync()函数将映射区中的数据刷写、更新至磁盘文件中(同步操作),系统调用 msync()类似于 fsync()函数,不过 msync()作用于映射区。

普通 I/O 与存储映射 I/O 比较
在这里插入图片描述
普通 I/O 实现文件复制示例图
在这里插入图片描述
存储映射 I/O 实现文件复制

7. 文件加锁

对于有些应用程序,进程有时需要确保只有它自己能够对某一文件进行 I/O 操作,在这段时间内不允许其它进程对该文件进行 I/O 操作。为了向进程提供这种功能,Linux 系统提供了文件锁机制。

前面学习过互斥锁、自旋锁以及读写锁,文件锁与这些锁一样,都是内核提供的锁机制,锁机制实现用于对共享资源的访问进行保护;只不过互斥锁、自旋锁、读写锁与文件锁的应用场景不一样,互斥锁、自旋锁、读写锁主要用在多线程环境下,对共享资源的访问进行保护,做到线程同步。

而文件锁,顾名思义是一种应用于文件的锁机制,当多个进程同时操作同一文件时,我们怎么保证文件数据的正确性,linux 通常采用的方法是对文件上锁,来避免多个进程同时操作同一文件时产生竞争状态。譬如进程对文件进行 I/O 操作时,首先对文件进行上锁,将其锁住,然后再进行读写操作;只要进程没有对文件进行解锁,那么其它的进程将无法对其进行操作;这样就可以保证,文件被锁住期间,只有它(该进程)可以对其进行读写操作。

文件锁的分类
文件锁可以分为建议性锁强制性锁两种。

flock()函数加锁
系统调用 flock(),使用该函数可以对文件加锁或者解锁,但是 flock()函数只能产生建议性锁

fcntl()函数加锁
fcntl()函数在前面章节内容中已经多次用到了,它是一个多功能文件描述符管理工具箱,通过配合不同的 cmd 操作命令来实现不同的功能。

⚫ flock()仅支持对整个文件进行加锁/解锁;而 fcntl()可以对文件的某个区域(某部分内容)进行加锁
/解锁,可以精确到某一个字节数据。
⚫ flock()仅支持建议性锁类型;而 fcntl()可支持建议性锁和强制性锁两种类型。

lockf()函数加锁
lockf()函数是一个库函数,其内部是基于 fcntl()来实现的,所以 lockf()是对 fcntl 锁的一种封装,具体的使用方法这里便不再介绍。

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

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

相关文章

centos7配置keepalive+lvs

拓扑图 用户访问www.abc.com解析到10.4.7.8&#xff0c;防火墙做DNAT将访问10.4.7.8:80的请求转换到VIP 172.16.10.7:80&#xff0c;负载均衡器再将请求转发到后端web服务器。 实验环境 VIP&#xff1a;负载均衡服务器的虚拟ip地址 LB &#xff1a;负载均衡服务器 realserv…

【亚马逊云科技】Amazon Bedrock搭建AI服务

前言 大模型应用发展迅速&#xff0c;部署一套AI应用的需求也越来越多&#xff0c;从头部署花费时间太长&#xff0c;然而亚马逊科技全托管式生成式 AI 服务 Amazon Bedrock&#xff0c;Amazon Bedrock 简化了从基础模型到生成式AI应用构建的复杂流程&#xff0c;为客户铺设了…

「C/C++」C/C++ 之 判断语句

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

git快速合并代码dev->master

需求&#xff1a; 日常开发都是在dev分支进行开发&#xff0c;但是dev代码开发测试完成后&#xff0c;需要将dev代码合到master主分支上 开始合并代码&#xff1a; 一、场景 一个代码仓库&#xff0c;包含两个分支&#xff0c;一个是master&#xff0c;另一个是dev&#xff1b…

uniapp使用uni-push模拟推送

uniapp使用uni-push模拟推送 第一步先去uniapp开发者中心添加开通uni-push功能 这里的Android 应用签名可以先用测试的官网有,可以先用这个测试 官方测试链接文档地址 在项目中的配置文件勾选 组件中使用 如果要实时可以去做全局ws //消息推送模版uni.createPushMessage(…

前沿技术与未来发展第一节:C++与机器学习

第六章&#xff1a;前沿技术与未来发展 第一节&#xff1a;C与机器学习 1. C在机器学习中的应用场景 C在机器学习中的应用优势主要体现在高效的内存管理、强大的计算能力和接近底层硬件的灵活性等方面。以下是 C 在机器学习领域的几个主要应用场景&#xff1a; 1.1 深度学习…

Java程序设计:spring boot(10)——单元测试

1 pom.xml 测试依赖添加 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId> </dependency> 2 Service业务方法测试 以 UserService 为例&#xff0c;src/test/java ⽬录下添…

解决edge浏览器无法同步问题

有时候电脑没带&#xff0c;但是浏览器没有同步很烦恼。chrome浏览器的同步很及时在多设备之间能很好使用。但是edge浏览器同步没反应。 在这里插入图片描述 解决方法&#xff1a; 一、进入edge浏览器点击图像会显示未同步。点击“管理个人资料”&#xff0c;进入后点击同步&…

21.java异常:关于java异常的学习笔记。 异常的分类,异常体系结构,异常处理机制

关于java异常的学习笔记 什么是异常异常的分类异常体系结构ErrorException异常处理机制IDEA中很重要的快捷键什么是异常 实际工作中,遇到的情况不可能是非常完美的。比如:你写的某个模块,用户输入不一定符合你的要求、你的程序要打开某个文件,这个文件可能不存在或者文件格…

记录一次mmpretrain训练数据并转onnx推理

目录 1.前言 2.代码 3.数据形态【分类用】 4.配置文件 5.训练 6.测试-分析-混淆矩阵等等&#xff0c;测试图片效果等 7.导出onnx 8.onnx推理 9.docker环境简单补充 1.前言 好久没有做图像分类了&#xff0c;于是想用商汤的mmclassification快速搞一波&#xff0c;发现已…

gb28181-sip注册流程

gb28181-sip注册流程 当客户端第一次接入时&#xff0c;客户端将持续向Server端发送REGISTER消息&#xff0c;直到Server端回复"200 OK"后结束 它的注册流程如下图&#xff1a; 注册流程&#xff1a; 1 . SIP代理向SIP服务器发送Register请求&#xff1a; 第1行表…

玄机-应急响应- Linux入侵排查

一、web目录存在木马&#xff0c;请找到木马的密码提交 到web目录进行搜索 find ./ type f -name "*.php" | xargs grep "eval(" 发现有三个可疑文件 1.php看到密码 1 flag{1} 二、服务器疑似存在不死马&#xff0c;请找到不死马的密码提交 被md5加密的…

如何有效提升MySQL大表分页查询效率(本文以一张900万条数据体量的表为例进行详细解读)

文章目录 1、提出问题1.1 问题测试 2、解决问题&#xff08;三种方案&#xff09;2.1、方案一&#xff1a;查询的时候&#xff0c;只返回主键 ID2.2、方案二&#xff1a;查询的时候&#xff0c;通过主键 ID 过滤2.3、方案三&#xff1a;采用 elasticSearch 作为搜索引擎 3、总结…

基于卷积神经网络的苹果病害识别与防治系统,resnet50,mobilenet模型【pytorch框架+python源码】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; 苹果病害识别与防治系统&#xff0c;卷积神经网络&#xff0c;resnet50&#xff0c;mobilenet【pytorch框架&#xff0c;python源码】_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积…

Java多线程编程基础

目录 编写第一个多线程程序 1. 方式一 : 继承Thread类, 重写run方法 2. 方式二: 实现Runnable接口, 重写run方法 3. 方式三: 使用Lambda表达式 [匿名内部类] [Lambda表达式] 在上个文章中, 我们了解了进程和线程的相关概念. 那么, 在Java中, 我们如何进行多线程编程呢? …

ffmpeg视频滤镜:网格-drawgrid

滤镜介绍 drawgrid 官网链接 》 FFmpeg Filters Documentation drawgrid会在视频上画一个网格。 滤镜使用 参数 x <string> ..FV.....T. set horizontal offset (default "0")y <string> ..FV.....T. set…

【AIGC】2024-arXiv-Lumiere:视频生成的时空扩散模型

2024-arXiv-Lumiere: A Space-Time Diffusion Model for Video Generation Lumiere&#xff1a;视频生成的时空扩散模型摘要1. 引言2. 相关工作3. Lumiere3.1 时空 U-Net (STUnet)3.2 空间超分辨率的多重扩散 4. 应用4.1 风格化生成4.2 条件生成 5. 评估和比较5.1 定性评估5.2 …

没有对象来和我手撕红黑树吧

1. 红黑树的介绍 红黑树也是一种自平衡的二叉搜索树&#xff0c;在每一个节点增加了一个存储位来表示节点的颜色&#xff0c;可以是红色也可以是黑色&#xff0c;通过约束颜色来维持树的平衡&#xff0c;具有以下的性质&#xff1a; 每个节点不是红色就是黑色根节点为黑色如果…

【网络面试篇】TCP与UDP类

目录 一、综述 1. TCP与UDP的概念 2. 特点 3. 区别 4. 对应的使用场景 二、补充 1. 基础概念 &#xff08;1&#xff09;面向连接 &#xff08;2&#xff09;可靠的 &#xff08;3&#xff09;字节流 2. 相关问题 &#xff08;1&#xff09;TCP 和 UDP 可以同时绑定…

linux:回车换行+进度条+git理解与使用以及如何解决免密码push问题

目录 特殊符号 Linux小程序---进度条 1.\n和\r的理解 2.缓冲区 3.设计简单的倒计时 4.设计简单的进度条 git-版本控制器 1.理解什么是版本控制器? 2.git的使用 3.git的其他说明 总结上传过程 特殊符号 1.(取消显化) 的作用:执行指令,但指令本身不会显化; 举个例子:我…