【linux】进程信号——信号的产生
进程信号
- 一、信号概念
- 1.1 信号理解
- 二、产生信号
- 2.1 通过键盘产生信号
- 2.2 捕捉信号自定义signal
- 2.3 系统调用接口产生信号
- 2.3.1 向任意进程发送任意信号kill
- 2.3.2 给自己发送任意信号raise
- 2.3.3 给自己发送指定信号abort
- 2.3.4 理解
- 2.4 硬件异常产生信号
- 2.4.1 除0异常
- 2.4.2 野指针异常
- 2.4.3 总结
- 2.5 软件条件产生信号
- 2.5.1 定时器软件条件alarm
- 2.5.2 alarm的深层理解
- 2.6 核心转储Core Dump
一、信号概念
首先要知道查看信号的指令:kill -l
通过观察发现没有0和32和33号信号,只有1 ~ 31, 34 ~ 64的信号。我们把
【1 ~ 31】叫做普通信号
【34 ~ 64】叫做实时信号
1.1 信号理解
在日常生活中有很多的信号,例如红路灯、裁判哨声、闹钟,这些都是给我们人类看的,当这些场景触发的时候,我们人类立马就知道要做什么,并且产生行动。
而我们为什么能识别这些信号呢?
我们对特定事件的反应,是被教育的结果,本质是我们记住了。
还有一种情况:当信号传来的时候我们可能正在做更重要的事情,所以不一定会立马处理信号,此时信号的产生和我们正在做的事情称为异步。
我们把信号传递过来到处理之前的这段时间称为时间窗口。在时间窗口我们必须得记住这个信号。
在我们处理信号的时候,我们可以有不同的处理方式,比方说我们早上听到闹钟响起,会直接起床,这里叫做默认动作,而听到闹钟后先做十个俯卧撑再起床,这叫做自定义动作,当然我们也可以不理会闹钟,这叫做忽略动作。
把概念迁移到进程中:
1️⃣ 进程能认识信号并产生动作是因为程序员编码完成的。
2️⃣ 当进程收到信号,进程可能在执行更重要的代码,所以信号不一定被立即处理。 所以进程要有对信号的保存能力。
3️⃣ 进程在处理信号的时候,一般有三种动作:默认、自定义、忽略,有个专业名词叫:信号被捕捉。
那么信号是怎么被捕捉的呢?
信号发给进程,而进程需要保存到PCB中,那么如何保存呢?
是否收到信号具有原子性,只有两太,而我们知道普通信号是1 ~ 31,所以我们可以在PCB中创建一个unsigned int的变量,有32个比特位,刚好用这些比特位来标记接收的信号。比特位的位置代表信号的编号。0表示没有,1表示有。
所以发送信号的本质:修改PCB中的信号位图。
而只有操作系统才能修改PCB,发信号本质就是给操作系统发信号,那么操作系统就必须要提供发送信号、处理信号的相关调用接口,我们以前的kill指令就是调用了底层接口。
二、产生信号
2.1 通过键盘产生信号
ctrl + c
:是一个组合键,OS将它解释成3号信号(SIGQUIT)
我们知道每个信号有三种处理动作,那么怎么查看信号的默认动作是什么呢?
ctrl + \
:是一个组合键,OS将它解释成2号信号(SIGINT)
指令:man 7 signal
可以看到二号信号的动作是:Term(终止),描述是:Interrupt from keyboard(从键盘中断)
2.2 捕捉信号自定义signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);RETURN VALUE
signal() returns the previous value of the signal handler, or SIG_ERR on error.
In the event of an error, errno is set to indicate thecause.
参数说明:
signum
:指定的信号。
handler
:设置自定义动作,就是一个回调函数,函数内我们可以自定义我们想要的动作。
void handler(int sig)
{std::cout << "进程捕捉到信号,编号是:" << sig << std::endl;
}int main()
{signal(2, handler);while(true){std::cout << "in service: " << getpid() << std::endl;sleep(1);}return 0;
}
这里要注意是signal函数的调用,不是handler的调用。这个函数仅仅是对2号信号的捕捉,并不代表被调用了。只有收到对应信号才会被调用。
可以看到发送2号信号并不能导致进程被终止了。
这里有个问题:如果我们对所有的信号都进行了信号捕捉,那我们是不是就写了一个不会被异常终止或者用户杀掉的进程呢?我们通过代码来验证一下!
void Catchsig(int sig)
{std::cout << "捕捉到了一个信号: " << sig << " pid: " << getpid() << std::endl;
}int main()
{for(int i = 1; i <= 31; ++i)signal(i, Catchsig);while(1) sleep(1);return 0;
}
操作系统的设计者也考虑到了上述的情况,所以就让 9 号信号无法被捕捉,9 号信号是管理员信号。
2.3 系统调用接口产生信号
2.3.1 向任意进程发送任意信号kill
#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);RETURN VALUE
On success (at least one signal was sent), zero is returned.
On error, -1 is returned, and errno is set appropriately.
参数说明:
pid
:目标进程的pid。
sig
:向目标进程发送指定信号。
所以我们可以自己写一个kill的进程。
// mykill.cc
void Usage(const std::string& proc)
{std::cout << "\nerror, format: " << proc << " pid sig" << std::endl;
}int main(int argc, char* argv[])
{if(argc != 3){Usage(argv[0]);exit(1);}pid_t pid = atoi(argv[1]);int sig = atoi(argv[2]);kill(pid, sig);return 0;
}
再写一个永远运行的进程,让mykill进程来杀死它。
// myproc.cc
int main()
{while(true){std::cout << "please kill me, my pid: " << getpid() << std::endl;sleep(1);}return 0;
}
2.3.2 给自己发送任意信号raise
#include <signal.h>int raise(int sig);
RETURN VALUE
raise() returns 0 on success, and nonzero for failure.
sig就是发送的信号。
int main(int argc, char* argv[])
{int cnt = 0;while(++cnt < 10){std::cout << "cnt: " << cnt << std::endl;sleep(1);if(cnt == 5){raise(9);}}return 0;
}
其实这里raise也可以写成:kill(getpid(), 9)
2.3.3 给自己发送指定信号abort
#include <stdlib.h>void abort(void);
int main(int argc, char* argv[])
{int cnt = 0;while(++cnt < 10){std::cout << "cnt: " << cnt << std::endl;sleep(1);if(cnt == 5){abort();}}return 0;
}
而发送的指定信号就是6号(SIGABRT)。
所以这里abort也可以自己用kill封装:kill(getpid(), 6)
2.3.4 理解
我们可以看到进程收到的大部分信号,默认处理动作都是终止进程。
信号的不同代表了不同的事件,但是它们的处理动作可以一样。
2.4 硬件异常产生信号
2.4.1 除0异常
信号的产生不一定需要用户手动发送。
int main(int argc, char* argv[])
{while(true){std::cout << "in service" << std::endl;sleep(1);int a = 1;a /= 0;}return 0;
}
这里为什么/0
会导致进程终止呢?
因为进程会收到来自操作系统的8号信号(SIGFPE)。
我们可以用前面学的捕捉信号进行验证:
void handler(int sig)
{std::cout << "捕获到信号:" << sig << std::endl;sleep(1);
}int main(int argc, char* argv[])
{signal(8, handler);while(true){std::cout << "in service" << std::endl;sleep(1);int a = 1;a /= 0;}return 0;
}
这次我们把/0
放到循环前
可以看到这里还是循环打印,好像一直在调用捕获函数。
这里就要先知道操作系统是如何得知要给进程发送八号信号的呢?(怎么知道的/0
)
这里
1/0
会被放进CPU中的寄存器中,0相当于无穷小的数字,这样就会导致CPU的状态寄存器中的溢出标记由0变为1。这样就发生了CPU的运算异常,操作系统就会知道(操作系统是软硬件的管理者),然后向目标进程发送8号信号。而收到信号进程不一定退出,没有退出说明还会被继续调度。而寄存器的内容属于当前进程上下文信息,但是进程没有能力把状态标识符置为0,所以进程切换的时候就有无数次的状态寄存器被保存和恢复(上下文信息),每次恢复就会发送信号。导致捕获函数一直被调度。
2.4.2 野指针异常
int main(int argc, char* argv[])
{while(true){std::cout << "in service" << std::endl;sleep(1);int *ptr = nullptr;*ptr = 2;}return 0;
}
这里为什么空指针会导致进程终止呢?
因为进程会收到来自操作系统的11号信号(SIGSEGV)。
利用signal函数证明:
void handler(int sig)
{std::cout << "捕获到信号:" << sig << std::endl;sleep(1);
}int main(int argc, char* argv[])
{signal(11, handler);while(true){std::cout << "in service" << std::endl;sleep(1);int *ptr = nullptr;*ptr = 2;}return 0;
}
那么操作系统是如何知道发生了野指针情况呢?
我们知道指针本质上是个虚拟地址,而我们知道虚拟地址需要转化成物理地址,通过页表+MMU,MMU是集成在CPU中的硬件,通过访问通过页表的内容形成物理地址,再访问物理地址。而我们解引用空指针,MMU就会发生异常,然后被操作系统得知,然后发送信号给进程。
2.4.3 总结
大部分信号会导致进程退出,我们需要捕获这个异常,因为异常的不同代表不同的原因导致的,进而让我们能够追溯原因,让我们能够反向定位问题。比如说我们收到的信号是段错误,我们就会想到可能是野指针,收到浮点数溢出报错就会想到可能是除0错误。
2.5 软件条件产生信号
我们以前学过管道:【linux】进程间通信——管道通信
当两个进程正在利用管道进行读写,此时把读端关闭,操作系统就会终止掉写进程(发送SIGPIPE信号)。这种情况称为软件条件产生信号。
2.5.1 定时器软件条件alarm
#include <unistd.h>unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号(14), 该信号的默认处理动作是终止当前进程。
int main(int argc, char* argv[])
{alarm(1);int cnt = 0;while(true){std::cout << cnt++ << std::endl;}return 0;
}
这个进程的目的就是统计1s
的时间内计算机能将数据叠加多少次。
而如果我们这么写:
int cnt = 0;void handler(int sig)
{std::cout << "捕获到信号:" << sig << std::endl;std::cout << "cnt: " << cnt << std::endl;
}int main(int argc, char* argv[])
{signal(14, handler);alarm(1);while(true){++cnt;}return 0;
}
从这里就可以看到IO跟不IO的效率差距相当大。
而只打印了一次说明是收到了一个SIGALRM信号,闹钟响过一次就不再响了。
那如果我们想让它一直打印呢?
相当于在handler内部又要调用handler。这样就类似于sleep(1)
unsigned int alarm(unsigned int seconds);
当然alarm也可能提前响起。比方说有可能手动发送SIGALRM,他就会返回剩余多少时间。当我们把seconds设置为0,表示取消闹钟。
2.5.2 alarm的深层理解
我们知道每个进程都可能通过alarm接口设置闹钟,所以可能会存在很多闹钟,那么操作系统一定要管理起来它们。
先用一个结构体描述每个闹钟,其中包含各种属性:闹钟还有多久结束(时间戳)、闹钟是一次性的还是周期性的、闹钟跟哪个进程相关、链接下一个闹钟的指针…… 然后我们可以用数据结构把这些数据连接起来。
接下来操作系统会周期性的检查这些闹钟,当前时间戳和结构体中的时间戳进行比较,如果超过了,说明超时了,操作系统就会发送SIGALRM给该进程。
为了方便检查是否超时,可以利用堆结构来管理。
2.6 核心转储Core Dump
核心转储:
当进程出现异常的时候,我们可以将该进程在对应时刻的内容数据保存到磁盘上,文件名通常是 core。
这里的Term和Core都表示进程退出,Trem表示正常结束,操作系统不会做额外的工作,如果是Core退出,我们暂时看不到明显的现象,如果想要看到,我们可以打开一个选项:ulimit -a
(可以看到操作系统给用户所设置的资源上限)
可以看到第一行core file size的大小为0,因为云服务器默认关闭了core file
这个选项。
如果我们想修改我们就可以用后边的参数进行修改(-c)。ulimit -c
打开了以后我们继续解引用空指针:
可以发现比以前多了一点内容。在查看当前目录:
多了一个core文件
我们把core dumped叫做核心转储,core文件后面的数字就是问题进程的pid。
那么为什么要有核心转储?
我们需要知道程序为什么崩溃,在哪崩溃?而核心转储就是为了支持我们进行调试。
那么如何调试呢?
第一步先编译的时候带上-g
选项
第二步使用gdb调试
第三步直接输入core-file core.17633
从结果可以看出代码终止的原因是收到了11号信号,引发了段错误。在mykill.cc的17行。
我们把这种处理错误的方法叫做事后调试
总结一下:当程序出现异常,我们先确定是几号信号,然后man 7 signal查看是core还是Trem,如果是core,直接打开核心转储,然后gdb调试直接定位错误。
相关文章:

【linux】进程信号——信号的产生
进程信号一、信号概念1.1 信号理解二、产生信号2.1 通过键盘产生信号2.2 捕捉信号自定义signal2.3 系统调用接口产生信号2.3.1 向任意进程发送任意信号kill2.3.2 给自己发送任意信号raise2.3.3 给自己发送指定信号abort2.3.4 理解2.4 硬件异常产生信号2.4.1 除0异常2.4.2 野指针…...
部署OpenStack
部署 1. 环境配置 配置主机名 使用CRT软件连接controller节点和compute节点,用户名默认为root,密码默认为000000。连接上之后,使用linux命令修改节点主机名。 [rootcontroller ~]# hostnamectl set-hostname controller [rootcontroller …...

Java 运算符与类型转化
Java 运算符与类型转化 1 算术运算符 Java中的算术运算符主要有(加)、-(减)、*(乘)、/(除)、%(求余),它们都是二元运算符。 2 自增和自减运算…...
《C++ Primer Plus》第18章:探讨 C++ 新标准(2)
移动语义和右值引用 现在介绍本书前面未讨论的主题。C11 支持移动语义,这就提出了一些问题:为何需要移动语义?什么是移动语义?C11 如何支持它?下面首先讨论第一个问题。 为何需要移动语义 先来看 C11 之前的复制过程…...

QML定时器
QML使用Timer使用定时器 Timer 计时器可用于触发操作一次,或以给定的间隔重复触发。 常用属性: interval 设置触发器之间的间隔(以毫秒为单位)。 默认间隔为 1000 毫秒。 repeat 设置重复,为真,则以指定的…...

第三章 opengl之纹理
OpenGL纹理纹理环绕方式纹理过滤多级渐远纹理加载和创建纹理stb_image.h生成纹理纹理的应用纹理单元纹理 用stb_image.h库,原先用SOIL库也可以实现。 可以为每个顶点添加颜色来增加图形的细节。但是想得到一个真实的图形,需要足够多的顶点,…...
【Flink】FlinkSQL中执行计划以及如何用代码看执行计划
FilnkSQL怎么查询优化 Apache Flink 使用并扩展了 Apache Calcite 来执行复杂的查询优化。 这包括一系列基于规则和成本的优化,例如: • 基于 Apache Calcite 的子查询解相关 • 投影剪裁 • 分区剪裁 • 过滤器下推 • 子计划消除重复数据以避免重复计算 • 特殊子查询重写,…...

从业者必读,一篇文章轻松掌握DevOps核心概念和最佳技能实践!
文章目录前言一. DevOps的定义及由来二. DevOps的价值三. devops工具有哪些3.1 devops工程师的硬实力3.2 devops工程师的软实力总结前言 大家好,又见面了,我是沐风晓月,本文是对DevOps的总结,一篇文章告诉你什么是DevOps. 对很多…...

2023爱分析·一体化HR SaaS市场厂商评估报告:北森
目录 1.研究范围定义 2. 一体化HR SaaS市场分析 3.厂商评估:北森 4.入选证书 1.研究范围定义 研究范围 伴随数字化转型走向深入,企业人力资源数字化也进入快速发展阶段,人力资源的价值也得到了重新审视和定义。政策层面,《…...
JAVA练习67-二叉树的中序遍历
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、题目-二叉树的中序遍历 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示:这里可以添加本文要记录的大概内容: 3月3日练习…...

【JeecgBoot-Vue3】第1节 源码下载和环境安装与启动
目录 一. 资料 1. 源码下载 2. 官网启动文档 二、 前端开发环境安装 2.1 开发工具 2.2 前后端代码下载 2.3 前端启动 Step 1:安装nodejs npm Step 2:配置国内镜像(这里选阿里) Step 3:安装yarn Step 4&…...

WebAPI
WebAPI知识详解day11.Web API 基本认知作用和分类什么是DOM?DOM树的概念DOM对象2.获取DOM对象通过css选择器获取dom对象通过其他方法获取dom3.设置/修改DOM元素内容方法1. document.write() 方法方法2. 对象.innerText 属性方法3. 对象.innerHTML4.设置/修改DOM元素…...
Shell命令——date的用法
date命令可以用来显示或设定系统的日期与时间。 一、显示系统的日期与时间 (1)如果date命令后面不加任何参数,则会按照固定的格式显示时间信息: 星期几 月份 日 时:分:秒 时区 年xjhubuntu:~/iot/tmp$ date Fri Mar 3 16:56:4…...

XSS跨站脚本
XSS跨站脚本XSS简介XSS验证XSS危害XSS简介 XSS被称为跨站脚本攻击(Cross-site scripting),由于和CSS(Cascading Style Sheets)重名,所以改为XSS。XSS主要基于javascript语言完成恶意的攻击行为,因为javascript可以非常灵活的操作html、css和…...

【强烈建议收藏:MySQL面试必问系列之慢SQL优化专题】
一.知识回顾 学习本篇文章之前呢,我们可以先看一下【强烈建议收藏:MySQL面试必问系列之SQL语句执行专题】,看完这篇文章再来学习本篇文章可谓是如虎添翼。好的,那我们也不讲太多的废话,直接开始。 二.如何做慢SQL查询优化呢&…...
windows,liunx,java实现apk解压,去签名、重新签名,重新打包apk
背景:由于项目需要,需要将apk包加入服务端返回的静态资源文件到apk中,形成离线apk包供下载安装。经过调查研究,决定使用apktool实现。关于apktool的资料可以参考 https://blog.csdn.net/quantum7/article/details/124060620 htt…...

【Linux】进程信号
🌠 作者:阿亮joy. 🎆专栏:《学会Linux》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉信号入门&…...

SpringBoot 集成Junit单元测试
学习文章: https://www.cnblogs.com/ysocean/p/6889906.html 开发工具: IDEA 2022.1.4 目录 目录 1. 概述 2. 实现步骤 2.1 maven导入依赖 2.2 随意代码演示(不推荐) 2.3 规范代码演示(推荐) 3. Junit相关其他注解 4. 注意事项 5. 结语 1. 概述 接触到Junit,…...

Android开发之简单控件
文章目录一 文本显示1.1 文本设置的两种方式1.2 常见字号单位类型2.2 设置文本的颜色三 视图基础3.1 设置视图的宽高3.2 设置视图的间距3.3 设置视图的对齐方式四常用布局4.1 线性布局LinearLayout4.2 相对布局RelativeLayout4.3 网格布局GridLayout4.4 滚动视图ScrollView五 按…...

树状数组讲解
树状数组 文章目录树状数组引入例题AcWing241.楼兰图腾思路代码AcWing 242. 一个简单的整数问题思路代码AcWing 244. 谜一样的牛思路代码总结引入 树状数组主要维护的是这样一个数据结构: tr[x]表示以x为终点的长度为lowbit(x)的前缀和、最大值、最小值、最大公约数…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...

消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...