Linux 信号捕捉与处理
💓博主CSDN主页:麻辣韭菜💓
⏩专栏分类:Linux知识分享⏪
🚚代码仓库:Linux代码练习🚚
🌹关注我🫵带你学习更多Linux知识
🔝
目录
前言
1. 信号的处理时机
1.1用户态与内核态
1.2再谈地址空间
1.3 信号处理过程
1. 4 内核如何实现信号的捕捉
2. sigaction
功能
参数
结构体定义
注意事项
信号处理函数
代码演示
3. 额外知识
3.1可重入函数
3.2 volatile
3.3 SIGCHLD 信号
前言
信号产生到处理这是有一个过程的,从上篇信号保存我们知道了有一个handler表 里面存放的是对信号处理的方法。
那什么时候调用这些方法?本篇就揭晓处理信号的时机。
1. 信号的处理时机
直接说结论:当我们用户从内核态返回到用户态时,进行信号的检测与处理。
那什么是用户态?什么又是内核态?
1.1用户态与内核态
用户态:我们写的代码被执行时,就是用户态。
内核态:执行系统代码时,就是内核态。
如何理解内核态?
我们之前所学的fork创建子进程。 open read witre close这些系统调用接口被执行时,进程就从用户态切换成了内核态。
在调用这些系统调用接口。其实有都有一条汇编指令:int 80 (用户态切换成内核态)

也就是说执行我们代码时,是会产生中断或者异常,当CPU在处理中断或异常时,会根据中断描述符表(Interrupt Descriptor Table, IDT)中的描述符的DPL来决定是否需要进行上下文切换。如果当前的Code Segment的DPL高于或等于IDT中描述符的DPL,那么CPU将不会切换到一个新的特权级别,否则会进行上下文切换。
回想之前的进程地址空间图,
高地址的 3G 到 4G是内核空间,所以我们需要从新再谈地址空间,才能更好的理解用户状态的切换。
1.2再谈地址空间
每个进程都会有自己的地址空间和用户级页表,因为进程具有独立性。

从上图可以看到每个地址空间的3G到4G是内核空间。 也就是说执行操作系统代码以及系统调用接口的代码和数据是在这一个区域。
那是不是每个进程都要有内核级的页表?
当然不用。内核级页表只有一份!!!
每一个进程 在0到3GB 所看到的代码和数据是不一样的。内核级页表只有一份,就意味着3GB到4GB这段空间,所有进程看到的都是同样的代码和数据(OS)所以 不管怎么切换进程 这段空间的内容是不变的。

那进程又是如何被调度的?
不要忘了OS也是软件啊。并且还是一个死循环等待指令软件。
当然等待指令也是需要硬件的。时钟硬件 每格一段时间就会向cpu发送信息。OS这时就会知道,那个进程的时间片到了 然后通过一系列系统调用函数
switch_to()宏,以及底层的context_switch()函数。进行上下文切换。
1.3 信号处理过程
这里需要解释用户自定义为什么要回到用户模式执行处理方法。
有人就会说 在第2步时,已经在内核态了 可以执行信号处理的方法。为什么还要回到用户态?
因为OS对除了自己的系统调用对任何的代码都不相信,所以OS检测到是用户定义方法时。会切换状态。变成用户态,用户态执行信号处理。再次切换成内核态
那为什么还要返回到内核态?
因为进程的上下文还在内核态中,自定义函数栈帧和进程的栈帧不是同一个。拿什么返回上下文的数据?
上面的图有点复杂 我们用简化的版图 数学上的无穷符号
1. 4 内核如何实现信号的捕捉
2. sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
功能
sigaction() 系统调用用于改变进程接收特定信号时采取的动作。
参数
signum: 指定信号的标识符,可以是任何有效的信号,除了SIGKILL和SIGSTOP。act: 如果非空,表示要安装的新信号动作,来源于act。oldact: 如果非空,表示先前的信号动作将被保存在oldact中。
结构体定义
struct sigaction {void (*sa_handler)(int); // 传统信号处理函数void (*sa_sigaction)(int, siginfo_t *, void *); // SA_SIGINFO 标志设置时使用的信号处理函数sigset_t sa_mask; // 信号掩码,定义了信号处理函数执行时哪些信号应当被屏蔽int sa_flags; // 信号处理标志void (*sa_restorer)(void); // 已废弃,不应使用
};
注意事项
- 结构体中
sa_handler和sa_sigaction不应同时被赋值,它们是互斥的。 sa_restorer成员已废弃,POSIX 标准中没有指定该成员。sa_flags中设置SA_SIGINFO标志时,使用sa_sigaction成员,否则使用sa_handler。
信号处理函数
sa_handler: 可以是SIG_DFL(默认动作)、SIG_IGN(忽略信号)或者指向信号处理函数的指针。处理函数仅接收信号编号作为参数。- 如果
sa_flags包含SA_SIGINFO,则sa_sigaction指定信号处理函数,该函数接收三个参数:信号编号、指向siginfo_t的指针(包含信号的详细信息),以及指向ucontext_t的指针(通常被转换为void *类型,包含了信号发生时的上下文信息)。
代码演示
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <signal.h>
using namespace std;
void PrintPending()
{sigset_t set;sigpending(&set);for (int signo = 31; signo >= 1; signo--){if (sigismember(&set, signo)){cout << "1";}elsecout << "0";}cout << endl;
}void handler(int signo)
{cout << " catch a signal ,signal number: " << signo << endl;while (true){PrintPending();sleep(1);}
}int main()
{// 1.使用sigaction函数struct sigaction act, oact;// 1.1初始化 act oact。memset(&act, 0, sizeof(act));memset(&oact, 0, sizeof(oact));// 2.1 初始化 并设置信号屏蔽集sigemptyset(&act.sa_mask);sigaddset(&act.sa_mask, 1);sigaddset(&act.sa_mask, 3);sigaddset(&act.sa_mask, 4);// 3.自定义handler函数act.sa_handler = handler;//3.1 给2号信号注册自定义动作。sigaction(2, &act, &oact);while (true){cout << "I am a process: " << getpid() << endl;sleep(1);}// 4. 发送信号return 0;
}

可以从运行结果来看,我们发送4号信号确实被阻塞了。但是未决表中有信信息。
3. 额外知识
掌握额外知识在后面线程章节中,能更好的理解线程。
3.1可重入函数
有没有一种可能 两个进程同时执行容器 list 的插入函数 insert, 当进程1开辟好空间时,刚好进程1的调度时间片到了切换成进程2。而进程2调度时间,刚好可以把insert这个函数执行完毕。然后进程1再次被调度执行insert。这时进程2的insert函数调用new开辟的空间就会出现内存泄漏。

当然大家也不用担心。
我们学过的99%的函数都是不可被重入。
那有没有需要重入的场景?
操作系统服务:操作系统提供的某些服务,如内存分配器,必须是可重入的,因为它们可能会被不同的进程或线程同时调用。
那什么样的函数可以重入?
不使用全局变量或静态变量:可重入函数不依赖于全局或静态变量来存储状态,因为这些变量可能被多个线程共享,从而导致数据竞争。
不执行I/O操作:I/O操作可能会涉及系统资源,如文件句柄,这些资源在多线程环境中可能会引起冲突。
不调用其他不可重入的函数:如果可重入函数调用了不可重入的函数,它自身也就不再是可重入的了。
不返回指向内部数据结构的指针:函数返回的指针不应该指向函数内部的局部变量或分配的内存,因为这些内存在函数返回后可能会被释放。
不产生副作用:可重入函数不应该改变程序的其他部分的状态,除了其返回值。
使用局部变量:所有需要的变量都应该在函数内部分配,通常是在栈上。
使用互斥锁或原子操作:如果必须访问共享资源,可重入函数应该使用互斥锁(mutexes)或原子操作来确保线程安全。
避免使用动态内存分配:虽然技术上可行,但动态内存分配(如使用
malloc或new)可能会使函数不可重入,因为这些操作可能会依赖于全局或静态状态。快速执行:可重入函数应该设计成执行时间尽可能短,以减少它被中断的可能性。
信号安全:如果函数设计用于响应信号(如在UNIX系统中),它应该是信号安全的,即它可以在信号处理的上下文中安全调用。
3.2 volatile
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <signal.h>
using namespace std;
int n = 0;
void handler(int signo)
{cout << " catch a signal ,signal number: " << signo << endl;n = 1;
}int main()
{signal(2, handler);while (!n);cout << "process quit normal" << endl;return 0;
}

初步结果符合预期,2 号信号发出后,循环结束,程序正常退出
那是因为当前编译器默认的优化级别很低,没有出现意外情况
通过指令查询 g++优化级别的相关信息
man g++
: /O1

其中数字越大,优化级别越高,理论上编译出来的程序性能会更好
那我们再改成 -O1试试
g++ -o $@ $^ -g -O1 -std=c++11
编译成功 运行得到下面的结果。
此时得到了不一样的结果:2 号信号发出后,对于 falg 变量的修改似乎失效了
从上面的实验的,我们知道编译肯定是优化了,不然为什么while循环一直不退出。
为什么优化了,就会出现 n 的值修改失效?

为了防止编译器过度优化,votatile 这个关键字就出现了。它的作用就是防止编译过度优化。
volatile int n = 0;
这时我们在编译

3.3 SIGCHLD 信号
在进程控制学习期间, 我们知道父进程必须等待子进程的退出然后回收。以前没有信号的概念,那子进程退出后,会给父进程发信号吗?
当然会发信号 子进程退出后会给父进程发送 SIGCHLD 信号
SIGCHLD 是普通信号的17号位
下面我们用一段代码证明
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;void handler(int signo)
{cout << " catch a signal ,signal number: " << signo << endl;}int main()
{signal(17,handler);for(int i = 0 ; i<10 ; i++){pid_t pid = fork();if(pid == 0){while(true){cout << "I am a child process: " << getpid() << " ppid: " << getppid() << endl;sleep(1);break;}cout << " child quit !!! " << endl;exit(0);}sleep(1);}while (true){cout << "I am father process: " << getpid() << endl;sleep(1);}return 0;
}

从面结果来看确实回收了子进程。但是这个代码有问题。不要忘了pending表的位图17只有一位。如果回收多个进程那么OS只会做一次信号处理,而其他的子进程没有回收就造成内存泄漏。那如何解决?
自定义捕捉函数中,采取
while循环式回收,有很多进程都需要回收没问题,排好队一个个来就好了,这样就可以确保多个子进程同时发出SIGCHLD信号时,可以做到一一回收
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;void handler(int signo)
{pid_t rid;while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0){cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;}}int main()
{signal(17,SIG_IGN);for(int i = 0 ; i<10 ; i++){pid_t pid = fork();if(pid == 0){while(true){cout << "I am a child process: " << getpid() << " ppid: " << getppid() << endl;sleep(1);break;}cout << " child quit !!! " << endl;exit(0);}sleep(1);}while (true){cout << "I am father process: " << getpid() << endl;sleep(1);}return 0;
}

这里说一说为什么用 signal 函数参数为什么是 SIG_IGN
这里的忽略是个特例,只是父进程不对其进行处理,但只要设置之后,子进程在退出时,由 操作系统 对其负责,自动清理资源并进行回收,不会产生 僵尸进程
原理:
父进程的 PCB 中有关僵尸进程处理的标记位会被修改,子进程继承父进程的特性,子进程在退出时,操作系统检测到此标记位发生了改变,会直接把该子进程进行释放。
注意: SIG_IGN 只在linux中有效,其他系统无效比如UNIX
相关文章:
Linux 信号捕捉与处理
💓博主CSDN主页:麻辣韭菜💓 ⏩专栏分类:Linux知识分享⏪ 🚚代码仓库:Linux代码练习🚚 🌹关注我🫵带你学习更多Linux知识 🔝 目录 前言 1. 信号的处理时机 1.1用户…...
桂林电子科技大学计算机工程学院、广西北部湾大学计信学院莅临泰迪智能科技参观交流
5月18日,桂林电子科技大学计算机工程学院副院长刘利民、副书记杨美娜、毕业班辅导员黄秀娟、广西北部湾大学计信学院院长助理刘秀平莅临广东泰迪智能科技股份有限公司产教融合实训基地参观交流。泰迪智能科技副总经理施兴、广西分公司郑廷和、梁霜、培训业务部孙学镂…...
Qt笔记:动态处理多个按钮点击事件以更新UI
问题描述 在开发Qt应用程序时,经常需要处理多个按钮的点击事件,并根据点击的按钮来更新用户界面(UI),如下图。例如,你可能有一个包含多个按钮的界面,每个按钮都与一个文本框和一个复选框相关联…...
Excel模板计算得出表格看板
背景 表格看板及导出,单元格时间年是根据筛选器时间变化的 较往年和往年是计算单元格 思路 1.通过excel模板来把数据填入excel再数据清洗得到数据返回前端 2.数据填充,通过行列作为key 列如:key整体20241月,根据key匹配数据填…...
es数据备份和迁移Elasticsearch
Elasticsearch数据备份与恢复 前提 # 注意: 1.在进行本地备份时使用--type需要备份索引和数据(mapping,data) 2.在将数据备份到另外一台ES节点时需要比本地备份多备份一种数据类型(analyzer,mapping,data,template) …...
Oracle数据块之数据行中的SCN
从Oracle 10g开始,如果在表级别打开ROW DEPENDENCIES,业务数据行发生更改时会在数据块中进行登记。 可以通过DUMP数据块来观察上述SCN: (1)创建测试表,插入3条测试数据,插入一条提交一次。并调用…...
手写tomcat(Ⅱ)——Socket通信+tomcat静态资源的获取
Socket通信简介 参考文章:socket通讯原理及例程(一看就懂) socket是介于应用层(http协议)和传输层(TCP/UDP协议)之间的一层虚拟层 Socket是一个程序,符合TCP/UDP协议的规范&…...
解决Error: error:0308010C:digital envelope routines::unsupported的四种解决方案
问题描述: 报错:Error: error:0308010C:digital envelope routines::unsupported 报错原因: 主要是因为 nodeJs V17 版本发布了 OpenSSL3.0 对算法和秘钥大小增加了更为严格的限制,nodeJs v17 之前版本没影响&am…...
shell 脚本笔记2
3.env与set区别 env用于查看系统环境变量 set用于查看系统环境变量自定义变量函数 4.常用环境变量 变量名称含义PATH命令搜索的目录路径, 与windows的环境变量PATH功能一样LANG查询系统的字符集HISTFILE查询当前用户执行命令的历史列表 Shell变量:自定义变量 目标…...
aws eks集成wasm运行时并启动pod
参考资料 WebAssembly 在云原生中的实践指南,https://cloud.tencent.com/developer/article/2324065 作为一种通用字节码技术,wasm的初衷是在浏览器中的程序实现原生应用性能。高级语言将wasm作为目标语言进行编译并运行在wasm解释器中。和nodejs类似的…...
linux:切分大文件
文章目录 1. 前言2. 用法3. 例子 1. 前言 如果传输、存储过程中出现大文件,希望切分成小文件。在 Linux 中,可以使用多种工具来切分大文件,最常用的是 split 命令。split 命令可以将一个大文件按照指定大小切分成多个小文件。 2. 用法 spl…...
docker 配置文件使用经验,后续持续增加
1. 容器中如何访问主机服务 在docker容器、docker compose 中如何访问主机服务呢? docker容器 20.10.0 版本在 linux 新增 host.docker.internal 支持: docker run -it --add-hosthost.docker.internal:host-gateway alpine cat /etc/hosts 127.0.0.…...
Qml:键盘事件
import QtQuickWindow {width: 640height: 480visible: truetitle: qsTr("Test KeyEvent")//传递给活动窗口的QQuickWindow//传递给当前活动的Item(focus为true),如没则找子节点中的,都没有则忽略Item{id:item1//focus:…...
Java列表导出时将附件信息压缩成一个zip
一:使用场景 在最近的工作当中遇到了一个需求,在列表导出时,不仅需要将列表信息导出为excel文件,同时也需要将列表每一条数据所对应的附件信息放在同一个文件夹当中,并且压缩成一个zip响应给浏览器。首先后端需要写两…...
简单美观易上手的 Docker Compose 可视化管理器 Dockge
本文首发于只抄博客,欢迎点击原文链接了解更多内容。 前言 Dockge 是 Uptime Kuma 作者的新作品,因此 UI 风格与 Uptime Kuma 基本一致,如果你正在使用 Uptime Kuma 的话,那么 Dockge 的 UI 设计应该也不会让你失望。Dockge 主打…...
贴片 RS8752XK 封装SOP-8 250MHz,2通道高速运放
传感器信号放大:在传感器应用中,RS8752XK可以用于放大微弱的传感信号,如压力、温度、光强等传感器的信号。 数据采集系统:在数据采集设备中,RS8752XK可以用于放大和调理模拟信号,以供模数转换器࿰…...
图论-最短路算法
1. Floyd算法 作用:用于求解多源最短路,可以求解出任意两点的最短路 利用动态规划只需三重循环即可(动态规划可以把问题求解分为多个阶段)定义dp[k][i][j]表示点i到点j的路径(除去起点终点)中最大编号不超…...
家政预约小程序05服务管理
目录 1 设计数据源2 后台管理3 后端API4 调用API总结 家政预约小程序的核心是展示家政公司提供的各项服务的能力,比如房屋维护修缮,家电维修,育婴,日常保洁等。用户在选择家政服务的时候,价格,评价是影响用…...
Django自定义命令
Django自定义命令 我们知道,Django内部内置了很多命令,例如 python manage.py runserver python manage.py makemigrations python manage.py migrate我们可以在python控制台中查看所有命令 我们也可以自定义命令,让python manage.py执行…...
详解VLSM技术
在现代网络设计中,如何高效地分配和管理IP地址是一个关键问题。传统的子网划分方法虽然简单,但在实际应用中常常导致IP地址的浪费。为了应对这一问题,VLSM(Variable Length Subnet Mask,可变长子网掩码)技术…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...
