【探索Linux】—— 强大的命令行工具 P.18(进程信号 —— 信号捕捉 | 信号处理 | sigaction() )
阅读导航
- 引言
- 一、信号捕捉
- 1. 内核实现信号捕捉过程
- 2. sigaction() 函数
- (1)函数原型
- (2)参数说明
- (3)返回值
- (4)函数使用
- 二、可重入函数与不可重入函数
- 1. 可重入函数条件
- 2. 不可重入函数特征
- 三、volatile关键字
- 温馨提示
引言
在Linux系统中,信号是进程之间通信的重要方式之一。前面的两篇文章已经介绍了信号的产生和保存,本篇文章将进一步探讨信号的捕捉、处理以及使用sigaction()函数的方法。信号捕捉是指进程在接收到信号时采取的行动,而信号处理则是指对接收到的信号进行适当的处理逻辑。通过使用sigaction()函数,我们可以在程序中设置对特定信号的处理方式,从而实现更加灵活和精确的信号处理机制。本文将详细介绍信号捕捉的原理和使用方法,以及sigaction()函数的具体用法,帮助读者更好地理解和应用信号处理的相关知识。无论是开发基于Linux的应用程序,还是进行系统级编程,信号处理都是一个至关重要的主题,相信通过学习本文,您将对信号处理有更深入的了解。
一、信号捕捉
1. 内核实现信号捕捉过程
当信号的处理动作是用户自定义函数,并且在信号到达时调用该函数,这被称为捕捉信号。由于信号处理函数的代码运行在用户空间,处理过程可能会比较复杂,下面举一个例子来说明:
- 用户程序注册了处理函数
sighandler
来捕捉SIGINT
信号。 - 当前正在执行
main
函数时,若发生中断或异常导致切换到内核态。 - 在中断处理完成后,在返回用户态执行
main
函数之前,检测到有SIGINT
信号递达。 - 内核决定在返回用户态后,不恢复
main
函数的上下文继续执行,而是调用sighandler
函数。sighandler
函数和main
函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。 sighandler
函数执行完毕后,会自动执行特殊的系统调用sigreturn
,再次进入内核态。- 如果没有新的信号递达,此次返回用户态将会恢复
main
函数的上下文,并继续执行。
2. sigaction() 函数
sigaction()
函数是一个用于设置信号处理函数的系统调用。它允许用户程序指定对特定信号的处理方式,包括捕捉信号、忽略信号或使用默认处理方式。
(1)函数原型
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
(2)参数说明
signum
:指定要设置处理方式的信号编号。act
:指向一个struct sigaction结构体,用于设置新的信号处理方式。oldact
:可选参数,指向一个struct sigaction
结构体,用于保存之前的信号处理方式。
⭕struct sigaction
结构体定义如下:
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);sigset_t sa_mask;int sa_flags;void (*sa_restorer)(void);
};
⭕该结构体的主要成员包括:
- sa_handler:指定信号处理函数的地址,可以是一个函数指针,或者是SIG_IGN(表示忽略信号)或SIG_DFL(表示使用默认处理方式)。
- sa_sigaction:用于指定信号处理函数的扩展形式,可以获取更多关于信号的信息,如发送信号的进程ID等。
- sa_mask:指定一个信号屏蔽集,当进入信号处理函数时,会将这个屏蔽集与当前进程的信号屏蔽字进行按位或操作,从而阻塞其他指定的信号。
- sa_flags:用于设置一些标志位,如SA_RESTART表示在信号处理函数返回后自动重启被中断的系统调用。
- sa_restorer:已废弃的字段,现在不再使用。
(3)返回值
sigaction()
函数返回值为0表示操作成功,-1表示出现了错误。如果发生错误,可以通过errno
变量获取错误码。常见的错误码包括:
- EINVAL:指定的信号编号无效或者提供的struct sigaction结构体无效。
- ENOENT:指定的信号编号不存在。
(4)函数使用
使用sigaction()
函数进行信号处理的一般步骤如下:
- 创建一个
struct sigaction
结构体对象,并根据需要设置其中的成员,特别是sa_handler
或sa_sigaction
成员来指定信号处理函数。 - 调用
sigaction()
函数,传入要设置处理方式的信号编号、指向上述结构体对象的指针以及可选的保存之前处理方式的结构体指针。 - 根据
sigaction()
函数的返回值判断操作是否成功。
下面是一个简单的C语言示例,演示如何使用sigaction()
函数来捕获和处理SIGINT
信号(即Ctrl + C
):
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>void sigint_handler(int signo) {printf("Caught SIGINT, exiting...\n");exit(1);
}int main() {struct sigaction sa;// 设置信号处理函数为sigint_handlersa.sa_handler = sigint_handler;// 清空sa_mask,即不阻塞任何其他信号sigemptyset(&sa.sa_mask);// 设置一些标志位,这里使用默认值0sa.sa_flags = 0;// 注册对SIGINT信号的处理方式if (sigaction(SIGINT, &sa, NULL) == -1) {perror("sigaction");return 1;}printf("Press Ctrl+C to send a SIGINT...\n");// 进入一个无限循环,等待信号while (1) {sleep(1);}return 0;
}
在这个示例中,首先定义了一个名为sigint_handler的函数,用于处理SIGINT信号。然后在main函数中,创建了一个struct sigaction对象sa,并设置了其中的成员,包括sa_handler指向sigint_handler函数地址,sa_mask为空,sa_flags为0。接着调用sigaction()函数注册对SIGINT信号的处理方式。最后进入一个无限循环,等待信号的到来。
当用户按下Ctrl+C时,会发送SIGINT信号,程序会捕获该信号并调用sigint_handler函数进行处理,打印一条消息并退出程序。这样就实现了对SIGINT信号的自定义处理。
二、可重入函数与不可重入函数
⭕main
函数调用insert
函数向一个链表head
中插入节点node1
,插入操作分为两步,刚做完第一步的时候因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler
函数。sighandler
也调用insert
函数向同一个链表head
中插入节点node2
,插入操作的两步都做完之后从sighandler
返回内核态,再次回到用户态就从main
函数调用的insert
函数中继续 往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是 main
函数和sighandler
先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了。
⭕像上例这样,insert
函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。insert
函数访问一个全局链表有可能因为重入而造成错乱。像这样的函数称为不可重入函数,反之如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。想一下,为什么两个不同的控制流程调用同一个函数,访问它的同一个局部变量或参数就不会造成错乱?
1. 可重入函数条件
✅可重入函数必须满足以下条件:
-
不使用全局变量或静态变量,或者只读取这些变量的值。
-
不修改非本地的内存区域,或者仅修改线程本地的内存区域。
-
不调用可能导致线程挂起或阻塞的函数,如
sleep()
和wait()
等。
一些示例可重入函数包括:memcpy()
、strlen()
、sprintf()
、strtok_r()
等。
🚨注意:为了确保函数的可重入性,可以使用线程安全的函数或使用锁或其他同步机制来保护共享资源。同时,应该避免在函数中使用全局变量和静态变量,并尽可能将数据和状态存储在本地变量中。
2. 不可重入函数特征
✅不可重入函数通常具有以下特征:
-
使用全局变量或静态变量,或者修改非本地的内存区域。
-
调用可能导致线程挂起或阻塞的函数。
-
依赖于某些外部状态或资源。
一些示例不可重入函数包括:printf()
、scanf()
、malloc()
、signal()
等。
🚨注意:在信号处理程序中只能使用可重入函数。由于信号处理程序执行时可能会中断主程序的正常执行流程,因此不能使用不可重入函数,否则可能会导致意外行为或安全问题。
三、volatile关键字
在C和C++中,volatile
用于告诉编译器不要对该变量进行优化,以确保每次访问该变量都从内存中读取或写入。
volatile
关键字通常用于以下两种情况:
-
并发访问:当多个线程或多个任务并发地访问同一个变量时,为了避免出现数据竞争和意外的优化行为,可以使用
volatile
关键字修饰变量。这样可以确保每次访问都从内存中读取或写入,而不是依赖于编译器的优化策略。 -
中断处理:在嵌入式系统或操作系统开发中,中断处理程序通常需要访问硬件寄存器或共享变量。由于中断可能在任何时间发生,编译器可能会对变量进行优化,导致不正确的结果。通过使用
volatile
关键字修饰这些变量,可以确保每次访问都是实时的,不受编译器的优化干扰。 -
在信号处理程序中,
volatile
关键字可以用于告诉编译器不要对某些变量进行优化。由于信号处理程序执行时可能会中断主程序的正常执行流程,因此编译器可能会错误地优化某些变量或表达式,导致程序行为异常。
正如下面这个示例
#include <stdio.h>
#include <signal.h>sig_atomic_t flag = 0;void handle_signal(int signum) {flag = 1;
}int main() {signal(SIGINT, handle_signal);while (1) {if (flag) {printf("Received SIGINT signal, exiting...\n");break;}}return 0;
}
优化情况下,键入 CTRL + C
,2号信号被捕捉,执行自定义动作,修改 flag=1
,但是 while
条件依旧满足,进程继续运行!但是很明显flag
肯定已经被修改了,但是为何循环依旧执行?很明显, while
循环检查的flag
,并不是内存中最新的flag
,这就存在了数据二异性的问题。 while
检测的flag
其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?很明显需要 volatile
!!
#include <stdio.h>
#include <signal.h>volatile sig_atomic_t flag = 0; //使用了volatile关键字,编译器不会对它进行优化void handle_signal(int signum) {flag = 1;
}int main() {signal(SIGINT, handle_signal);while (1) {if (flag) {printf("Received SIGINT signal, exiting...\n");break;}}return 0;
}
在上面的示例中,定义了一个名为flag
的volatile sig_atomic_t
类型变量,用于表示是否收到了SIGINT信号。在主程序中,进入一个无限循环,检查flag
变量是否被设置为1。如果收到SIGINT信号,信号处理程序会将flag
变量设置为1,从而跳出循环并退出程序。由于flag
变量被声明为volatile
关键字,编译器不会对它进行优化,确保每次访问都从内存中读取或写入。这样可以避免由于编译器优化导致的意外行为。
🚨注意:在信号处理程序中,只有少量的函数和表达式可以安全地使用。具体来说,只有那些不分配内存或锁定全局资源的函数和表达式才能被安全地使用。为了确保信号处理程序的可重入性和线程安全性,应该尽可能避免在信号处理程序中使用非安全函数和表达式。
温馨提示
感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!
再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
相关文章:

【探索Linux】—— 强大的命令行工具 P.18(进程信号 —— 信号捕捉 | 信号处理 | sigaction() )
阅读导航 引言一、信号捕捉1. 内核实现信号捕捉过程2. sigaction() 函数(1)函数原型(2)参数说明(3)返回值(4)函数使用 二、可重入函数与不可重入函数1. 可重入函数条件2. 不可重入函…...

vue3+ts v-model 深度学习
<template><div><h1>我是App.vue组件</h1><div>isShpw:{{ isShow }}</div><div>text:{{ text }}</div><div><button click"isShow !isShow">开关</button></div><hr /><vModeVal…...

网络通信概述
文章目录 IP地址端口号协议三要素作用 五元组协议分层OSI七层模型TCP/IP 五层模型应用层传输层网络层数据链路层物理层 封装和分用发送方 - 封装中间转发接收方 - 分用 一般认为计算机网络就是利用通信线路和通信设备将地理上分散的、具有独立功能的多个计算机系统按不同的形式…...

<avue-crud/>,二级表头,children下字典项的dicUrl失效问题
目录 1.提出问题: 1.1 代码: 1.2 效果图:会发现处在children下的dicUrl失效了 2. 解决思路 3. 解决代码(你要的都在这,看这里) 1.提出问题: 在使用<avue-crud/>组件实现二级表头时&…...

FastApi接收不到Apifox发送的from-data字符串_解决方法
接收不到Apifox发送的from-data字符串_解决方法 问题描述解决方法弯路总结弯路描述纵观全局小结 问题描述 这里写了一个接口,功能是上传文件,接口参数是file文件和一个id字符串 gpt_router.post("/uploadfiles") async def create_upload_fi…...
Python高级数据结构——堆(Heap)
Python中的堆(Heap):高级数据结构解析 堆是一种基于树结构的数据结构,具有高效的插入和删除操作。在本文中,我们将深入讲解Python中的堆,包括堆的基本概念、类型、实现方式、应用场景以及使用代码示例演示…...

linux 讨论题合集(个人复习)
常规文件的权限是什么?如何分配或修改这些权限?文件夹(目录)的权限是什么?显示常规文件和文件夹的区别 讨论:①常规的文件权限有四种,r可读、w可写、x可执行、-没有权限;②可以使用c…...
浅析SD-WAN技术如何加强企业网络安全
在这个数字化时代,企业组网的安全性已经成为许多企业所面临的一个重要挑战。特别是随着云计算、移动办公等新型信息技术的普及,企业网络的规模和复杂度不断增加,网络攻击和数据泄露的威胁也日益增加。因此,企业需要采取更加有效的…...

测试相关-面试高频
测试面试相关 面试 测试的具体场景 功能测试 具体的测试工具Jmeter Postman selenium pytest 怎么看待测试的潜力与挑战 软件测试是正在快速发展,充满挑战的领域。尽管现在许多自动化测试软件的出现使得传统手工测试的方式被代替,但自动化测试工具的…...

基于Java web的多功能游戏大厅系统的开发与实现
摘 要 目前,国内游戏市场上的网络游戏有许多种类,游戏在玩法上也越来越雷同,形式越来越单调。这种游戏性系统给玩家带来的成就感虽然是无穷的,但是也有随之而来的疲惫感,尤其是需要花费大量的时间和精力,这…...
【MySQL工具】my2sql-快速解析binlog
目录 安装 my2sql简介 用途 工具优势 限制 账号所需权限 参数解析 场景 场景1 回滚 场景2 生成正向SQL 场景3 DML与事务统计 场景4 解析本地 与binlog2sql性能对比 安装 安装比较简单 直接下载二进制命令即可使用 wget https://git…...
vueRouter常用属性
vueRouter常用属性 basemodehashhistoryhistory模式下可能会遇到的问题及解决方案 routesprops配置(最佳方案) scrollBehavior base 基本的路由请求的路径 如果整个单页应用服务在 /app/ 下,然后 base 就应该设为 “/app/”,所有的请求都会在url之后加上/app/ new …...
Qt5.15.2的镜像网址
其它版本的qt把相应数字更换即可 已安装的QT怎么更新安装组件。离线版QT安装:已安装的QT怎么更新安装组件。离线版QT安装_哔哩哔哩_bilibili https://mirrors.tuna.tsinghua.edu.cn/qt/online/qtsdkrepository/windows_x86/desktop/qt5_5152_wasm/https://mirrors.…...
Python隐藏特性:字符串驻留、常量折叠
下面是Python字符串的一些微妙的特性,绝对会让你大吃一惊。 案例一: a “some_string” id(a) 140420665652016 id(“some” “_” “string”) # 注意两个的id值是相同的. 140420665652016 案例二: a “wtf” b “wtf” a is b True …...
2-Python与设计模式--工厂类相关模式
23种计模式之 前言 (5)单例模式、工厂模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式、(7)代理模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式、桥梁模式、(11)策略模式、责任链模式、命令模式、中介者模…...

PGP 遇上比特币
重复使用 PGP 密钥作为比特币密钥 介绍 在数字安全领域,密码学在确保数据的完整性和真实性方面发挥着至关重要的作用。 一种广泛使用的加密技术是使用 Pretty Good Privacy (PGP1)。 PGP 为安全通信(例如电子邮件、文件传输和数据存储)提供加…...

项目demo —— GPT 聊天机器人
本文介绍我的开源项目 TelegramChatBot,这是一个基于 OpenAI GPT API 开发的 telegram 机器人,具有多模态交互能力,求 star!感谢大家!在 telegram jokerController_bot 立即体验!欢迎对 GPT 应用开发或对 t…...

Airtest进阶使用篇!提高脚本稳定性 + 批量运行脚本!
一、背景 今天彭于晏为大家分享Airtest进阶使用篇,主要包含两块的内容: 提高脚本稳定性批量运行脚本生成测试报告 二、提高脚本稳定性 1、添加全局配置: #全局设置 ST.FIND_TIMEOUT10 #设置隐式等待时长,默认识别图片时间是30秒,可改为…...

数据库系统概述之数据库优化
为什么需要进行优化? 数据库性能瓶颈 数据库服务器的性能受许多因素影响,包括硬件能力、系统规模、业务模型及架构、代码设计、数据库表设计、系统环境等。 因此,可以从几个方面进行数据库优化 喜欢点赞收藏,如有疑问ÿ…...
【error:Custom elements in iteration require ‘v-bind:key‘ directives】元素绑定:key
在vue3中使用v-for操作的时候,报error Custom elements in iteration require v-bind:key directives 当我想自定义绘制echarts图的代码: <el-row><div v-if"data.chartDataList.length > 0"><el-col :span"12&quo…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...
ThreadLocal 源码
ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...
第22节 Node.js JXcore 打包
Node.js是一个开放源代码、跨平台的、用于服务器端和网络应用的运行环境。 JXcore是一个支持多线程的 Node.js 发行版本,基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。 本文主要介绍JXcore的打包功能。 JXcore 安装 下载JXcore安装包&a…...