【Linux】信号之信号的产生详解

🤖个人主页:晚风相伴-CSDN博客

💖如果觉得内容对你有帮助的话,还请给博主一键三连(点赞💜、收藏🧡、关注💚)吧

🙏如果内容有误的话,还望指出,谢谢!!!

✨下一篇文章《信号的阻塞》,敬请期待

目录

✨初识信号 

🔥从程序员角度看待信号 

理解组合键变成信号的原理

🔥常见的信号

理解信号为什么要被进程保存起来

🔥理解信号发送的本质

信号的常见处理方式

✨信号的产生

🔥Core Dump

初识信号的捕捉

🔥由系统调用产生信号

kill函数 

raise函数

abort函数

理解系统调用发送信号

🔥由软件条件产生信号

管道 

 alarm函数

理解软件条件给进程发送信号

🔥由硬件条件产生信号 

✨总结


在我们的生活中充满了大量的信号,我们一看到这些信号就能反应出后续的一系列动作,比如:红绿灯、交警的手势、你打游戏时给队友发送的信号等等。

举一个详细点的例子:在双11的时候,你在网上购买了许多的商品,并且在等待不同商品快递的到来。但是即使是快递还没到,你也能够清楚的判断正在运送的是否是你的快递(根据快递的单号),这也就是你能“识别快递”。当快递员到了你家楼下,你收到了快递到来的通知,但是你正在打游戏,并且正是关键时刻,需要5分钟之后才能拿下去拿快递。那么在这5分钟之内你并没有下楼取快递,但是你是知道快递已经到了的,也就是拿快递的行为并不是一定要立即执行,可以理解为“在合适的时候去拿快递”。从收到快递已到的通知再到你拿快递,这期间是有一个时间窗口的,在这段时间内,你并没有拿到快递,但是你知道快递已经到了。本质上是“你知道了有一个快递需要去拿”,但是此时你正在忙,当时间合适的时候,你会去将快递拿回来。当你将快递拿回来之后,需要处理这个快递,而处理这个快递一般方式有三种:1、拆开快递并自己使用(执行默认动作)  2、拆开快递立马送给别人(执行自定义动作)  3、快递拿回来后丢在一边,继续开一把游戏(忽略快递)。

这快递到来的整个一过程对你来说是异步的,你可以做你自己的事,因为你不能确定快递员什么时候把你的快递送到。

✨初识信号 

🔥从程序员角度看待信号 

当你在前台启动了一个进程,并且这个进程不会自己退出,这时你就可以从键盘输入快捷键Ctrl+C将这个进程终止掉。当你在键盘输入Ctrl+C时会产生一个硬件中断,被操作系统获取,并且操作系统会将其解释成2号信号,发送给前台进程,前台进程收到这个信号,进而引起前台进程的退出。

这里的进程就是对应上面例子中的你,而操作系统对应的就是快递员,信号对应的就是快递

因此得出结论

Linux信号本质是一种通知机制,用户或者操作系统通过发送相应的信号通知进程某些事件已经发生,你可以在后续(合适的时机)对这个信号进行处理。 

理解组合键变成信号的原理

键盘的工作方式是通过中断方式进行的,当你按下一个组合键时,键盘硬件会识别出这是一个特殊的组合,并生成相应的中断。Linux内核中的中断处理程序会捕获这个硬件中断,并且读取键盘的状态,确定是哪个键或者组合键被按下了。之后根据键盘中断的信息,中断处理程序就会生成一个或多个信号。

🔥常见的信号

使用kill -l命令可以查看信号列表

前1-31个信号为普通信号,后34-64个信号为实时信号,本篇文章只讨论普通信号,有兴趣的可以自行去了解一下实时信号。每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。

理解信号为什么要被进程保存起来

我们知道进程在收到信号时并不是立即对信号进行处理,而是在合适的时机进行处理,那么信号就需要被我们的进程保存起来,所以在进程的PCB中肯定具有保存信号的相关数据结构。

那么用什么数据结构来保存信号最合适呢

这个数据结构要能体现这是一个什么信号、这个信号是否产生等,并且我们的信号只有62个,说到这,想必你已经猜到了,对,没错,用位图来保存信号是为最合适,每个信号都可以用一个二进制位来表示其状态。通过使用位图,Linux操作系统可以非常快速地检查、设置或清除某个信号的状态,而不需要遍历整个信号列表或者执行其它复杂的操作。

🔥理解信号发送的本质

信号发送的本质:操作系统修改目标进程的PCB中的指定信号的位图结构,将指定信号的二进制位由0 -> 1。

信号的常见处理方式

  1. 默认(进程自带的,程序员写好的逻辑)
  2. 忽略(不做处理)
  3. 自定义捕捉(捕捉信号)

✨信号的产生

🔥Core Dump

如果core dump是被打开的,进程在收到某些信号而异常终止时,操作系统会将此时进程地址空间中的内容以及有关进程状态的其它信息一同写入一个磁盘文件中,生成的这个文件的文件名通常是core,这就叫做core dump,也叫做核心转储。这个文件通常用于调试,可以帮助我们快速的定位到出错的地方。

还记得在上一篇文章进程控制中的一幅图

用一个比特位来表示core dump是否被打开,下面我们就会用代码提取这个比特位进行验证

如果你使用的和我一样是云服务器,那么一般核心转储的功能是关闭的,需要我们手动打开。

使用ulimit -a查看core dump

使用ulimit -c对core dump进行修改

示例代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main()
{pid_t id  = fork();if(id == 0){sleep(1);int a = 100;a /= 0;//除0错误exit(1);}int status = 0;waitpid(id, &status, 0);cout << "父进程pid: " << getpid() << " 子进程id: " << id << " 退出信号: " << (status & 0x7F) \<< " core dump: " << ((status >> 7) & 1) << endl;return 0;
}

结果演示

利用生成的core dump文件定位问题

但是并不是所有的信号都会生成core dump文件的

man 7 signal查看手册

只有Action是core的才会生成core dump文件。

一般在生产环境中都是会关闭core dump的,原因如下

  1. 浪费磁盘空间:core dump文件非常大,会占用大量的磁盘空间
  2. 不安全:core dump文件可能包含敏感信息,如密码等,增加泄漏的风险
  3. 影响性能:生成的core dump会消耗CPU资源,影响系统性能。

初识信号的捕捉

参数

  • signum:对应的信号编号
  • handler:回调函数,通过回调的方式来修改对应信号的捕捉方法

返回值:一般不关心

示例代码如下

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signum)
{cout << "捕捉到一个信号: " << signum << endl;
}int main()
{signal(2, handler);while(true) sleep(1);return 0;
}

结果演示

2号信号已经被我们自定义捕捉了,那么我们如何将这个进程终止掉呢?

可以使用kill命令将进程终止

3号信号(SIGQUIT)——进程退出(对应的快捷键:Ctrl+\)

🔥由系统调用产生信号

kill函数 

 参数

  • pid:目标进程的pid
  • sig:要发送的信号

返回值:成功返回0,失败返回-1

示例代码 

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>using namespace std;void Usage(string proc)
{cout << "Usage:\r\n\t" << proc << " signumber processid" << endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}int signumber = atoi(argv[1]);int processid = atoi(argv[2]);kill(processid, signumber);return 0;
}

结果演示

raise函数

自己给自己发送信号

参数

  • sig:要发送的信号

返回值:成功返回0,失败返回非0

示例代码

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>using namespace std;int main(int argc, char* argv[])
{raise(8);//自己给自己发送信号return 0;
}

8号信号(SIGFPE)——浮点异常(比如除0错误、浮点型错误等) 

结果演示

abort函数

 

用于异常终止一个进程

没有返回值

示例代码

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>using namespace std;int main(int argc, char* argv[])
{cout << "我开始执行我的代码了" << endl;sleep(1);abort();return 0;
}

结果演示 

 

理解系统调用发送信号

用户调用系统接口,执行操作系统对应的系统调用代码,操作系统提取其参数,向目标进程的PCB中的信号位图中修改信号标记位,进程会在合适的时候处理信号,执行相应的处理动作。

🔥由软件条件产生信号

管道 

在管道中,如果我们将读端关闭,写端一直写,写端将缓冲区写满后,操作系统会自动终止对应的写端进程,终止的本质是:操作系统向写端进程发送13号信号(SIGPIPE)。

示例代码 

#include <iostream>
#include <assert.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{// 1.创建管道int pipefd[2] = {0}; // pipefd[0]:读端 , pipefd[1]: 写端int n = pipe(pipefd);assert(n != -1);(void)n;// 2.创建子进程pid_t id = fork();assert(id != -1);if (id == 0){// 子进程close(pipefd[0]);// close(pipefd[0]);char buffer[1024 * 8];while (true){string message = "我是子进程,我正在给你发消息";int count = 0;char send_buffer[1024];snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);write(pipefd[1], send_buffer, strlen(send_buffer));}exit(0);}// 父进程close(pipefd[0]);int status = 0;pid_t ret = waitpid(id, &status, 0);assert(ret > 0);(void)ret;cout << "signumber: " << (status & 0x7F) << endl;close(pipefd[1]);return 0;
}

结果演示

 alarm函数

​ 

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发送14号信号(SIGALARM),该信号默认的处理动作是终止当前进程。

这个函数的返回值是0或者是以前设定闹钟时间剩下的秒数。

打个比方,中午吃完饭午睡,我设定了一个30分钟的闹钟,但是在20分钟之后,我被外面的装修声给吵醒了,而我又不在想睡了,但是闹钟还有10分钟才会响,这10分钟就是设定闹钟时间剩下的秒数。

示例代码

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main()
{alarm(1);int count = 0;while(true) {cout << "count: " << count++ << endl;}return 0;
}

结果演示

这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。

理解软件条件给进程发送信号

操作系统先识别到某种软件条件触发或者不满足,之后操作系统就会构建相应的信号,并且发送给指定的进程。

🔥由硬件条件产生信号 

硬件异常是硬件通过某种检测方式识别到有错误并通知操作系统,然后操作系统会向当前进程发送适当的信号。例如当前进程中有除0错误,CPU内部的状态寄存器中的溢出标记位就会由0置1,操作系统识别到溢出标记位为1,立即找到该进程并且向该进程发送8号信号(SIGFPE),进程收到信号会在合适的时候进行处理。再比如当前进程访问了非法地址,寄存器中的MMU(内存管理单元)就会产生异常,操作系统就会识别到这个异常,并且立即找到该进程,向该进程发送11号信号(SIGSEGV),进程收到信号会在合适的时候进行处理。

示例代码

野指针异常

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signum)
{sleep(1);cout << "捕捉到一个信号: " << signum << endl;
}int main()
{signal(SIGSEGV, handler);int *p= nullptr;*p = 100;return 0;
}

结果演示

​ 

先要明确一个点:一旦出现硬件异常,进程是不一定会退出的,但是一般默认是退出,因为进程即使不退出,我们也做不了什么。

在我们的结果演示中,虽然我们捕捉到了这个信号,但是为什么它会死循环呢?

因为寄存器中的异常一直没有被解决,操作系统就会一直识别到这个异常,也就一直会给该进程发送相应的信号。

✨总结 

  1. 所有信号的产生,最终都要有操作系统来进行执行,因为操作系统是进程的管理者
  2. 信号不是立即被处理的,是在合适的时机,进程会执行处理
  3. 信号不是被立即处理的,那么就需要被保存起来,信号会被保存在进程PCB中的信号位图上

 

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

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

相关文章

vue核心模块源码解析

响应式原理 Object.definePropertysetterProxy var count 1 var state {} Object.defineProperty(state , count,{get(){return count},set(val){count val} }) //弊端&#xff1a;不能主动监听到对象属性的新增或者删除&#xff0c;add/deleteref和reactive 声明响应式数…

翻译《The Old New Thing》- How do I mark a shortcut file as requiring elevation?

How do I mark a shortcut file as requiring elevation? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20071219-00/?p24103 Raymond Chen 2007年12月19日 如何将快捷方式标记为需要提升权限 简要 文章介绍了如何通过设置SLDF_RUNAS_US…

许冉直播不治本,京东需要刘强东

图片&#xff5c;影视剧《纸牌屋》剧照 ©自象限原创 作者丨艾AA 编辑丨薛黎 这届618&#xff0c;消费者的热情还未显现&#xff0c;商家的怒火先爆发了。 5月21日京东618开幕次日&#xff0c;多家图书社抵制618图书大促登上了热搜。此次争议与去年双十一京东采销与电…

移动端h5适配方案:媒体查询、编写js、lib-flexible、vw、rem和vw单位换算

文章目录 各种方案第二种&#xff1a;动态设置html的font-size媒体查询mediajs 第三种&#xff1a;vw方案 rem、vw单位换算手动根据设计稿进行计算lessvs code 插件 各种方案 1&#xff09;百分比设置&#xff08;X&#xff09;【百分比很难统一&#xff0c;不推荐】 2&#xf…

安卓高级控件(下拉框、列表类视图、翻页类视图、碎片Fragment)

下拉框 此小节介绍下拉框的用法以及适配器的基本概念&#xff0c;结合对下拉框Spinner的使用说明分别阐述数组适配器ArrayAdapter、简单适配器SimpleAdapter的具体用法与展示效果。 下拉框控件Spinner Spinner是下拉框控件&#xff0c;它用于从一串列表中选择某项&#xff0…

Java与GO语言对比分析

你是不是总听到go与java种种对比&#xff0c;其中在高并发的服务器端应用场景会有人推荐你使用go而不是 java。 那我们就从两者运行原理和基本并发设计来对比分析&#xff0c;看看到底怎么回事。 运行原理对比 java java 中 jdk 已经帮我们屏蔽操作系统区别。 只要我们下载并…

Android中华为手机三态位置权限申请理解

博主前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住也分享一下给大家&#xff0c; &#x1f449;点击跳转到教程 前言&#xff1a; 使用的华为MATE 20,Android10的系统。 <!--精准定位权限&#xff0c;如&#xff1a;…

“闻起来有股答辩的味道”,答辩到底是什么味?

“闻起来有股答辩的味道”&#xff0c;答辩到底是什么味&#xff1f; 一位名叫“小鸡全家桶”的作者虚构了这样一个学校故事&#xff0c;故事说&#xff0c;由于学生的考试试卷印刷得特别模糊&#xff0c;导致里面的插图根本看不清&#xff0c;学生感到懵逼&#xff0c;监考老…

【区块链】智能合约漏洞测试

打开Ganache vscode打开智能合约漏洞工程 合约内容 pragma solidity >0.8.3;contract EtherStore {mapping(address > uint) public balances;function deposit() public payable {balances[msg.sender] msg.value;emit Balance(balances[msg.sender]);}function with…

深度学习之基于Tensorflow卷积神经网络(CNN)实现猫狗识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在人工智能和深度学习的热潮中&#xff0c;图像识别是一个备受关注的领域。猫狗识别作为图像识…

权限维持--windows

隐藏文件 ①文件属性隐藏 如何排查&#xff1a; 使用dir命令无法看到有特殊属性的文件需使用/a ②真隐藏 相当于给原本的文件增加系统文件属性、存档文件属性、只读文集属性、隐藏文件属性 如何排查&#xff1a; 取消受保护的操作系统文件 ③利用ADS隐藏 使用数据流 echo &…

Docker(四) 文件和网络

1 Dockerfile 1.1 什么是Dockerfile Dockerfile是一个文本文件&#xff0c;包含一系列命令&#xff0c;这些命令用于在 Docker 镜像中自动执行操作。Dockerfile 定义了如何构建 Docker 镜像的步骤和所需的操作。 Dockerfile 中包含的命令可以设置和定制容器的环境&#xff0c;…

求第 N 个泰波那契数 | 动态规划

1.第 N 个泰波那契数 题目连接&#xff1a;1137. 第 N 个泰波那契数 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2给你整数 n&#xff0c;请返回第 n 个泰波那契数 Tn 的值。 2.什么是动态规划 在解决这道问题之前…

2024年5月份最新独角数卡使用USDT详细小白教程

直观配套视频教程 2024年5月份最新独角数卡安装及USDT使用详细小白教程 1、创建服务器 Centos或者Ubuntu2、宝塔面板开心版安装寶塔 Linux 面版 8.0.5 開心版 - 2024年1月12日 - 开心专区 - 异次元 - Powered by Discuz!Centos安装命令&#xff08;默认安装是 8.0.1 直接在线升…

张大哥笔记:改变自己,才是改变一切的开始

人往往有一种惰性&#xff0c;总喜欢把希望寄托于别人&#xff01;比如会将注意力投向外部因素如环境、他人或命运从而期望为我们的生活带来突破和转机。但现实往往是残酷的&#xff0c;不会发生任何改变的&#xff01;真正的改变来自于自己&#xff0c;自我革新才是改变整个局…

89.网络游戏逆向分析与漏洞攻防-游戏技能系统分析-游戏中使用的哈希算法逆向分析

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

第九节 设计 Starter 不能忽视的细节

我们要定义一个生产可用的 Starter &#xff0c;还有几个细节&#xff0c;我们必须要关注。这些细节可以很好的帮助我们写出更优秀的 Starter 一、maven 包依赖 每一个 Starter&#xff0c;可以理解为一个 Jar&#xff0c;这个 Jar 包&#xff0c;如果被其他应用引用&#xf…

贪心算法--区间调度问题

贪心算法 引言 贪心算法是一种简单而有效的算法设计技巧&#xff0c;在解决一些优化问题时具有广泛的应用。其基本思想是通过每一步的局部最优选择&#xff0c;最终达到全局最优解。贪心算法通常不会回溯之前的决策&#xff0c;而是根据当前状态作出最优决策&#xff0c;因此…

面试准备-八股【面试准备】

面试准备-八股【面试准备】 Java基础解决hash冲突的方法try catch finallyException与Error的包结构OOM你遇到过哪些情况&#xff0c;SOF你遇到过哪些情况线程有哪些基本状态?Java IO与 NIO的区别堆和栈的区别对象分配规则notify()和notifyAll()有什么区别&#xff1f;sleep()…

Docker(四)容器相关操作及问题处理

目录 一、进入、退出容器操作 二、查看Docker 容器的配置文件 方法一&#xff1a;进入docker容器内进行查看 方法二&#xff1a;通过数据卷挂载方式查看配置文件 方法三&#xff1a;使用Docker可视化工具查看配置文件 三、容器与宿主机时间同步 方法一&#xff1a;创建启…