信号(上)

本节目标:

1. 掌握Linux信号的基本概念
2. 掌握信号产生的一般方式
3. 理解信号递达和阻塞的概念,原理。
4. 掌握信号捕捉的一般方式。
5. 重新了解可重入函数的概念。
6. 了解竞态条件的情景和处理方式
7. 了解SIGCHLD信号, 重新编写信号处理函数的一般处理机制

 首先声明:这里所讲的信号与上文的信号量毫无关联。

目录

1. 信号是什么?

2. 信号的处理过程 

注意:

3. 信号的准备知识  

3.1 信号的种类

3.2 信号的行为

core dump(核心转储) 

3.3  常见信号处理方式  

4. 信号的一生

4.1 信号的产生 

方法1--kill命令​编辑

方法2--键盘键入 

方法3-- 系统调用

1. kill 向任意进程发送任意信号

2. raise 向自己发送任意信号

3. abort 向自己发送六号信号 

方法四--由软件条件产生信号

方法五--硬件异常产生信号 

4.2 信号的保存 

4.2.1 pending、block与handler 

4.2.2 sigset_t 

4.2.3 信号集操作函数 

4.2.4 sigprocmask与sigpending 

sigprocmask 

sigpending

4.2.5 实验 

实验1

代码 

 结果

 实验2

 代码

结果


 

1. 信号是什么?

什么是信号呢?

我们从生活引入。当你在房间里苦学c++的时候,妈妈喊你吃饭,你有两个选择--立即去吃饭、看完再吃饭,这个例子就可以完美的解释什么是信号。

这就是我们日常生活中的信号以及对信号的处理过程。

那么我们要讲的信号是什么呢? 

我们所将的信号同上例本质是一样的,不过是由某人发给进程,然后由进程进行一系列处理过程罢了。为什么用某人呢?因为这个某人并不确定,有可能是OS,有可能是用户,也有可能是其他进程乃至进程自己(这个就涉及信号的产生了)。

那么从上面的例子我们可以知道信号与进程之间有哪些关系呢?

1. 信号既然是由其他人发送的,会中断我们(进程),且这一信号是我们无法预料的,那么信号本身就是异步的,即信号是OS提供给用户(进程)向其他进程发送异步信息的一种方式,这一过程是并发的。

2. 信号发来我们就需要能够进行处理,这就说明我们(进程)具备认识信号的能力,并知道如何对该信号进行处理。因此进程不仅认识信号,而且还储存有对信号的处理方式。

3. 当我们(进程)接收到信号,如果我们在做更重要的事,我们可以先对信号进行保存,等到合适的时候再进行处理。因此进程应当具备保存信号,以及在合适时机处理信号的能力

2. 信号的处理过程 

下图是信号的处理过程时间轴示意图,我们后续的讲解线就是根据这个时间轴。

注意:

1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
2. Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C 这种控制键产生的信号。
3. 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。

3. 信号的准备知识  

3.1 信号的种类

我们先来看看都有哪些信号(kill -l命令)

这些信号大部分的行为都是终止进程,还有一部分是忽略,暂停等等。 

3.2 信号的行为

下图信号的行为,三十一个信号的行为都囊括其中。

core dump(核心转储) 

其他的信号默认处理动作都很好理解,但core动作是怎么回事,好像有点看不懂。

 

首先解释什么是Core Dump(核心转储)。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。 首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K: $ ulimit -c 1024

也就是说,我们的云服务器默认关闭核心转储功能,在Linux中,我们的g++/gcc默认是release版本,因此如果要生成core文件,除却上述的打开core dump功能外,还需要在编译时加-g选项。

有这样一种场景,倘若某种大型服务器出现异常,但由于大型服务器出错的第一件事不是找错,而是重新启动,因此我们有自启服务器的程序。如果服务器在无人发觉的情况下疯狂终止又疯狂重启,经过一段时间后,会生成无数core文件,这会导致空间爆炸的问题。因此在部分系统里,core文件的名字就叫做core,一个进程即便不断终止与重启,也只会有一个core文件。

此前我们在讲进程返回码时有一个标志位略过没有讲,现在看他刚刚好。

3.3  常见信号处理方式  

可选的处理动作有以下三种:
1. 忽略此信号。(即接收到该信号的进程对此信号进行忽略)
2. 执行该信号的默认处理动作。(每一个信号有自己的默认处理动作,如果用户没有对信号的处理动作进行自定义,那么就执行该默认处理动作)
3. 对信号进行捕捉。提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号(即用户对信号的处理动作进行自定义化)

比如:

SIGINT的默认处理动作是终止进程,我们现在对SIGINT信号进行捕捉,自定义其处理动作为打印一串字符。使用signal函数。

4. 信号的一生

4.1 信号的产生 

信号要想发给进程,首先当然要先产生,那么信号的产生方式有哪些呢?

我们先写一个正常情况下不会终止的进程。

#include<iostream>
#include<unistd.h>int main()
{while(true){sleep(1);pid_t pid=getpid();std::cout<<"process pid :"<<pid<<std::endl;}return 0;
}

方法1--kill命令

方法2--键盘键入 

记得我们之前使用的ctrl+c吗,它可以终止进程,但键盘可以输入的信号可不只有他

ctrl+\后的core dumped是什么呢?

方法3-- 系统调用

这里的系统调用一般有三种,我们挨个来看看。

1. kill 向任意进程发送任意信号

kill可以向任意进程发送任意信号,我们来试试吧。

我们看到实验成功了,不过这一实验有一些丑陋,大家可以使用父进程发送信号杀死子进程,同时记录当前进程状况。 

2. raise 向自己发送任意信号

3. abort 向自己发送六号信号 

相当于kill(getpid(),9)

方法四--由软件条件产生信号

首先这一方式我们熟知的有SIGPIPE,即管道读端关闭而写端还在写时,OS会向写端进程发送SIGPIPE强制杀死该进程。

还有我们并未接触过的SIGALARM,我们来看看。

我们来验证一下。

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时还余下的秒数。

闹钟是由OS发送的,而OS中的进程何其多,所以OS就需要对闹钟进行管理,先描述在组织。

方法五--硬件异常产生信号 

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程。再比如当前进程访问了非法内存地址,,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

因此我们平时写的程序崩溃了,就是硬件异常发送给我们进程信号了。

4.2 信号的保存 

欸你可能会疑惑,为什么信号的一生时间线中没有发送信号的过程呢?别急,我们对信号的一生填充一下。 

我们学习了上面的内容,应该已经明白了向进程发送信号的过程是由OS来做的,因为产生信号的方式全部是系统调用。那么OS是如何发送的呢?

我们知道,进程是承担系统资源的实体,那么信号既然要保存,自然也是存储在进程中的某个区域。那么存在哪呢?进程地址空间吗?

不是的,信号的保存是在pcb中的。信号本质并不属于进程,而是属于系统,但由于进程需要能够对信号及时响应,因此进程需要保存信号,且进程要可以及时察觉到信号的变化,因此将信号保存在pcb中。

4.2.1 pending、block与handler 

那么问题来了,信号在pcb中要怎么保存呢?

由上图我们可知,信号在pcb中的存储是三张表, pending(未决信号)、block(阻塞信号)、handler(信号处理函数)。

注意,信号的屏蔽与忽略是截然不同的,信号的屏蔽是指信号始终处于未决,不对其进行处理;信号的忽略本身就是对信号的处理,即信号已然递达

这里要注意,我们一开始就说,只谈1-31个信号,因此这里的位图都是三十二个比特位,1-31位标识信号。

我们来看看三张表的作用 

我们之前有一个案例代码,其中有对信号进行捕捉,其实就是让我们的捕捉函数覆盖了该信号的原处理函数。

看到这里,有没有明白信号是如何发送给进程的呢?

没错,就是OS对pending表进行写入,进程会时刻监视这三张表,并做相应处理。

我们之前所讲信号的产生,无论是命令行输入命令,还是程序代码调用系统调用,都是让OS帮我们向进程内写入信号。那么系统调用究竟是什么呢?系统调用其实就是写在系统内的函数,只有系统有权限使用。OS内会有一个函数指针数组,其内放的全部都是系统方法。

4.2.2 sigset_t 

每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。下一节将详细介绍信号集的各种操作。 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

4.2.3 信号集操作函数 

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。


函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置为1,表示 该信号集的有效信号包括系统支持的所有信号。


注意,在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。


这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。

注意:这里的几个函数仅仅是对创建出的对象进行操作,要设置入进程内需要其他的函数,

4.2.4 sigprocmask与sigpending 

sigprocmask 

调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

 如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。(即set为要设置入进程block位图的信号集,oset为输出型参数,将会记录进程原block位图信号集)假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

 如果调用sigprocmask解除了若干个对未决信号的阻塞,那么在sigprocmask返回前,OS会立即将其中一个信号递达。

sigpending
#include <signal.h>
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。

4.2.5 实验 

接下来我们将使用上面的函数做一个实验

实验1

将2,3信号屏蔽(sigprocmask)

向进程发送信号,打印pending位图。

代码 
#include<iostream>
#include<unistd.h>
#include<signal.h>//打印当前pending信号集
void printsig(sigset_t p)
{std::cout<<getpid()<<"  ";for(int i=31;i>0;i--){if(sigismember(&p,i))std::cout<<"1";elsestd::cout<<"0";}std::cout<<std::endl;
}int main()
{sigset_t s,p;//创建信号集sigemptyset(&s);//清空信号集sigaddset(&s,2);//向信号集内添加有效信号2sigaddset(&s,3);//添加3sigprocmask(SIG_BLOCK,&s,nullptr);//这里我们不需要记录原信号屏蔽字while(true){sleep(1);sigpending(&p);//获取当前进程pending信号集printsig(p);//打印pending信号集}return 0;
}
 结果

 

 实验2

将所有信号屏蔽(sigprocmask)

向进程发送信号,打印pending位图。

 代码
#include<iostream>
#include<unistd.h>
#include<signal.h>//打印当前pending信号集
void printsig(sigset_t p)
{std::cout<<getpid()<<"  ";for(int i=31;i>0;i--){if(sigismember(&p,i))std::cout<<"1";elsestd::cout<<"0";}std::cout<<std::endl;
}int main()
{sigset_t s,p;//创建信号集sigemptyset(&s);//清空信号集for(int i=1;i<32;i++){sigaddset(&s,i);//向信号集内添加有效信号}sigprocmask(SIG_BLOCK,&s,nullptr);//这里我们不需要记录原信号屏蔽字while(true){sleep(1);sigpending(&p);//获取当前进程pending信号集printsig(p);//打印pending信号集}return 0;
}
结果

 

下篇我们来看信号的处理。

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

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

相关文章

ChatGPT基本原理详细解说

ChatGPT基本原理详细解说 引言 在人工智能领域&#xff0c;自然语言处理&#xff08;NLP&#xff09;一直是研究的热点之一。随着技术的发展&#xff0c;我们见证了从简单的聊天机器人到复杂的语言模型的演变。其中&#xff0c;ChatGPT作为一项突破性技术&#xff0c;以其强大…

2004NOIP普及组真题 2. 花生采摘

线上OJ&#xff1a; 【04NOIP普及组】花生采摘 核心思想&#xff1a; 1、本题为贪心即可。 2、因为本题严格限制了顺序&#xff0c;所以先把每个节点的花生数量按降序排序。然后逐一判断下一个花生是否需要去采摘即可 3、每一次采摘完&#xff0c;记录耗时 t 以及采集的花…

基于web的垃圾分类回收系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;用户管理&#xff0c;公告管理&#xff0c;运输管理&#xff0c;基础数据管理 用户账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;运输管理&#xff0c;公告…

pyqt QlineEdit内部增加按钮方法

pyqt QlineEdit内部增加按钮方法 def addButton(self,lineEdit):btn QtWidgets.QPushButton("")icon1 QtGui.QIcon()icon1.addPixmap(QtGui.QPixmap(":/image/images/th.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)btn.setIcon(icon1)btn.setStyleShe…

全光谱led灯的危害有哪些?曝光低质量全光谱led灯产生的四大风险

眼睛是人类获取信息最重要的感官器官之一&#xff0c;而近视则会导致视力模糊&#xff0c;进而影响学习效果和生活品质。因此&#xff0c;如何保护眼睛&#xff0c;尤其是在学习和使用电子设备时&#xff0c;成为了一个迫切需要解决的问题。然而在护眼领域上&#xff0c;护眼台…

SCAU 数据结构 实验六 排序算法

![[Pasted image 20240 8638 直接插入排序 Description 用函数实现直接插入排序&#xff0c;并输出每趟排序的结果. 输入格式 第一行&#xff1a;键盘输入待排序关键的个数n 第二行&#xff1a;输入n个待排序关键字&#xff0c;用空格分隔数据 输出格式 每行输出一趟排序…

十三、resultMap解析

分为两部分&#xff1a;解析和使用 解析 1.解析XML的时候单独解析所有的resultMap标签&#xff0c;封装成ResultMap对象存入configuration中 2.解析XML中的SQL语句&#xff0c;封装MappedStatement对象&#xff0c;这里会根据SQL的返回类型是resultMap还是resultType做处理。如…

C语言 | Leetcode C语言题解之第133题克隆图

题目&#xff1a; 题解&#xff1a; struct Node** visited; int* state; //数组存放结点状态 0&#xff1a;结点未创建 1&#xff1a;仅创建结点 2&#xff1a;结点已创建并已填入所有内容void bfs(struct Node* s) {if (visited[s->val] && state[s->val] 2…

Python Lambda函数的应用实例教程

在Python编程中&#xff0c;lambda函数是一种简洁且强大的工具&#xff0c;用于创建小型匿名函数。它们在需要快速定义简单函数时特别有用。本文将详细介绍lambda函数的语法及其多种应用实例&#xff0c;帮助读者更好地理解和使用lambda函数。 一、lambda函数的基本概念 1.1 什…

c++(内存分配,构造,析构)

#include <iostream>using namespace std; class Per { private:string name;int age;double *height;double *weigh; public://无参构造Per(){cout << "Per::无参构造" << endl;}//有参构造Per(string name,int age,double height,double weigh):…

【TB作品】 51单片机8x8点阵显示滚动汉字仿真

功能 题目5基于51单片机LED8x8点阵显示 流水灯 直接滚动显示HELLO 直接滚动显示老师好 代码 void main( void ) {/** 移位后&#xff0c;右边的是第一个595&#xff0c;接收0X02&#xff0c;显示出0X02* 移位后&#xff0c;左边的是第2个595&#xff0c;接收0Xfe&#xff0c…

创建常规DLL的动态链接库

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系本人将于及时删除 【例9.3】创建一个MFC 常规DLL的动态链接库Areadll&#xff0c;在该动态链接库中添加一个导出类CArea&#xff0c;通过该类获取正方形和圆的面积。 (1) 使用“MFC动态链接…

Allegro器件角度倾斜如何回正?

Allegro器件角度倾斜,坐标含有小数点调整为45度整数倍的方法 Allegro器件角度倾斜回正的方法。 在用Allero进行PCB设计过程中,有时候由于误操作;或者刚开始器件需要非45度整数倍的角度,后又需要调整为整数倍的角度。器件角度倾斜含有小数点调整为45度整数倍的方法。 1、如…

Arduino网页服务器:如何将Arduino开发板用作Web服务器

大家好&#xff0c;我是咕噜铁蛋&#xff01;今天&#xff0c;我将和大家分享一个有趣且实用的项目——如何使用Arduino开发板搭建一个简易的网页服务器。通过这个项目&#xff0c;你可以将Arduino连接到互联网&#xff0c;并通过网页控制或查询Arduino的状态。 一、项目背景与…

vue实现pdf下载——html2canvas

html2canvas 官方文档https://html2canvas.hertzen.com/getting-started html2canvas 的原理是通过遍历DOM树,将每一个HTML元素转化为Canvas对象,并叠加到一起形成一张完整的图片或者PDF文件。 1. 安装插件 npm install html2canvas jspdf --save 2.使用&#xff08;页面已经…

git 的基本操作 Master and branch的版本合并 @ VS 1019

前言&#xff1a; 在VS 2019有git 的可视化管理,但&#xff0c;感觉微软其实就是在git上包了一层。版本冲突后&#xff0c;还是要靠git 的命令行代码搞。本文记录了一次&#xff0c;branch和master的版本合并的过程。作为&#xff0c;后续的参考。 【注意&#xff0c;这个是一…

【二进制部署k8s-1.29.4】十三、metrics-server的安装部署

文章目录 简介 一.metrics-server的安装 简介 本章节主要讲解metrics-server的安装&#xff0c;metrics-server主要是用于采集k8s中节点和pod的内存和cpu指标&#xff0c;在观察几点和pod的实时资源使用情况还是比较有用的&#xff0c;如果需要记录历史信息&#xff0c;建议采用…

运行编译openjdk12-33

编译环境 ubuntu20 Ubuntu里用户可以自行选择安装GCC或CLang来进行编译&#xff0c;但必须确保最低的版本为GCC 4.8或者CLang 3.2以上&#xff0c;官方推荐使用GCC 7.8或者CLang 9.1来完成编译。 源码 https://github.com/openjdk/jdk/tree/jdk-12%2B33 安装gcc sudo apt…

使用neural_network_console训练模型并导出.nnb文件应用于索尼spresense

一.创建数据集 首先你需要一个csv标记的数据集 然后我们使用neural_network_console将数据集进行处理 dataset->create dataset->image 用户可以通过该界面选择源目录&#xff08;Source Dir&#xff09;&#xff0c;输出目录&#xff08;Output Dir&#xff09;&…

哈希表、HashMap\Map-1657. 确定两个字符串是否接近

题目链接及描述 1657. 确定两个字符串是否接近 - 力扣&#xff08;LeetCode&#xff09; 题目分析 今日看到这道题目&#xff0c;乍一看觉得非常熟悉&#xff0c;对于将一个字符串转换为另一个字符串的题目之前做过一些。分析题目&#xff0c;题目中所述就是两种操作&#xff…