Linux 信号终篇(总结)

news2025/5/14 2:40:02

前文:本文是对信号从产生到被处理的过程中的概念和原理的总结,如果想了解具体实现,请查看前两篇博客:Linux 信号-CSDN博客、Linux 信号(下篇)-CSDN博客

一、信号的产生

1.1 信号产生的五种条件

①键盘组合键 :“ctrl + c”或者“ctrl + z”等;

②linux命令: kill - signo processpid , 例:杀掉进程号12345的进程  :kill -9 12345 ;

③系统调用:在自己的代码中调用系统调用接口,给某个进程发信号或者给自己发信号常用的几个接口:

#include <sys/types.h>
#include <signal.h>
int kill( pid_t pid, int sig);
//1. pid : 进程pid,想要发送到哪个进程?
//2. sig :要发送的信号码,想要发送什么信号?
//3. 返回值int,失败返回-1
#include <signal.h>
int raise(int sig);
//1. 给自己发信号,相当于 kill(getpid(),sig);
//2. sig: 想要给自己发送的信号
#include <stdlib.h>
void abort(void);
//1.终止自己
//2.给自己发送6号信号 :SIGABRT

④、异常

异常的过程是:当你的进程被调度的时候,cpu执行你的代码,当cpu执行到某行代码时(例如:除0错误或者对空指针访问等)->

cpu知道自己的运算异常,在cpu的状态寄存器中修改溢出标志位->

同时cpu给OS发送硬件异常错误 ->

OS收到cpu硬件异常的报错后去找到对应的进程 ->

并给这个进程发送对应的异常信号,提示这个进程需要注意 ->

如果这个进程不结束,异常一直没有解决,cpu会一直调度这个进程,cpu会一直给OS发送硬件异常错误,OS会一直给这个进程发信号,进程处于死循环;

⑤软件条件

例如:闹钟接口,每隔一段时间给进程发送闹钟信号;

#include <unistd.h>
unsigned int alarm( unsigned int second);
//1. second:每隔多少秒给进程发信号?
//2. 返回值unsigned int 是上一个闹钟剩余的时间
1.2 理解core dump

打开系统的core dump功能,一旦进程出现异常,OS会将进程在内存中的运行信息,给dump(转储)到进程的当前目录(磁盘)上,形成core.pid文件,(核心转储:core dump);

未来在调试的时候,在core-file 直接定位到代码出错行,这是事后调试的方法(运行出错后再调试);

1.3 深度理解信号的发送和产生

①给进程发信号其实是给进程的PCB发的信号;

②所谓的“发信号”,其本质是OS修改PCB内的一个用来管理信号的位图数据结构,这个数据结构其实是一个int的整形,因为一个int 32bit位刚好可以用来表示1~31号信号,所以所谓的“发信号”其实就是对内核数据的写入操作,把对应位置的0写成1,代表信号产生了;

③信号的产生处理整个过程都是OS在操作,因为OS是所有硬件的管理者,只有OS才能直接访问内核数据结构,普通用户只能调用系统接口,因为操作系统不相信任何用户;

二、信号的保存

2.1、信号为什么要保存?

因为进程收到信号之后,可能不会立即处理,会有一个时间窗口,在这个过程中需要先把信号保存起来;

2.2、信号如何保存?进程如何管理信号?

通过三张表:

①block表,位图数据结构,与每个信号映射对应关系,这张表用来记录某个信号是否被屏蔽,如果被屏蔽把对应信号位置的0改1,当取消屏蔽把对应的1再改为0;

②pending表,位图数据结构,与每个信号映射对应关系,这张表用来记录某个信号是否已经产生,是否已经收到某个信号,注意:如果信号已产生但并未处理即(信号未决),把对应信号位置的0改为1,当这个信号被处理后再从对应的1改为0;

handler表,指针数组结构,这是一个用来保存对信号的处理的方法对应的指针,当未来处理这个信号的时候,直接调用指针指向的方法;注意:这里的处理方法包括三种:SIG_DFL(默认)、SIG_IGN(忽略)、自定义方法;

2.3、操作这三张表相关的接口

因为这三张表属于内核数据结构,所以用户不能对其直接进行访问或修改操作,只能通过系统调用接口;

数据准备工作:信号集函数:

#include <signal.h>
int sigemptyset( sigset_t * set);
//1. 对信号集清空处理操作
//2. set:sigset_t类型结构,是系统封装的位图结构体
#include <signal.h>
int sigaddset( sigset_t * set, int signum);
//1. 添加信号编号到信号集中
//2. set:系统封装的位图结构体
//3. signum想要添加到信号集中的信号编号(未来对这个信号进行操作)
//4. 返回值int,如果成功返回0否则返回-1
#include <signal.h>
int sigismember(const sigset_t *set ,int signo);
//1. 检测信号signo是否存在内核中的pending表中,存在代表信号产生,信号未决;
//2. set: 系统封装的位图结构体
//3. signo: 需要检查的信号
//4. 返回值int,如果不存在返回0,如果存在返回1,如果失败返回-1
#include<signal.h>
int sigpending(sigset_t * set);
//1. 获取内核中pending表的接口
//2. 这是输出型参数,传入set带出内核中的pending表
//3. set: 系统封装的位图结构体
//4. 返回值int,如果获取失败返回值<0

系统调用接口:

#include <signal.h>
int sigprocmask( int how, const sigset_t*set, sigset_t* oset);
//1.how: 当how是SIG_SETMASK时,将set自定义设置好的数据一个一个设置进进程PCB的管理信号的block表里
//2. set: 自定义设置好的位图结构体,oset: 通过oset保存内核更改前的数据以便日后恢复
//3. 当第二个参数传入oset,第三个参数传入nullptr时,代表的是取消屏蔽信号

三、信号的捕捉

3.1两个信号捕捉接口

①:signal

#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//1. signum: 需要捕捉的信号
//2. handler: 捕捉信号后调用的自定义方法
//3. sighandler_t : 定义的函数指针类型,参数为int,返回值为void

②:sigaction

#include <signal.h>
int sigaction(int signum, struct action * act,struct action* oact);
//1. signum: 需要捕捉的信号
//2. act: 自己创建并设置好的action对象,输入型参数,OS通过act写入到内核数据中
//3. oact: 自己创建初始化好的action对象,输出型参数,通过oact把内核中修改前的数据带出来,以便日后恢复
//4. action: 结构体,成员中包括handler处理方法、sigset_t类型的sa_mask,传入前通过添加sa_mask字段达到信号在处理时屏蔽其它信号的效果;
//5.注意:这个接口需要再传参之前把act、oact创建并初始化好,同时act需要把结构体成员sa_handler(自定义方法)、sa_mask(需要屏蔽的信号)字段设置好(如果你不想屏蔽其它信号可以不设置);
3.2可重入函数理解

例如:当程序运行时向链表头插一个Node1节点,调用insert方法,在方法运行到创建了一个节点,链接到头部,并且正准备让head指向Node1的时候,突然接收到一个信号!这时主函数main会停下来,先执行处理信号的方法,正巧的是处理信号的方法也是insetr即(向链表头插一个节点),那么此时会先运行完处理信号的方法:把Node2头插到链表并把head指向了Node2,处理完后回到主函数main接着刚才的下一步即:让head指向Node1,那么此时Node2无法再被找到!!

简单的代码(不要扣细节,大概意思懂就行):

void insert()
{
    Node->next=head;//①当main函数执行到这里的时候收到2号信号
    head=Node;//④回到main刚才被暂停的位置继续往下执行,执行完后Node2没有指针指向它,成为丢失节点!!
}
void handler(int signo)
{
    list->insert(Node);//③执行inser成功头插并把head指向Node
}
int main()
{
    signal(2,handler);//② 二号信号被捕获并跳转到自定义的handler方法
    list->insert(Node1);
    return 0;
}

重入函数:如果一个函数被重复进入的情况下,出错了或者可能出错那么这个函数为不可重入函数!反之则是可重入函数!!!

3.3、volatile

volatile的作用:防止编译器过度优化,保持内存可见性!!

代码:

#include <iostream>
using namespace std;
#include <unistd.h>
#include <signal.h>
int flag=0;
void handler(int signo)
{
    flag=1;
    cout<<"  signo is: "<<signo<<endl;
}
int main()
{
    signal(2,handler);
    while(!flag);
        cout<<"i am process !! process id:"<<getpid()<<endl;
    return 0;
}

编译器优化后cup没从内存读取flag不会运行下一句:

加上volatile后再编译运行:

cpu从内存中读取flag,循环条件判断失败,指向下一条语句;

四、基于信号方式的进程等待

4.1、当子进程退出时会给父进程发一个信号

我们捕获这个信号然后自定义处理方法去等待回收这个进程,而父进程继续做自己的事:

#include <sys/wait.h>
#include <iostream>
using namespace std;
#include <unistd.h>
void handler(int signo)
{
   pid_t id= waitpid(-1,nullptr,0);
   if(id>0)
   {
    cout<<"waitprocess success!! process id:"<<id<<endl;
   }
}
int main()
{
    signal(2,handler);
    pid_t id=fork();
    if(id==0)
    {
        //child
        cout<<"i am a child process ,process id is: "<<getpid()<<"  ppid is:"<<getppid()<<endl;
        exit(0);
    }
    //farther
    while(true)
    {
        cout<<"i am farther process!!"<<endl;
        sleep(1);
    }
    return 0;
}

运行:

发送2号信号:

 

4.2 如果十个进程同时结束呢?或者结束一半后再结束另一半呢?

此时进程在正在执行信号处理方法,会把同信号屏蔽掉,那怎么办?

用循环非阻塞轮询:

一秒钟创建10个进程并处于僵尸状态:

发送2号信号:

成功回收完所有子进程!!

4.3 如果不需要获取子进程退出状态,不想自己等待的话,还有一个办法能让子进程退出时自动被回收掉

捕获17号信号(子进程结束时给父进程发送的信号),并把方法设置为SIG_IGN(忽略):

子进程被自动回收,监视窗口没有Z状态的进程,且只有一个父进程在跑:

今天的分享就到这里,如果对你有所帮助麻烦点赞收藏+关注哦!!谢谢!!!

咱下期见!!!

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

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

相关文章

LVGL对象(Objects)

文章目录 &#x1f9f1; 一、LVGL 中的对象&#xff08;lv\_obj&#xff09;&#x1f539; lv\_obj\_t 的作用 &#x1f9e9; 二、对象的分类结构&#xff08;类比继承&#xff09;&#x1f9f0; 三、对象的创建与销毁✅ 创建对象示例&#xff1a;创建一个按钮❌ 删除对象 &…

服务器配置错误导致SSL/TLS出现安全漏洞,如何进行排查?

SSL/TLS 安全漏洞排查与修复指南 一、常见配置错误类型‌ 弱加密算法与密钥问题‌ 使用弱密码套件&#xff08;如DES、RC4&#xff09;或密钥长度不足&#xff08;如RSA密钥长度<2048位&#xff09;&#xff0c;导致加密强度不足。 密钥管理不当&#xff08;如私钥未加密存…

路由重发布

路由重发布 实验目标&#xff1a; 掌握路由重发布的配置方法和技巧&#xff1b; 掌握通过路由重发布方式实现网络的连通性&#xff1b; 熟悉route-pt路由器的使用方法&#xff1b; 实验背景&#xff1a;假设学校的某个分区需要配置简单的rip协议路由信息&#xff0c;而主校…

C++修炼:stack和queue

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C修炼之路》 欢迎点赞&#xff0c;关注&am…

【计算机视觉】优化MVSNet可微分代价体以提高深度估计精度的关键技术

优化MVSNet可微分代价体以提高深度估计精度的关键技术 1. 代价体基础理论与分析1.1 标准代价体构建1.2 关键问题诊断 2. 特征表示优化2.1 多尺度特征融合2.2 注意力增强匹配 3. 代价体构建优化3.1 自适应深度假设采样3.2 可微分聚合操作改进 4. 正则化与优化策略4.1 多尺度代价…

软考错题集

一个有向图具有拓扑排序序列&#xff0c;则该图的邻接矩阵必定为&#xff08;&#xff09;矩阵。 A.三角 B.一般 C.对称 D.稀疏矩阵的下三角或上三角部分包含非零元素&#xff0c;而其余部分为零。一般矩阵这个术语太过宽泛&#xff0c;不具体指向任何特定性 质的矩阵。对称矩阵…

T2I-R1:通过语义级与图像 token 级协同链式思维强化图像生成

文章目录 速览摘要1 引言2 相关工作统一生成与理解的 LMM(Unified Generation and Understanding LMM.)用于大型推理模型的强化学习(Reinforcement Learning for Large Reasoning Models.)3 方法3.1 预备知识3.2 语义级与令牌级 CoT语义级 CoT(Semantic-level CoT)令牌级…

Dockers部署oscarfonts/geoserver镜像的Geoserver

Dockers部署oscarfonts/geoserver镜像的Geoserver 说实话&#xff0c;最后发现要选择合适的Geoserver镜像才是关键&#xff0c;所以所以所以…&#x1f437; 推荐oscarfonts/geoserver的镜像&#xff01; 一开始用kartoza/geoserver镜像一直提示内存不足&#xff0c;不过还好…

扩增子分析|微生物生态网络稳定性评估之鲁棒性(Robustness)和易损性(Vulnerability)在R中实现

一、引言 周集中老师团队于2021年在Nature climate change发表的文章&#xff0c;阐述了网络稳定性评估的原理算法&#xff0c;并提供了完整的代码。自此对微生物生态网络的评估具有更全面的指标&#xff0c;自此网络稳定性的评估广受大家欢迎。本系列将介绍网络稳定性之鲁棒性…

【含文档+PPT+源码】基于微信小程序的社区便民防诈宣传系统设计与实现

项目介绍 本课程演示的是一款基于微信小程序的社区便民防诈宣传系统设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套…

【MySQL】存储引擎 - ARCHIVE、BLACKHOLE、MERGE详解

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

代码随想录第41天:图论2(岛屿系列)

一、岛屿数量&#xff08;Kamacoder 99&#xff09; 深度优先搜索&#xff1a; # 定义四个方向&#xff1a;右、下、左、上&#xff0c;用于 DFS 中四向遍历 direction [[0, 1], [1, 0], [0, -1], [-1, 0]]def dfs(grid, visited, x, y):"""对一块陆地进行深度…

VUE CLI - 使用VUE脚手架创建前端项目工程

前言 前端从这里开始&#xff0c;本文将介绍如何使用VUE脚手架创建前端工程项目 1.预准备&#xff08;编辑器和管理器&#xff09; 编辑器&#xff1a;推荐使用Vscode&#xff0c;WebStorm&#xff0c;或者Hbuilder&#xff08;适合刚开始练手使用&#xff09;&#xff0c;个…

Java EE初阶——初识多线程

1. 认识线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。 基本概念&#xff1a;一个进程可以包含多个线程&#xff0c;这些线程共享进程的资源&#xff0c;如内存空间、文件描述符等&#xff0c;但每个线程都有自己独…

如何删除网上下载的资源后面的文字

这是我在爱给网上下载的音效资源&#xff0c;但是发现资源后面跟了一大段无关紧要的文本&#xff0c;但是修改资源名称后还是有。解决办法是打开属性然后删掉资源的标签即可。

FPGA图像处理(5)------ 图片水平镜像

利用bram形成双缓冲&#xff0c;如下图配置所示&#xff1a; wr_flag 表明 buffer0写 还是 buffer1写 rd_flag 表明 buffer0读 还是 buffer1读 通过写入逻辑控制(结合wr_finish) 写哪个buffer &#xff1b;写地址 进而控制ip的写使能 通过状态缓存来跳转buffer的…

day21python打卡

知识点回顾&#xff1a; LDA线性判别PCA主成分分析t-sne降维 还有一些其他的降维方式&#xff0c;也就是最重要的词向量的加工&#xff0c;我们未来再说 作业&#xff1a; 自由作业&#xff1a;探索下什么时候用到降维&#xff1f;降维的主要应用&#xff1f;或者让ai给你出题&…

ERP学习(一): 用友u8安装

安装&#xff1a; https://www.bilibili.com/video/BV1Pp4y187ot/?spm_id_from333.337.search-card.all.click&vd_sourced514093d85ee628d1f12310b13b1e59b 我个人用vmware16&#xff0c;这位up已经把用友软件和环境&#xff08;sqlserver2008&#xff09; 都封城vmx文件了…

01 | 大模型微调 | 从0学习到实战微调 | AI发展与模型技术介绍

一、导读 作为非AI专业技术开发者&#xff08;我是小小爬虫开发工程师&#x1f60b;&#xff09; 本系列文章将围绕《大模型微调》进行学习&#xff08;也是我个人学习的笔记&#xff0c;所以会持续更新&#xff09;&#xff0c;最后以上手实操模型微调的目的。 (本文如若有…

海康相机无损压缩

设置无损压缩得到更高的带宽和帧率&#xff01;