《Linux C编程实战》笔记:信号的捕捉和处理
Linux系统中对信号的处理主要由signal和sigaction函数来完成,另外还会介绍一个函数pause,它可以用来响应任何信号,不过不做任何处理
signal函数
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
可以分解为以下几个部分:
signal是一个函数,它接受两个参数:signum和handler。signum是一个整数,表示信号的编号,例如SIGINT表示中断信号。handler是一个指向函数的指针,该函数负责处理收到的信号。
signal 函数返回一个函数指针,该指针指向之前注册的信号处理函数。
写成这样可能更好理解,我看我的系统里的源码也是长这样的:
#include<signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
sighandler_t指的类型是一个函数指针,指向的函数是一个int类型参数,返回值为void类型。
signal会根据参数signum指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是常数SIG_IGN (忽略该信号)或SIG_DFL (对该信号执行默认操作)。handler 是一个函数指针,它所指向的函数的类型是sighandler t,即它所指向的函数有一个int型参数,且返回值的类型为void。
signal函数执行成功时返回以前的信号处理函数指针,当有错误发生时返回SIG_ERR(即-1)。
注意:SIGKILL和SIGSTOP这两个信号不能被捕捉或忽略。
示例程序1
该示例程序演示了signal的使用
#include<stdio.h>
#include<signal.h>
void handler_sigint(int signo){printf("recv SIGINT\n");
}
int main(){if(signal(SIGINT,handler_sigint)==SIG_ERR){perror("Error setting signal handler");return 1;}while(1);return 0;
}
首先使用signal安装信号SIGINT的处理函数,然后进入死循环。当接收到SIGINT信号时,程序自动跳转到信号处理函数执行,打印出提示信息,然后返回主函数继续死循环。
SIGINT这个信号按Ctrl+C就可以产生了,信号的介绍可看前一篇《Linux C编程实战》笔记:Linux信号介绍-CSDN博客
执行结果如下:

最后按下Ctrl+\组合键向进程发送SIGQUIT信号。由于程序本身没有处理SIGQUIT信号,按照默认处理方式,进程退出。
sigaction函数
sigaction函数可以用来检查或设置进程在接收信号时的动作。函数原型如下
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数说明:
signum: 要处理的信号编号。act: 一个指向struct sigaction结构的指针,指定新的信号处理方式。oldact: 一个指向struct sigaction结构的指针,用于存储之前的信号处理方式。
sigaction会根据参数signum指定的信号编号来设置信号的处理函数。参数signum可以是SIGKILL和SIGSTOP以外的任何信号。如果参数act不是空指针,则为signum设置新的信号处理函数;如果oldact不是空指针,则旧的信号处理函数将被存储在oldact中。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 是用于指定信号处理函数的指针,sa_mask 是一个信号集,用于指定在信号处理函数执行期间要阻塞的信号,sa_flags 是用于设置信号处理的一些标志,sa_restorer 是用于指定一个恢复函数。
sa_handler和sa_sigaction在某些体系结构上被定义为共用体,即这两个值在某一时刻只有一个有效。
数据成员sa_restorer 已经作废,不再使用,POSIX标准也不支持该数据成员。
sa_handler可以是常数SIG_DFL或SIG_IGN,或者是一个信号处理函数的函数名。信号处理函数只
有一个参数即信号编号。该参数和参数sa_sigaction实际上都是函数指针。
sa_ sigaction也是用来指定信号signum的处理函数,但是它有3个参数,第一个参数是信号编号;
第二个参数是一个指向siginfo_t结构的指针;第三个参数是一个指向任何类型的指针,一般不使用。
sa_mask成员声明了一个信号集,在调用信号捕捉函数之前,该信号集会增加到进程的信号屏蔽码中,新的信号屏蔽码会自动包括正在处理的信号(sa_flags未指定SA_NODEFER或SA_NOMASK).当从信号捕捉函数返回时,进程的信号屏蔽码会恢复为原来的值。因此,当处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞直到本次信号处理结束为止。若这种信号发生了多次,则对于不可靠信号,它只会被阻塞一次, 即本次信号处理结束以后只会再处理一次(相当于丢失了信号);对于可靠信号(实时信号),则会被阻塞多次,即信号不会丢失,信号发生了多少次就会调用信号处理函数多少次。
sa_flags成员用来说明信号处理的一些其他相关操作。
这个字段的取值可以是以下几个常量之一,或者它们的按位组合:
SA_RESETHAND或SA_ONESHOT: 信号处理程序执行一次后,就恢复为默认的处理方式(SIG_DFL),即重新设置为系统默认的信号处理函数。这样,下次再接收到相同信号时,会再次调用用户设置的处理函数。
SA_RESTART: 如果系统调用由信号中断,那么自动重启该系统调用。这通常用于防止由信号中断的系统调用在中断后不会继续执行,而是返回错误。
SA_NODEFER或SA_NOMASK: 在信号处理程序执行期间,不阻塞同样的信号。也就是说,如果进程在执行信号处理程序时再次收到相同的信号,处理程序会被再次调用。默认情况下,执行信号处理程序时,相同的信号会被阻塞,直到处理完成。
SA_NOCLDSTOP: 如果设置了这个标志,子进程的停止状态(stopped)不会生成SIGCHLD信号。通常,当子进程停止或继续执行时,会发送SIGCHLD信号给父进程。
SA_SIGINFO: 如果设置了这个标志,信号处理程序是sa_sigaction而不是sa_handler。sa_sigaction允许更多的信息传递给信号处理程序。这些标志可以通过按位或(
|)的方式组合使用。
当使用三参数的sa_sigaction来指定信号处理函数时,它的第二个参数可以用来传递数据,其定义如下:
#include <signal.h>struct siginfo_t {int si_signo; // 信号编号int si_errno; // 与信号相关的错误编号int si_code; // 信号代码pid_t si_pid; // 发送信号的进程IDuid_t si_uid; // 发送信号的用户IDint si_status; // 子进程的退出状态或信号clock_t si_utime; // 用户态运行时间clock_t si_stime; // 内核态运行时间sigval_t si_value; // 信号值int si_int; // 附加的整数值void* si_ptr; // 附加的指针值int si_overrun; // 未处理的timer信号的数量int si_timerid; // 产生timer信号的timer IDvoid* si_addr; // 发生错误的内存地址long si_band; // 通用的事件描述int si_fd; // 文件描述符short si_addr_lsb; // 最低有效字节的地址int si_tid; // 产生signal的线程IDstruct {int si_trapno; // 陷阱的编号short si_addr_lsb; // 最低有效字节的地址(覆盖si_addr_lsb)} si_perfdata;
};
其中,所有的信号都有si_signo、si_errno和si_code 这3个数据成员,分别表示信号编号,errno值和信号产生的原因。其他成员则根据信号的不同含有不同的意义,接收信号的进程只能读这些成员的值,而不能进行设置。si_int 和si_ptr 可以用来传递数据,后面会演示其用法。其余的数据成员则根据不同的信号存在不同的组合,了解即可。
sigaction函数执行成功时返回0,当有错误发生时返回-1,错误代码存入errno。
注意:Linux下signal函数是由sigaction实现的。
示例程序2
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int temp=0;
void handler_sigint(int signo){printf("\nrecv SIGINT\n");sleep(5);temp+=1;printf("the value of temp is:%d\n",temp);printf("in handler_sigint,after sleep\n");
}
int main(){struct sigaction act;act.sa_handler=handler_sigint;act.sa_flags=SA_NOMASK;//允许嵌套sigaction(SIGINT,&act,nullptr);while (1);return 0;
}
运行结果

主要看第二和第三次按下Ctrl+C。我们设置的是按下Ctrl+C时,信号的处理函数会睡5s,第三次Ctrl+C正是在这5s内按下的,由于我们设定了sa_flags的值为SA_NOMASK,因此程序又一次响应了SIGINT,程序从sleep()处嵌套调用信号处理函数handler_sigint,再一次打印出"recv SIGINT",睡眠5s后,将temp的值打印出来并返回到本次信号处理程序的跳入点sleep()处,然后再打印出temo的值并返回到主函数。
从程序执行可以看到,temp 的值随着信号处理函数被调用的次数的增加而递增,而由于实际应用中信号总是随机发生的,这样temp的值也会随机变化。如果main函数或其他地方还用到了这个全局变量,则程序将产生不可预料的结果。我们称这种数据会被破坏的函数为不可重入函数。编写信号处理程序时要注意不要使用不可重入函数。一般来说,满足下列条件之一的函数是不可重入的。
使用了静态的数据结构,如getgrgid(),全局变量等。
函数实现时,调用了malloc或者 free函数。
函数实现时,使用了标准I/O函数。

将程序中的SA_NOMASK这一行去掉,重新编译运行,执行时快速按下Ctrl+c三次以上,结果如下

可以看到确实按了三次Ctrl+c,但是函数只执行了两次,因为SIGINT是不可靠信号,不可靠信号不支持排队,从而有可能丢失信号。第三个信号就丢失了。
pause函数
pause函数使调用进程挂起直至捕捉到一个信号
#include<unistd.h>
int pause(void);
pause函数会令目前的进程暂停,知道被信号所中断。该函数只返回-1并将errno设置为EINTR。
相关文章:
《Linux C编程实战》笔记:信号的捕捉和处理
Linux系统中对信号的处理主要由signal和sigaction函数来完成,另外还会介绍一个函数pause,它可以用来响应任何信号,不过不做任何处理 signal函数 #include <signal.h> void (*signal(int signum, void (*handler)(int)))(int);可以分解…...
python算法与数据结构---单调栈与实践
单调栈 单调栈是一个栈,里面的元素的大小按照它们所在栈的位置,满足一定的单调性; 性质: 单调递减栈能找到左边第一个比当前元素大的元素;单调递增栈能找到左边第一个比当前元素小的元素; 应用场景 一般用…...
文心一言使用分享
ChatGPT 和文心一言哪个更好用? 一个直接可以用,一个还需要借助一些工具,还有可能账号会消失…… 没有可比性。 通用大模型用于特定功能的时候需要一些引导技巧。 import math import time def calculate_coordinate(c, d, e, f, g, h,…...
【C++干货铺】C++11新特性——lambda表达式 | 包装器
个人主页点击直达:小白不是程序媛 C系列专栏:C干货铺 代码仓库:Gitee 目录 C98中的排序 lambda表达式 lambda表达式语法 表达式中的各部分说明 lambda表达式的使用 基本的使用 [var]值传递捕捉变量var 编辑 [&var]引用传递捕…...
在 EggJS 中实现 Redis 上锁
配置环境 下载 Redis Windows 访问 https://github.com/microsoftarchive/redis/releases 选择版本进行下载 - 勾选 [配置到环境变量] - 无脑下一步并安装 命令行执行:redis-cli -v 查看已安装的 Redis 版本,能成功查看就表示安装成功啦~ Mac brew i…...
Unity-场景
创建场景 创建新的场景后: 文件 -> 生成设置 -> Build中的场景 -> 将项目中需要使用的场景拖进去 SceneTest public class SceneTest : MonoBehaviour {// Start is called before the first frame updatevoid Start(){// 两个类: 场景类、场…...
MATLAB R2023b for Mac 中文
MATLAB R2023b 是 MathWorks 发布的最新版本的 MATLAB,适用于进行算法开发、数据可视化、数据分析以及数值计算等任务的工程师和科学家。它包含了一系列新增功能和改进,如改进了数据导入工具,增加了对数据帧和表格对象的支持,增强…...
01 MyBatisPlus快速入门
1. MyBatis-Plus快速入门 版本 3.5.31并非另起炉灶 , 而是MyBatis的增强 , 使用之前依然要导入MyBatis的依赖 , 且之前MyBatis的所有功能依然可以使用.局限性是仅限于单表操作, 对于多表仍需要手写 项目结构: 先导入依赖,比之前多了一个mybatis-plus…...
HarmonyOS 应用开发入门
HarmonyOS 应用开发入门 前言 DevEco Studio Release版本为:DevEco Studio 3.1.1。 Compile SDK Release版本为:3.1.0(API 9)。 构建方式为 HVigor,而非 Gradle。 最新版本已不再支持 (”Java、JavaScrip…...
【机器学习300问】9、梯度下降是用来干嘛的?
当你和我一样对自己问出这个问题后,分析一下!其实我首先得知道梯度下降是什么,也就它的定义。其次我得了解它具体用在什么地方,也就是使用场景。最后才是这个问题,梯度下降有什么用?怎么用? 所以…...
第13章 1 进程和线程
文章目录 程序和进程的概念 p173函数式创建子进程Process类常用的属性和方法1 p175Process类中常用的属性和方法2 p176继承式创建子进程 p177进程池的使用 p178并发和并行 p179进程之间数据是否共享 p180队列的基本使用 p180使用队列实现进程之间的通信 p182函数式创建线程 p18…...
什么是中间件?
文章目录 为什么需要中间件?中间件生态漫谈数据库中间件读写分离分库分表引进数据库中间件MyCat 服务端代理模式ShardingJDBC 客户端代理模式 总结 IT 系统从单体应用逐渐向分布式架构演变,高并发、高可用、高性能、分布式等话题变得异常火热,…...
汽车售后服务客户满意度调查报告
本文由群狼调研(长沙旅行社满意度调查)出品,欢迎转载,请注明出处。汽车售后服务客户满意度调查报告通常包括以下内容: 1.调研概况:介绍调研的目的、背景和范围,包括调研的时间、地点和样本规模等…...
初始RabbitMQ(入门篇)
消息队列(MQ) 本质上就是一个队列,一个先进先出的队列,队列中存放的内容是message(消息),是一种跨进程的通信机制,用于上下游传递消息, 为什么使用MQ: 削峰填谷: MQ可以很好的做一个缓冲机制,例如在一个系统中有A和B两个应用,A是接收用户的请求的,然后A调用B进行处理. 这时…...
JVM:Java类加载机制
Java类加载机制的全过程: 加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始, 这是为了支持Java…...
要经历痛苦,才能在赚钱路上觉醒!
新手赚钱,一个秘诀就够了! 黎明前的黑暗实际是最漫长的,就如同开发进度99%到100%这个过程尤其漫长。赚钱的路上起初就是黑暗,不断地摸索最终才能走出迷雾,真正的迎接朝阳。如果有一段路程,十来公里的路线&a…...
LeetCode 第381场周赛个人题解
目录 100191. 输入单词需要的最少按键次数 I 原题链接 题目描述 思路分析 AC代码 100188. 按距离统计房屋对数目 I 原题链接 题目描述 思路分析 AC代码 100192. 输入单词需要的最少按键次数 II 原题链接 题目描述 思路分析 AC代码 100213. 按距离统计房屋对数目…...
数据结构之二叉树的性质与存储结构
数据结构之二叉树的性质与存储结构 1、二叉树的性质2、二叉树的存储结构 数据结构是程序设计的重要基础,它所讨论的内容和技术对从事软件项目的开发有重要作用。学习数据结构要达到的目标是学会从问题出发,分析和研究计算机加工的数据的特性,…...
机器视觉检测设备在连接器外观缺陷检测中的应用
作为传输电流或信号连接两个有源器件的器件,连接器被广泛应用于各个行业,从手机、平板、电脑,到冰箱、空调、洗衣机,再到汽车、国防、航空,处处是它的所在。每个电子产品少了连接器将无法运作,因此…...
ChatGPT vs 文心一言(AI助手全面比较)
随着人工智能的不断发展,ChatGPT(OpenAI)和文心一言都代表了当前先进的自然语言处理技术。它们在智能回复、语言准确性和知识库丰富度等方面都有各自的优势。在下面的比较中,我们将从多个角度探讨这两个AI助手,帮助你更…...
python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...
