【Linux高级 I/O(4)】异步 IO实例及其优化(全文代码)

news2024/7/6 19:48:58

  异步 I/O     

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

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

  • 通过指定 O_NONBLOCK 标志使能非阻塞 I/O。
  • 通过指定 O_ASYNC 标志使能异步 I/O。
  • 设置异步 I/O 事件的接收进程。也就是当文件描述符上可执行 I/O 操作时会发送信号通知该进程, 通常将调用进程设置为异步 I/O 事件的接收进程。
  • 为内核发送的通知信号注册一个信号处理函数。默认情况下,异步 I/O 的通知信号是 SIGIO,所以内核会给进程发送信号 SIGIO。
  • 以上步骤完成之后,进程就可以执行其它任务了,当 I/O 操作就绪时,内核会向进程发送一个 SIGIO 信号,当进程接收到信号时,会执行预先注册好的信号处理函数,我们就可以在信号处理函数中进行 I/O 操作。

O_ASYNC 标志

        O_ASYNC 标志可用于使能文件描述符的异步 I/O 事件,当文件描述符可执行 I/O 操作时,内核会向异步 I/O 事件的接收进程发送 SIGIO 信号(默认情况下),该标志主要用于异步 I/O。 需要注意的是:在调用 open()时无法通过指定 O_ASYNC 标志来使能异步 I/O,但可以使用 fcntl()函数 添加 O_ASYNC 标志使能异步 I/O,譬如:

int flag;

flag = fcntl(0, F_GETFL); //先获取原来的 flag
flag |= O_ASYNC; //将 O_ASYNC 标志添加到 flag
fcntl(fd, F_SETFL, flag); //重新设置 flag

        设置异步 I/O 事件的接收进程

        为文件描述符设置异步 I/O 事件的接收进程,也就是设置异步 I/O 的所有者。同样也是通过 fcntl()函数进行设置,操作命令 cmd 设置为 F_SETOWN,第三个参数传入接收进程的进程 ID(PID),通常将调用进程的 PID 传入,譬如:

fcntl(fd, F_SETOWN, getpid());

        注册 SIGIO 信号的处理函数

        通过 signal()或 sigaction()函数为 SIGIO 信号注册一个信号处理函数,当进程接收到内核发送过来的 SIGIO 信号时,会执行该处理函数,所以我们应该在处理函数当中执行相应的 I/O 操作。

        使用示例

        代码演示了以异步 I/O 方式读取鼠标,当进程接收到 SIGIO 信号时,执行信号处理函数 sigio_handler(),在该函数中调用 read()读取鼠标数据。

#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 >= ret){
        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 的原理以及使用方法,在一个需要同时检查大量文件描述符(譬如数千个)的应用程序中,例如某种类型的网络服务端程序,与 select()和 poll()相比,异步 I/O 能够提供显著的性能优势。 之所以如此,原因在于:对于异步 I/O,内核可以“记住”要检查的文件描述符,且仅当这些文件描述符上 可执行 I/O 操作时,内核才会向应用程序发送信号。

        而对于 select()或 poll()函数来说,内部实现原理其实是通过轮训的方式来检查多个文件描述符是否可执行 I/O 操作,所以,当需要检查的文件描述符数量较多时,随之也将会消耗大量的 CPU 资源来实现轮训检查操作。当需要检查的文件描述符并不是很多时,使用 select()或 poll()是一种非常不错的方案!

        Tips:当需要检查大量文件描述符时,可以使用 epoll 解决 select()或 poll()性能低的问题。在性能表现上,epoll 与异步 I/O 方式相似, 但是 epoll 有一些胜过异步 I/O 的优点。

        不管是异步 I/O、还是 epoll,在需要检查大量文件描述符的应用程序当中,在这种情况下,它们的性能相比于 select()或 poll()有着显著的优势!

        本小节将对上一小节所讲述的异步 I/O 进行优化,既然要对其进行优化,那必然存在着一些缺陷,如下所示:

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

        所以本小节我们将会针对以上列举出的两个缺陷进行优化。

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

        SIGIO 作为异步 I/O 通知的默认信号,是一个非实时信号,我们可以设置不使用默认信号,指定一个实时信号作为异步 I/O 通知信号,如何指定呢?同样也是使用 fcntl()函数进行设置,调用函数时将操作命令 cmd 参数设置为 F_SETSIG,第三个参数 arg 指定一个实时信号编号即可,表示将该信号作为异步 I/O 通知信号,譬如:

fcntl(fd, F_SETSIG, SIGRTMIN);

使用 sigaction()函数注册信号处理函数

        在应用程序当中需要为实时信号注册信号处理函数,使用 sigaction 函数进行注册,并为 sa_flags 参数指定 SA_SIGINFO,表示使用 sa_sigaction 指向的函数作为信号处理函数,而不使用 sa_handler 指向的函数。 因为 sa_sigaction 指向的函数作为信号处理函数提供了更多的参数,可以获取到更多信息,函数定义参考 Linux信号基础(3)——更泛化的使用方式 中示例代码关于 struct sigaction 结构体的描述。

        函数参数中包括一个 siginfo_t 指针,指向 siginfo_t 类型对象,当触发信号时该对象由内核构建。siginfo_t 结构体中提供了很多信息,我们可以在信号处理函数中使用这些信息,就对于异步 I/O 事件而言,传递给信号处理函数的 siginfo_t 结构体中与之相关的字段如下:

  • si_signo:引发处理函数被调用的信号。这个值与信号处理函数的第一个参数一致。
  • si_fd:表示发生异步 I/O 事件的文件描述符;
  • si_code:表示文件描述符 si_fd 发生了什么事件,读就绪态、写就绪态或者是异常事件等。该字段 中可能出现的值以及它们对应的描述信息参见下表。
  • si_band:是一个位掩码,其中包含的值与系统调用 poll()中返回的 revents 字段中的值相同。

        如表所示,si_code 中可能出现的值与 si_band 中的位掩码有着一一对应关系。

         所以,由此可知,可以在信号处理函数中通过对比 siginfo_t 结构体的 si_code 变量来检查文件描述符发生了什么事件,以采取相应的 I/O 操作。

使用示例

        通过前文的学习,我们已经知道了如何针对本节开头提出的异步 I/O 存在的两个缺陷进行优化。下示例代码对上文代码进行了优化,使用实时信号+sigaction 解决:默认 异步 I/O 通知信号 SIGIO 可能存在丢失以及信号处理函数中无法判断文件描述符所发生的 I/O 事件这两个问题。

        调用 sigaction()注册信号处理函数时,sa_flags 指定了 SA_SIGINFO,所以将使用 sa_sigaction 指向的函数 io_handler 作为信号处理函数,io_handler 共有 3 个参数,参数 sig 等于引发信号处理函数被调用的信号值。

#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 io_handler(int sig, siginfo_t*info, void *context){
    static int loops = 5;
    char buf[100] = {0};
    int ret;

    if(SIGRTMIN != sig) {
        return ;
    }

    /* 判断鼠标是否可读 */
    if (POLL_IN == info->si_code) {
        ret = read(fd, buf, sizeof(buf));
        if (0 < ret)
            printf("鼠标: 成功读取<%d>个字节数据\n", ret);
        loops--;
        if (0 >= loops) {
            close(fd);
            exit(0);
        }
    }
} 


int main (void){
    struct sigaction act;
    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);

    /* 指定实时信号 SIGRTMIN 作为异步 I/O 通知信号 */
    fcntl(fd, F_SETSIG, SIGRTMIN);

    /* 为实时信号 SIGRTMIN 注册信号处理函数 */
    act.sa_sigaction = io_handler;
    act.sa_flags = SA_SIGINFO;
    sigemptyset(&act.sa_mask);
    sigaction(SIGRTMIN, &act, NULL);
    
    for ( ; ; )
        sleep(1);    
}

        对上述示例代码进行编译时,出现了一些报错信息,如下所示:

 

        报错提示没有定义F_SETSIG,确实如此,我们需要定义了_GNU_SOURCE宏之后才能使用F_SETSIG。

        这里笔者选择直接在源文件中使用#define 定义_GNU_SOURCE 宏,如下所示:

#define _GNU_SOURCE //在源文件开头定义_GNU_SOURCE 宏

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

        再次进行编译测试即可

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

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

相关文章

rocketmq中ConsumeThreadMax不生效原因探究

在项目中设置了ConsumeThreadMax但是消息消费时仍是单线程消费&#xff0c;故而进行追踪排查。 其根本原因是rocketmq中&#xff0c;消费者线程池队列使用的是LinkedBlockingQueue。 rocket版本&#xff1a;rocketmq-client-4.9.3 代码追踪&#xff1a;从DefaultMQPushConsu…

C++设计模式之抽象工厂模式(Abstract Factory)

文章目录 定义前言问题解决方案 结构适用场景实现方式优点缺点与其他模式的关系实例 [C]22种设计模式的C实现大纲 定义 抽象工厂是一种创建型设计模式&#xff0c;它能创建一系列相关的对象&#xff0c;而无需指定其具体类。 前言 问题 假设你正在开发一款家具商店模拟器。…

Multi-Head Attention和Transformer Decoder(GPT)详解

文章目录 一、Transformer的Attention1. Self-Attention2. Masked Self-Attention3. Multi-Head Attention 二、Transformer Decoder&#xff08;GPT&#xff09;1. GPT的网络结构2. GPT的计算原理 一、Transformer的Attention 1. Self-Attention 如前篇文章所述&#xff08;ht…

如何开发一个人人爱的组件?

组件&#xff0c;是前端最常打交道的东西&#xff0c;对于 React、Vue 等应用来说&#xff0c;万物皆组件毫不为过。 有些工作经验的同学都知道&#xff0c;组件其实也分等级的&#xff0c;有的组件可以被上万开发者复用&#xff0c;有些组件就只能在项目中运行&#xff0c;甚…

Springboot +spring security,配置多个数据源:验证不同用户表

一.简介 上篇文章写到&#xff0c;我们在配置jdbc和mybatis 来源&#xff0c;进行登录后&#xff0c;出现了如下错误! 后面解决方案是&#xff1a;屏蔽了其中一个来源&#xff0c;登陆成功&#xff0c;也分析了其原因。 但是&#xff0c;但是如果需要配置多个数据来源&#…

2023年认证杯SPSSPRO杯数学建模B题(第一阶段)考订文本全过程文档及程序

2023年认证杯SPSSPRO杯数学建模 B题 考订文本 原题再现&#xff1a; 古代文本在传抄过程中&#xff0c;往往会出现种种错误&#xff0c;以至于一部书可能流传下来多种版本。在文献学中&#xff0c;错误往往被总结成“讹”、“脱”、“衍”、“倒”等形式&#xff0c;也可能同…

cda 1级模拟题错题知识点总结

Sql truncate函数 格式&#xff1a;TRUNCATE(number, decimals) number: the number to be truncated decimals:the number of decimal places to truncate to 截断到的小数位数&#xff0c;如果为0则表示不保留小数 例如: select truncate(2.83,0) 结果为2 select truncate(…

解读kubernetes部署:配置docker私服密钥与SSL证书创建

为k8s配置docker私服密钥 为了kubernetes有权访问您的docker私服&#xff0c;需要在kubernetes的凭证中建立docker私服的密钥&#xff1a; kubectlcreatesecretdocker-registryaliyun-secret--docker-server--docker-username--docker-password--docker-email--namespacens-jav…

2.golang的变量、常量、数据类型、循环和条件判断

一、变量 变量&#xff08;Variable&#xff09;的功能是存储数据。Go语言中的每一个变量都有自己的类型&#xff0c;并且变量必须经过声明才能开始使用。 Go语言的变量声明格式为&#xff1a; var 变量名 变量类型 例如&#xff1a; var name string var age int var isOk b…

线上问题处理案例:出乎意料的数据库连接池 | 京东云技术团队

导读 本文是线上问题处理案例系列之一&#xff0c;旨在通过真实案例向读者介绍发现问题、定位问题、解决问题的方法。本文讲述了从垃圾回收耗时过长的表象&#xff0c;逐步定位到数据库连接池保活问题的全过程&#xff0c;并对其中用到的一些知识点进行了总结。 一、问题描述…

LabVIEWCompactRIO 开发指南29 数据通信

LabVIEWCompactRIO 开发指南29 数据通信 LabVIEW FPGA中的数据通信分为两类&#xff1a;进程间和目标间。进程间通信通常对应于FPGA目标上的两个或多个环路之间的数据共享。目标间数据通信是在FPGA目标和主机处理器之间共享数据。对于这两种情况&#xff0c;在决定使用哪种机…

扩散能垒计算在电池材料领域的革新应用

扩散能垒计算在电池材料领域的革新应用 随着能源需求的增长和环境意识的提高&#xff0c;电池技术成为解决可再生能源存储和移动电子设备需求的关键。电池材料的研究和开发变得日益重要&#xff0c;而扩散能垒计算作为一种先进的计算方法&#xff0c;为电池材料领域带来了革新的…

设计模式之【观察者模式】,MQ的单机实现雏形

文章目录 一、什么是观察者模式1、观察者模式应用场景2、观察者模式的四大角色3、观察者模式优缺点 二、实例1、观察者模式的一般写法2、微信公众号案例3、鼠标响应事件API案例 三、实现一个异步非阻塞框架1、EventBus2、使用MQ 四、源码中的观察者模式1、Observable/Observer2…

pygam第3课——画图小程序

前言&#xff1a;我们前两节课已经学习了&#xff0c;界面的设计、图片的加载、那么今天我们将继续学习pygame的基础知识&#xff0c;我们的今天学习的内容是&#xff1a;鼠标滑动时坐标的实时获取、鼠标的移动事件、鼠标的点击事件、图形绘制等。希望大家能 搭建界面&#xf…

firewalld防火墙(又到了可以看日落和晚霞的日子了)

文章目录 一、firewalld概述二、firewalld和iptables的关系三、firewalld区域的概念四、firewalld数据处理流程五、firewalld检查数据包源地址的规则六、firewalld防火墙的配置种类1.运行时配置2.永久配置 七、firewalld防火墙的配置方法八、使用命令配置firewalld防火墙1.获取…

Ventoy 多合一启动盘制作工具神器 - 将多个系统 Win/PE/Linux 镜像装在1个U盘里

最近很多操作系统都纷纷发布了新版本&#xff0c;比如 Windows 11、Ubuntu、Deepin、优麒麟、CentOS、Debian 等等&#xff0c;对喜欢玩系统的人来说绝对是盛宴。 不过一般用 Rufus 等工具&#xff0c;一个 U 盘往往只能制作成一个系统的启动盘/安装盘&#xff0c;想要增加另一…

零入门kubernetes网络实战-33->基于nat+brigde+veth pair形成的跨主机的内网通信方案

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 本文主要使用的技术是 nat技术Linux虚拟网桥虚拟网络设备veth pair来实现跨主机网桥的通信 1、测试环境介绍 两台centos虚拟机 # 查看操作系统版本 cat …

VIBRO-METER VM600 IRC4 智能继电器卡

额外的继电器&#xff0c;由来自MPC4和/或AMC8卡的多达86个输入的方程驱动&#xff0c;用于需要2oo3表决等更复杂的逻辑时8个继电器&#xff0c;可配置为8个SPDT或4个DPDT使用IRC4配置器软件进行完全软件配置继电器可配置为正常通电(NE)或正常断电(NDE)&#xff0c;具有可配置的…

小航助学GESP_C++一级模拟测试试卷(含题库答题软件账号)

GESP在线模拟训练系统请点击 电子学会-全国青少年编程等级考试真题Scratch一级&#xff08;2019年3月&#xff09;在线答题_程序猿下山的博客-CSDN博客_小航答题助手 答案:A 第1题人们在使用计算机时所提到的 Windows 通常指的是&#xff08;&#xff09;。 A、操作系统B、多…

Science Bulletin:张占军教授团队提出“额叶保持,颞叶损伤” 假说解析成功认知老化

步入老年后&#xff0c;各项认知能力会逐渐衰退&#xff0c;我们把这一过程称之为认知老化。认知老化的过程与速度因人而异&#xff0c;走向阿尔茨海默病&#xff08;AD&#xff09;等认知障碍疾病为结局的属于病理认知老化&#xff0c;也就是经历轻度认知障碍阶段&#xff0c;…