【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)的前缀和、最大值、最小值、最大公约数…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...