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 内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
如果用户程序注册了SIGQUIT信号的处理函数 sighandler。        
当前正在执行main函数,这时发生中断或异常切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返 回后自动执行特殊的系统调用sigreturn 再次进入内核态。
如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

 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%的函数都是不可被重入

那有没有需要重入的场景?

操作系统服务:操作系统提供的某些服务,如内存分配器,必须是可重入的,因为它们可能会被不同的进程或线程同时调用。

那什么样的函数可以重入?

  1. 不使用全局变量或静态变量:可重入函数不依赖于全局或静态变量来存储状态,因为这些变量可能被多个线程共享,从而导致数据竞争。

  2. 不执行I/O操作:I/O操作可能会涉及系统资源,如文件句柄,这些资源在多线程环境中可能会引起冲突。

  3. 不调用其他不可重入的函数:如果可重入函数调用了不可重入的函数,它自身也就不再是可重入的了。

  4. 不返回指向内部数据结构的指针:函数返回的指针不应该指向函数内部的局部变量或分配的内存,因为这些内存在函数返回后可能会被释放。

  5. 不产生副作用:可重入函数不应该改变程序的其他部分的状态,除了其返回值。

  6. 使用局部变量:所有需要的变量都应该在函数内部分配,通常是在栈上。

  7. 使用互斥锁或原子操作:如果必须访问共享资源,可重入函数应该使用互斥锁(mutexes)或原子操作来确保线程安全。

  8. 避免使用动态内存分配:虽然技术上可行,但动态内存分配(如使用mallocnew)可能会使函数不可重入,因为这些操作可能会依赖于全局或静态状态。

  9. 快速执行:可重入函数应该设计成执行时间尽可能短,以减少它被中断的可能性。

  10. 信号安全:如果函数设计用于响应信号(如在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

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

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

相关文章

【排版问题解决】word加入公式时字间距突然变大

出现以下问题 解决方案 第一步:选择段落 第二步 段落括起来后右键选择“段落”- 第三步 “换行和分页”-在换行里打勾“允许西文在单词中间换行”。 恢复格式

vue.js状态管理和服务端渲染

状态管理 vuejs状态管理的几种方式 组件内管理状态&#xff1a;通过data&#xff0c;computed等属性管理组件内部状态 父子组件通信&#xff1a;通过props和自定义事件实现父子组件状态的通信和传递 事件总线eventBus&#xff1a;通过new Vue()实例&#xff0c;实现跨组件通…

LP-MSPM03507学习资料汇总

(因对MSPM0研究不够深入,故暂不开启浏览权限,权当记录学习。但愿尽快掌握供大家免费阅读。有意者可私信我共同学习) 一、延时函数 1、滴答定时器SYSTICK 1.1 SysConfig配置 配置1ms延时函数,并开启中断 1.2 编写延时函数delay_ms unsigned int utick = 0;//滴答定时器中…

57. UE5 RPG 处理AI敌人转向以及拾取物品的问题

在上一篇文章中&#xff0c;我们实现了使用AI行为树控制敌人进行移动&#xff0c;它们可以一直跟随玩家&#xff0c;虽然现在还未实现攻击。但在移动过程中&#xff0c;我发现了有两个问题&#xff0c;第一个是敌人转向的时候很僵硬&#xff0c;可以说是瞬间转向的&#xff0c;…

Vue3实战笔记(39)—封装页脚组件,附源码

文章目录 前言一、封装页脚组件二、使用组件总结 前言 在Web开发中&#xff0c;页脚组件是一个重要的部分&#xff0c;它为用户提供关于网站的信息、导航链接以及版权声明等。而封装页脚组件则是一种高效的方法&#xff0c;可以提高代码的可重用性和可维护性。 一、封装页脚组…

重生之我要精通JAVA--第五周笔记

文章目录 APIJDK7时间Date时间类CalendarSimpleDateFormat 类SimpleDateFormat 类作用 JDK8时间Zoneld时区 包装类Integer成员方法 Arrays Lambda表达式标准格式注意点好处省略写法 集合进阶Collection迭代器遍历Collection集合获取迭代器Iterator中的常用方法细节注意点 增强f…

机器学习势系列教程(3):cp2k的安装

大家好&#xff0c;我是小马老师。 本文继续介绍机器学习势相关内容&#xff1a;cp2k的安装。 和abacus一样&#xff0c;cp2k也是一款开源的第一性原理模拟软件&#xff0c;模拟的数据也可作为机器学习势的训练数据集。 cp2k安装方法也很多&#xff0c;常见的有docker安装、…

2024年中国电机工程学会杯数学建模思路 - 案例:最短时间生产计划安排

# 前言 2024电工杯(中国电机工程学会杯)数学建模思路解析 最新思路更新(看最新发布的文章即可): https://blog.csdn.net/dc_sinor/article/details/138726153 最短时间生产计划模型 该模型出现在好几个竞赛赛题上&#xff0c;预测2022今年国赛也会与该模型相关。 1 模型描…

安当SLA操作系统双因素认证——成功解决Windows远程桌面登录的安全问题

Windows远程桌面登录的安全风险主要包括以下几个方面&#xff1a; 弱密码和暴力破解&#xff1a;如果远程桌面用户使用了弱密码&#xff0c;或者密码被泄露&#xff0c;攻击者可能会使用暴力破解技术来尝试猜测密码&#xff0c;从而获取远程桌面的访问权限。中间人攻击&#x…

【编译原理复习笔记】语法分析-补充(二义性与LR错误处理)

二义性文法的 LR 分析 每个二义性文法都不是 LR 的 但是某些二义性文法更加简短&#xff0c;描述更方便 如 I7 和 I8 具有移进归约冲突 使用优先级和结合性解决冲突 对于 I7&#xff0c;由于乘号优先级高于加号&#xff0c;所以当下一个输入符号为乘号时&#xff0c;我们优…

pdfbox pdf转换图片时中文丢失,变成方框,提示No glyph for xxx in font STSong-Light

使用pdfbox转换图片时&#xff0c;转换出来的图片中文丢失&#xff0c;变成方框。原因是由于服务器字体缺失&#xff0c;pdfbox在转换时找不到合适的字体。 有几种方案&#xff1a; 服务器安装字体&#xff0c;具体资源百度使用备用字体。 将pdfbox中的FontMapperImpl类&…

Android 逆向学习【2】——APK基本结构

APK安装在安卓机器上的&#xff0c;相当于就是windows的exe文件 APK实际上是个压缩包 只要是压缩的东西 .jar也是压缩包 里面是.class(java编译后的一些东西) APK是Android Package的缩写,即Android安装包。而apk文件其实就是一个压缩包&#xff0c;我们可以将apk文件的后…

RabbitMQ安装及配套Laravel使用

MQ MQ 全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于系统之间的异步通信。 为什么需要mq: 解耦:MQ能够使各个系统或组件之间解耦,降低它们之间的耦合度,提高系统的灵活性和可维护性异步处理:通过MQ可以实现异步处理,提高系统响应速度和吞…

后量子密码解决方案

什么是后量子密码学 (PQC)&#xff0c;为什么准备工作如此重要? 量子计算正在迅速发展;用不了多久&#xff0c;量子网络攻击就会成为可能。量子网络攻击将能够在几分钟内瘫痪大型网络。我们今天赖以保护我们的连接和交易的一切都将受到量子计算机的威胁&#xff0c;危及所有密…

PostgreSQL学习:关于PostgreSQL以及认证

1、关于PostgreSQL PostgreSQL&#xff08;简称PG&#xff09;是强大的企业级开源关系数据库&#xff0c;世界排名第四&#xff0c;前三位Oracle 、SQLServer、MySQL都是商业数据库或受商业主体的控制&#xff0c;PG是学术社区开源数据库&#xff0c;开源协议自由度非常高&…

免费开源人脸识别系统,支持RESTful API

简介 CompreFace 是一个免费开源的人脸识别项目&#xff0c;您不需要具备机器学习技能就能安装设置和使用 CompreFace&#xff0c;官方提供了基于 docker 的部署方法&#xff0c;可以方便地部署在本地或者云端服务器上。 CompreFace 提供了 RESTful API&#xff0c;用于人脸识别…

Springboot 多环境切换 方法

准备工作 假设系统中有以下几个yml文件&#xff1a; application.ymlapplication-dev.ymlapplication-prode.ymlapplication-test.yml 方法一&#xff1a;在Active Profiles:输入dev 启动效果&#xff1a; 方法二&#xff1a;在Environment variables: 输入spring.profile…

Dijkstra算法在《庆余年》中的应用:范闲的皇宫之旅

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

GPT‑4o普通账户也可以免费用

网址 https://chatgpt.com/ 试了一下&#xff0c;免费的确实显示GPT‑4o的模型&#xff0c;问了一下可以联网&#xff0c;不知道能不能通过插件出图 有兴趣的可以试试

Acrobat Pro DC 2024 Mac软件安装包下载PDF2024 Mac安装教程

安装 步骤 1&#xff0c;双击打开下载好的安装包。 2&#xff0c;选择acrobat dc installer.pkg双击启动安装程序。 3&#xff0c;点击继续。 4&#xff0c;点击继续。 5&#xff0c;点击继续。 6&#xff0c;点击安装。 7&#xff0c;输入电脑密码。 8&#xff0c;软件安装中…