(十)信号事件集成到多路I/O机制

I/O模型详解 传统的 Linux 系统的标准 I/O 接口(read、write)是基于数据拷贝的,也就是数据都是 copy_to_user或者 copy_from_user,这样做的好处是,通过中间缓存的机制,减少磁盘 I/O 的操作,但是坏处也很明显,大量数据的拷贝,用户态和内核态的频繁切换,会消耗大量的 CPU 资源,严重影响数据传输的性能,统计表明,在Linux协议栈中,数据包在内核态和用户态之间的拷贝所用的时间甚至占到了数据包整个处理流程时间的57.1% 阅读详情

前言

之前剖析代码的时候我们知道事件没激活前,都有自己的数据结构来管理,但是在激活之后都是放在激活链表中的。本小节我们将介绍libevent中关于信号事件如何管理,如何将信号事件统一到多路I/O复用事件中一起管理的。
首先我们先介绍一下evsignal_info结构体,再来解析信号事件是如何从注册到激活,最后被处理的。

evsginal_info

该结构体专门用于关于信号处理。并且每个base对应了一个evsignal_info。其中ev_signal 这个事件是供libevent内部使用的,它在evsignal_init中其事件类型会被设置成永久读事件,并且其状态被设置为内部事件。

struct evsignal_info {
        struct event ev_signal; //注册读事件使用的event结构体
        int ev_signal_pair[2];  //socket pair对
        int ev_signal_added;  //标识位,用于记录该事件是否已经注册
        volatile sig_atomic_t evsignal_caught;  //标识位,用于记录是否有信号发生。volatile关键字强制cpu每次从内存中读取数据
        struct event_list evsigevents[NSIG];  //注册到信号的事件链表
        sig_atomic_t evsigcaught[NSIG]; //记录每个信号触发的次数(跟ncalls作用相同)
        //sh_old记录原来的信号处理指针
    #ifdef HAVE_SIGACTION
        struct sigaction **sh_old;
    #else
        ev_sighandler_t **sh_old;
    #endif
        int sh_old_max;
};

如何将信号集成到主循环中

libevent采用的是socketpair的方法。分为读socket和写socket(虽然可以全双工通信,但是libevent只采用了单向的),读socket会在event_base上注册一个读事件(即ev_signal事件),并给每个需要监听的信号添加一个共同的信号捕捉函数evsignal_handler,然后设置好处理该信号事件的回调函数。
当信号发生时,evsignal_handler会向读socket发送1个字节并将该信号对应的evsigncaught加1,代表该信号发生,接着由于由于之前注册了ev_signal事件,因此该事件被触发,使得事件分发器知道有事件发生了,于是就可以进行激活进入到激活链表中等待处理了,最后调用的是用户自己设置的信号处理函数。

例子:

拿SIGINT信号举例。
1. 首先我们在使用libevent库时,会调用event_init,它内部会调用event_base_new来创建一个struct event_base *结构体,在event_base_new中,信号相关的会被初始化(evsignal_new),它会创建socketpair对以及设置好内部的ev_signal事件等操作。
2. 我们调用event_set设置好我们的信号事件,struct event中的ev_fd用来记录信号的编号,即SIGINT的编号。
3. 使用event_add将该信号事件注册到管理注册信号的链表中并与base绑定。此时event_add内部会检测到该事件是一个信号事件,因此将它的注册交给evsignal_add来注册。evsignal_add会查看SIGINT信号对应的信号事件链表是否有事件,如果没有,给SIGINT注册一个信号捕捉函数,即evsignal_handler,最后将该信号事件注册到SIGINT信号对应的事件链表上
4. 接着我们调用event_dispatch,其内部最终会调用event_base_loop,即主循环,等待事件发生。当SIGINT信号发送到当前进程时,首先信号捕捉函数开始工作。它将SIGINT信号对应的捕捉次数+1并将有信号发生的标志位置1(对应evsignal_info中的evsignal_caught),然后向读socket发送1个字节的数据。
5. 此时,对应的事件分发器(linux下的epoll)得知有事件发生,并且通过判断evsignal_caught标志位就可以知道是否有信号触发,如果有信号触发,则进入到evsignal_process处理该信号。evsignal_process下一节我们会进行分析,它的主要功能就是遍历evsigcaught数组,得知哪些信号被触发了,然后调用event_active将该信号事件加入到激活链表中。
6. 最后,eveve_process_active处理激活事件,成功处理掉我们注册的SIGINT事件。

evsignal_init

在前面的第3小节中,我们提到了event_base_new函数,它是event_init函数中最主要的部分,完成了所有关于event_base的初始化操作。
里面有一条语句是这样的base->evbase = base->evsel->init(base),它指向的是某个具体的多路I/O复用机制的初始化函数,在这个初始化函数中,会调用evsignal_init来初始化,它的作用就是初始化信号管理相关的数据。

int
evsignal_init(struct event_base *base)
{
        int i;

        /*
         * Our signal handler is going to write to one end of the socket
         * pair to wake up our event loop.  The event loop then scans for
         * signals that got delivered.
         */
         //创建套结字对
        if (evutil_socketpair(
                    AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {
#ifdef WIN32
                /* Make this nonfatal on win32, where sometimes people
                   have localhost firewalled. */
                event_warn("%s: socketpair", __func__);
#else
                event_err(1, "%s: socketpair", __func__);
#endif
                return -1;
        }
        //设置FD_CLOEXEC标识,具体作用前面说过,这里就不说了
        FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]);
        FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]);
        //设置成员的值
        base->sig.sh_old = NULL;
        base->sig.sh_old_max = 0;
        base->sig.evsignal_caught = 0;
        memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG);
        /* initialize the queues for all events */
        for (i = 0; i < NSIG; ++i)
                TAILQ_INIT(&base->sig.evsigevents[i]);
        //将设置成非阻塞态
        evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);
        //注册读事件(注意为永久事件)
        event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1],
                EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
        //设置base以及当前事件的状态
        base->sig.ev_signal.ev_base = base;
        base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;

        return 0;
}

代码中涉及到了一个回调函数evsignal_cb,代码如下:

/* Callback for when the signal handler write a byte to our signaling socket */
static void
evsignal_cb(int fd, short what, void *arg)
{
        static char signals[1];
#ifdef WIN32
        SSIZE_T n;
#else
        ssize_t n;
#endif

        n = recv(fd, signals, sizeof(signals), 0);
        if (n == -1)
                event_err(1, "%s: read", __func__);
}

小结

本小节主要介绍了将信号事件和多路I/O机制联系到一起的方法:socket pair,并阅读了有关信号的结构体以及在event_init中初始化信号函数的代码。下节我们将继续探讨信号事件注册、注销、激活等部分。

Linux中五种I/O模式 《Linux网络编程》五种I/O模式1、阻塞 I/O2、非阻塞 I/O3、I/O 多路复用4、信号驱动 I/O( SIGIO)5、异步 I/O一般来说,程序进行输入操作有两步:1.等待有数据可以读2.将数据从系统内核中拷贝到程序的数据区。对于一个对套接字的输入操作,第一步一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中;第二步是从内核中把数据拷贝到程序的数... 阅读详情

相关推荐

I/O 多路复用 (I/O Multiplexing)

I/O 多路复用是指单线程或单进程能够同时监测多个文件描述符(File Descriptor, FD),并判断哪些描述符已经就绪、可以执行I/O操作的能力。本质:用更少的系统资源(避免线程/进程创建开销、上下文切换成本、资源竞争)完成更多事件流的并发处理。

2401_83533039的博客 1318

libevent源码分析(一)

以\libevent-patches-1.4\sample\signal-test.c文件为起始,开始我的libevent源码剖析之路。本文主要讲述了围绕stuct event_base,struct event两个结构体发生的一系列函数调用。

Tryturned 301

I/O基础知识入门

前言 在我们开始学习netty之前,我们非常有必要先来简单的了解一下I/O相关的知识,因为netty所有的努力都是基于操作系统对I/O的不断优化和Java对这些能力的不断支持,所以我们在开始学习netty之前一定要先学习一下I/O相关的基础知识 概念解释 I/O I/O : I/O是Input和Output的缩写,意思就是输入/输出,根据操作对象的不同,可以分为内存I/O、磁盘I/O、网络I/O等,和Unix操作系统和Linux操作系统中,所有的组成元素都是一个文件,文件就是一种流的外在表现形式,对这些数

技术人集结地 7920

[libevent]evsignal_init()

int evsignal_init(struct event_base *base) { int i; /* * Our signal handler is going to write to one end of the socket * pair to wake up our event loop. The event loop then scans for * sign

scnuxiaotao的博客 880

宇树机器人G1二次开发:导览、起始点、目的地与完整示例

通过宇树G1 SDK的导航接口,开发者可以高效地实现机器人的自主移动功能。连接 -> 设置目标点 -> 发送命令 -> 监控状态。本文提供的完整示例涵盖了从环境搭建到代码实现的全部步骤,并解释了起始点、目的地等关键概念。开发者可以在此基础上,结合多目标点巡逻、动态避障(需高级配置)等功能,构建更复杂的机器人应用。建议读者先在小范围、熟悉的平坦环境中测试,成功后再逐步扩展到更复杂的场景。

m0_52645558的博客 747

APB Timer验证环境搭建——学习笔记(1)

以下为个人学习笔记,如有疏漏,请大家指正补充~~APB Timer是一个简单的下行计数器,包括一个32位计数器和一组比较器,可以实现多种计时功能。包括生成一次性中断或者定时中断。

qq_41651535的博客 1578

event 信号初始化

上一节我们对信号evsignal_info作了一个简单的分析, 本节就如同前面分析event功能函数时一样的来分析signal操作. 信号初始化 evsignal_init函数实现对一个信号的初始化操作. 源码位置: signal.c int evsignal_init(struct event_base *base) { int i; /* * Our signal handler ...

啦啦啦 703

基于DE2-115红外解码实验

基于DE2-115红外解码实验   一、   功能描述 在同一个遥控电路中通常要使用实现不同的遥控功能或区分不同的机器类型, 这样就要求信号按一定的编码传送,编码则会由编码芯片或电路完成。对应于编码芯片通常会有相配对的解码芯片或包含解码模块的应用芯片。在实际的产品设计或业余电子制作中,编码芯片并一定能完成我们要求的功能,这时我们就需要了解所使用的编码芯片到底是如何编码的。只有知道编码方式,我...

乘风的博客 2515

libev源码分析(三)---ev_signal

ev_signal结构体: typedef struct ev_signal { int active; int pending; int priority; void* data; void (*cb)(struct ev_loop *loop, struct ev_signal *w,int revents); struct ev...

君子性非异 1078

手把手教你用MATLAB和PSIM搞定Buck电路控制器:从传递函数到运放电路实战

本文详细介绍了如何使用MATLAB和PSIM协同设计Buck电路控制器,从传递函数推导到运放电路实现的完整流程。通过小信号建模、波特图分析和双平台仿真验证,帮助电力电子工程师解决理论到实践的转化难题,提升Buck电路控制器设计的效率和可靠性。

weixin_29271053的博客 268

微机原理与接口技术第六版周荷琴课后习题答案及解析(期末重点)

第二章 1.8086/8088CPU可直接寻址多少个内存(字节)单元?多少个I/O端口?它们的外部数据总线各有多少根? 8086/8088CPU使用的是20位地址总线,因此可以直接寻址2的20次方个内存单元(即1,048,576),每个单元大小为1字节,因此可以直接寻址2的20次方个字节的内存。 8086/8088CPU有64K个I/O端口,每个端口占用1字节的地址空间,因此可以直接寻址6...

Wmicheng的博客 1万+

libevent源码分析--libevent库对信号事件的处理

信号的处理在libevent中也是一个很重要的部分,同时处理的也很巧妙,这里根据自己的理解分析其堆信号的处理。和信号相关的几个重要API都是如何调用的。 这是网上关于libevent处理信号的过程图解(感谢原作者,由这个图来分析libevent如何处理信号的) 一、evsignal_init()函数(这里的分析都是按照IO复用机制的epOLL)        这个信号初始化在epol

鱼思故渊的专栏 2633

ICC2(一)import design —— tluplus

itf转tluplus相关命令 TLUPlus是存储RC系数的二进制表格式。 TLUPlus模型通过包括宽度,空间,密度和温度对电阻系数的影响,可以估算RC计算net delay。 1.2 产生TLUplus的方法 Itf文件转TLUplus的命令: grdgenxo -itf2TLUPlus -i xx.itf-o xx.tlu+ itf: interconnect technology format file 含每层的厚度,面积等参数 itf同样可以转nxtgrd格式文...

m0_61544122的博客 5483

Linux I/O编程:I/O多路复用与异步 I/O对比

本文将简要介绍 Linux 中的多路复用 I/O 和异步 I/O 模型,并编写示例代码简述。对于网络 I/O,特别是 UDP 和 TCP,多路复用 I/O(如 `select`、`epoll()`)通常是更好的选择。而异步 I/O(如 `libaio`)只建议用在文件和块设备的 I/O 操作。

Where there is life, there is hope. 1446

扫地机器人设计与实现——基于嵌入式系统的智能家居解决方案

本文介绍了基于嵌入式技术的扫地机器人设计与实现。通过利用STM32单片机、传感器、驱动系统等硬件组件,并结合路径规划与控制算法、远程控制等软件技术,设计出了一款具有智能避障、路径规划、自动充电等功能的扫地机器人。系统的开发不仅提高了清洁效率,也为进一步研究和优化扫地机器人提供了实践基础。

2201_76125393的博客 1639

HFSS实战项目——GPS北斗双模微带天线

本文设计了一款方形圆极化微带天线应用于北斗B1和GPSL1频段,满足高增益、低VSWR、适当方向图等要求。设计中心频率为1568MHz,介质板采用厚度h=1.6mm的Arlon AD450(tm)板,相对介电常数为4.5,采用探针馈电。

2401_87464239的博客 1767

Unix五种I/O模型(阻塞、非阻塞、多路复用、信号驱动、异步)

本文介绍了Unix五种I/O模型及其对比。

weixin_38597669的博客 1621

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

I/O 模型——阻塞 I/O、非阻塞 I/O、 I/O 多路复用、信号驱动 I/O、异步 I/O

模型特点使用场景优缺点阻塞 I/O阻塞操作,直到完成才返回简单应用场景,如文件操作编程简单,但性能差,阻塞线程,不适合高并发非阻塞 I/O发起 I/O 操作后立即返回,若没有准备好则返回错误适合高并发场景,轮询检查 I/O 状态编程复杂,CPU 占用高I/O 多路复用单线程监听多个 I/O 事件,减少线程切换开销高并发服务器,网络通信编程复杂,某些实现效率低,需频繁系统调用信号驱动 I/O使用信号来通知 I/O 完成较少使用,主要用于特定的事件处理编程复杂,信号处理可能影响控制流。

1345

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Java网络编程之I/O多路复用

I/O其实就是输入输出,I对应Input,O对应Output,在网络系统中,端与端怎么通信呢,靠的就是I/O系统。输入指从外部环境向计算机系统传输数据或指令的过程。常见的输入设备包括键盘、鼠标、扫描仪、摄像头、传感器等。例如,当你在键盘上打字时,键盘会将按键信息转换成计算机可以理解的信号,这就是输入。输出指计算机系统向外部环境发送数据或结果的过程。常见的输出设备有显示器、打印机、扬声器等。例如,当你在浏览器中请求网页时,服务器处理请求并将结果返回给你的计算机,计算机再将网页内容显示在屏幕上,这就是输出。

qq_44701525的博客 2146

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

I/O多路复用的三种机制

I/O多路复用的三种机制

安静的软件工程师 908

Node.js I/O 多路复用

I/O 多路复用是一种同时监视多个 I/O 源(如文件描述符、网络套接字等)的技术,它允许单个进程同时处理多个 I/O 操作,而无需使用多线程或多进程。这种技术能够显著提高程序的效率和性能,特别是在处理大量并发连接的网络应用中。

nihaio25的博客 1334

I/O 多路复用

什么是 select?select 是I/O 多路复用的一种机制,最早在 BSD Unix 中引入,用于监控多个文件描述符(FD)的 I/O 状态变化。它允许程序同时监听多个 FD,以判断哪些可以进行读、写或异常处理。2.什么是poll?poll 是一种I/O 多路复用技术,常用于处理大量并发连接,尤其在网络编程中广泛应用。它的设计目的是改进 select 的一些限制,尤其在处理大量文件描述符时更具优势。3.什么是epoll?epoll 是 Linux 内核 2.6 版本引入的。

f_2789889770的博客 1104

Redis高级篇之I/O多路复用的引入解析

这种设计在高并发、低延迟场景(如 Redis、Nginx)中表现尤为突出,成为现代高性能服务器的基石。通过这种设计,Redis 在单线程中轻松支持数万甚至数十万的并发连接,成为高性能内存数据库的标杆。以下是关键源码模块的分析,结合 Redis 6.0 源码(代码片段已简化)。,解决了传统阻塞 I/O 和多线程模型的根本性缺陷。当监听 Socket(如 6379 端口)有新的连接到达时,触发读事件,执行。Redis 的 I/O 多路复用实现是其高性能的核心设计之一,源码中通过。

qq_39126115的博客 1677

五种I/O模式 — 阻塞I/O 非阻塞I/O I/O多路复用 信号驱动I/O 异步I/O

五种I/O模式 ●阻塞I/O ●非阻塞I/O ●I/O多路复用 ●信号驱动I/O ●异步I/O 程序进行输入操作的步骤: 一般情况: 1、等待有数据可以读; 2、将数据从系统内核中拷贝到程序的数据区sock编程来说: 1、一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中; 2、是从内核中把数据拷贝到程序的数据区; 阻塞I/O模式阻塞I/O是指,进...

QX_a11的博客 1101

什么是I/O?

计算机有输入设备(键盘等)和输出设备(显示屏等),(网卡和硬盘既可以作为输入也可以作为输出)。所以从计算机结构角度来看,I/O描述的是。为了保证操作系统的安全性和稳定性,一个进程的地址空间被分为和。我们平时的应用程序都是运行在用户空间,而像等都是需要依赖内核空间来完成的。而我们要执行I/O操作时,由于我们没有权限,所以只有,让操作系统来帮我们完成。而我们一般开发常见的就是。所以从应用程序来看,I/O是指我们的应用程序,将我们需要的数据从内核空间拷贝到用户空间。

m0_51808048的博客 6725

嵌入式解谜日志—多路I/O复用

创建实例:通过创建 epoll 实例(epfd注册事件:通过epoll_ctl向epfd中添加需要监听的 FD 及事件(如EPOLLIN等待就绪:通过epoll_wait阻塞等待,内核自动将就绪事件写入events数组;处理事件:遍历events数组,根据就绪的 FD 和事件类型(如可读)进行处理;循环监听:重复步骤 3~4,持续处理新的就绪事件。

2201_75637288的博客 1192
上一篇: (九)事件的激活与调度
下一篇: (十一)信号事件的管理
UKey_
博客等级 码龄10年 258粉丝 186原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值