<Linux系统复习>信号
创始人
2024-02-18 20:52:33
0

一、本章重点

1、什么是信号?

2、查看信号列表

3、信号捕捉

4、信号产生的5种方式

5、介绍CoreDump

6、信号处理的方式

7、如何理解信号产生到处理的过程

8、sigpending、sigprocmask、sigaction函数的使用

9、信号处理的时机

10、SIGCHLD信号

11、可重入函数

01 什么是信号?

生活中的信号:红绿灯、下课铃声、闹钟铃声等等。

当信号出现的时候,我们之前就知道信号该如何处理,并且不被要求立即处理该信号,因为有时我们正在处理重要的事情。(类比闹钟铃声)

技术应用角度的信号:当进程执行除0代码的时候是如何被操作系统终止的?本质是因为进程收到了信号,在合适的时机处理该信号的时候被操作系统终止。

02 查看信号列表

如何查看Linux中信号列表?

使用命令:kill -l

 需要知道的是没有32、33号信号,总共就62个信号。其中1-31是普通信号,34-64是实时信号。

03 信号捕捉

1、什么是信号捕捉?

简单来说就是改变信号的默认处理方式

介绍signal函数

 功能:对特定信号进行捕捉

使用方式:signal(要捕捉的信号编号,要执行的处理方式)

04 信号产生的5种方式

1、代码异常(如:除零错误、对野指针解引用)

2、命令行产生(如:kill -信号编号 pid、killall -信号编号 进程名)

3、键盘组合键产生(如:ctrl + c、ctrl + z、ctrl + \ ,分别对应2,20,3号信号)

4、系统调用

5、软件条件

代码演示

1、异常

#include
#include
#includeusing namespace std;int main()
{int cnt = 5;while(true){cout<<"I am a process"<

 错误信息和信号列表对比大概可以看出除零错误属于8号信号

用signal函数对8号信号进行捕捉,执行我们的自定义的处理方式

#include
#include
#includeusing namespace std;void handler(int signo)
{cout<<"收到了8号信号"<

 

2、命令行产生

①kill -信号编号 pid

#include
#include
#includeusing namespace std;int main()
{int cnt = 5;while(true){cout<<"I am a process"<

②killall -信号编号 进程名

#include
#include
#includeusing namespace std;int main()
{int cnt = 5;while(true){cout<<"I am a process"<

3、键盘组合键产生

①ctrl + c

②ctrl + z(强制当前进程转为后台,并使之停止)

jobs可以显示后台进程,fg + 后台编号可以将进程从后台放置到前台运行。

 

 ./test是前台运行该进程,./test &代表的是后台运行该进程

在后台运行进程时,解释器还能够解释命令,但由于都向同一个显示器打印,会造成互相干扰的情况,键盘组合键只能对前台进程有效,后台进程无效。

要想杀掉后台进程有两种方式:1、先fg 1 再 ctrl +c       2、kill -2 pid

③ctrl + \

如何知道ctrl + c、ctrl + z、ctrl + \分别给进程发的是什么信号?

答:对所有信号捕捉,然后再发送信号,信号捕捉函数就可以打印发送的信号。

#include
#include
#include
#includeusing namespace std;void handler(int signo)
{cout<<"进程收到了"<

 ctrl + c:向进程发送2号信号

ctrl + z:向进程发送20号信号

ctrl + \:向进程发送3号信号(注:有的朋友可能ctrl + \没有反映,具体原因可以百度解决)

上述代码,还证明了并不上所有信号都能够被捕捉,比如9号信号就不能被捕捉,因为所有信号都能够被捕捉,那么这个进程真就“刀枪不入”了,连操作系统也拿它没办法了。

4、系统调用产生

①kill

功能:向任意进程发送任意信号

 写一个kill命令 

#include
#include
#includeusing namespace std;int main(int argc,char** argv)
{if(argc!=3){cout<<"Usage: kill -信号编号 pid"<

②raise

功能:向当前进程发送任意信号

 

③abort

功能:向当前进程发送abort信号

 

 这个函数有点特殊,因为你就算捕捉了该信号还是会终止进程。

5、软件条件产生

1、管道的读端关闭,写端会收到SIGPIPE信号,处理该信号时被操作系统杀掉。

common.h

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PATH "./.fifo"
using namespace std;

 server.cc

#include "common.h"int main()
{umask(0);if(mkfifo(PATH,0666)!=0){cerr<<"mkfifo error"<0){buf[s] = '\0';cout<<"client->server: "<

client.cc

#include "common.h"void handler(int signo){cout<<"写端收到了"<

2、alarm函数

功能:自定义多少秒之后向进程发送一个alarm信号,该信号的默认处理方式是终止进程。

测试1秒钟服务器能够对cnt++多少次

#include
#include
#include
#includeusing namespace std;int cnt = 0;
void handler(int signo)
{cout<

05 介绍CoreDump

1、什么是CoreDump?(核心转储)

coredump是指当程序出错而异常中断时,OS会把程序工作的当前状态存储成一个coredump文件。然后我们可以通过该文件来定位异常的地方。

2、一般线上生产环境都不会自动打开CoreDump,需要我们手动打开。

ulimit -c 文件大小(一般1024的整数倍)

 

 3、试验一下CoreDump

#include
#include
#include
using namespace std;int main()
{cout<<"my pid is "<

发生段错误,生成了core.25647文件,这个25647是什么意思呢?

本质其实就是异常进程的pid

生成了core.25647,我们可以通过gdb来分析CoreDump文件,定位异常。

 使用core-file core.26293可以导入coredump文件,然后gdb就可对该文件进行分析。

分析结果:进程终止是因为收到了11号信号,异常的地方是在test.cc文件的第10行。

06 信号处理的方式

1、默认处理方式

一般是终止该进程,可通过man 7 signal查看各个信号的默认处理方式

2、忽略

什么都不处理,只是将该信号从收到设置为未收到(ignore的缩写)

3、自定义

可通过signal函数或者sigaction函数自定义处理方式。

07 信号屏蔽字&&信号未决表&&信号处理函数

task_struct中存在三张表,分别是block表、pending表、handler表。

 block是一个32位的位图结构,从上到下0代表该信号没被阻塞,1代表该信号被阻塞。

pedding也是一个32位的位图结构,从上到下0代表该信号没被收到,1代表该信号被收到。

handler是一个函数指针数组,从上到下依次为每个信号对应的处理方式,SIG_DFL代表默认处理方式,SIG_IGN代表忽略。

pedding表说白了就是记录进程收到了哪些信号。

block表为了防止信号被处理而设计的,当某个信号被阻塞后,该信号就是递达了它都不会被处理,直到它被解除阻塞才能够被处理。

解释一下,给进程test使用组合键ctrl + c产生信号,操作系统做了什么?

首先,ctrl + c产生的是2号信号,因此操作系统会修改test进程控制块中的pending位图,将2号信号由0置为1,然后在合适时机处理该信号,当合适时机到来时,进程test执行2号信号的处理函数,由于该信号的处理函数是默认处理方式,那么操作系统会将test进程改为z状态并释放部分资源,最后被父进程回收test进程。

08 sigpending、sigprocmask、sigaction函数的使用

1、sigprocmask

功能:可以读取或更改进程的信号屏蔽字

成功返回0,出错返回-1

 其中how选项为SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK

set为输入型参数,设置新的阻塞信号集。

oldset为输出型参数,获取老的阻塞信号集。

在此之前需要介绍一下sigset_t 类型

sigset_t是一个32位的位图结构

对它进行操作的常用函数有:

①sigemptyset(sigset_t* set)

②sigfillset(sigset_t* set)

③sigaddset(sigset_t* set , signo)

④sigdelset(sigset_t* set , signo)

⑤sigismember(const sigset_t* set , signo)

现在我们使用sigprocmask函数对所有信号进行屏蔽

#include
#include
#includeusing namespace std;int main()
{sigset_t new_block;sigset_t old_block;sigfillset(&new_block);sigemptyset(&old_block);sigprocmask(SIG_SETMASK,&new_block,&old_block);while(true){cout<<"I am process"<

 可以发现,绝大多数信号可以被屏蔽,但9号信号是无法被屏蔽的

使用一下sigprocmask中老的信号集

#include
#include
#includeusing namespace std;int main()
{sigset_t new_block;sigset_t old_block;sigfillset(&new_block);sigemptyset(&old_block);sigprocmask(SIG_SETMASK,&new_block,&old_block);int cnt = 0;while(true){cout<<"I am process"<

 从上述现象可以看出,当有多个信号同时到来的时候,先处理信号编号小的。

2、sigpending

成功返回0,失败返回-1。

功能:读取当前进程的未决信号集

代码测试:

#include
#include
#include
using namespace std;void ShowPending(const sigset_t& pending)
{for(int i = 1; i<=31; i++){if(sigismember(&pending,i)){cout<<"1";}else{cout<<"0";}}cout<

3、sigaction

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

功能:可以读取和修改与指定信号相关联的处理动作

 sa_handler:设置普通信号的处理方式

 sa_sigaction:实时信号的处理方式,这里我们不管。

 sa_mask:在信号处理时,设置额外需要屏蔽的信号

 sa_flags:用来处理普通信号,设置为0就行。

 sa_restirer:不管。

代码测试

#include
#include
#includeusing namespace std;void handler(int signo)
{cout<<"收到"<

09 信号处理的时机

信号处理的时机:从内核态切换回用户态时会做信号检测,检测到有信号就会处理。

 当检测到有信号要处理时,就要处理信号。

对默认处理方式是终止进程,修改pending位图,然后返回用户态。

对忽略处理方式是修改pending位图,返回返回用户态。

对自定义的处理方式则需返回到用户态模式下处理该自定义函数,然后在返回用户态,在进行信号检测,如果没有信号,则返回用户态继续进行向下执行。

1、为什么要返回用户态默认执行自定义函数?

因为自定义函数是用户写的,必须要防止用户冒用操作系统的身份执行非法行为。

2、处理完自定义函数,为什么不能直接返回用户态继续执行剩下的代码?

因为你需要返回到内核态完成返回工作,比如你需要恢复上下文或者系统调用的返回值,这些都需要更高的权限来做,必须返回到内核态再回到用户态。

10 SIGCHLD信号

子进程退出时会给父进程发送SIGCHLD信号,对该信号的默认处理方式是忽略。

#include
#include
#include
#include
#includeusing namespace std;void handler(int signo)
{cout<

 但这种对SIGCHLD信号的处理方式在多个子进程同时退出则不能很好的处理。因为对于普通信号而言只有一个比特位来记录信号是否被收到(实时信号会被链表链接起来)。

多个子进程同时退出的现象

#include
#include
#include
#include
#include
#include
using namespace std;void handler(int signo)
{cout<

这个代码的弊端:

①无法处理多个子进程同时退出的情况

②当子进程永远不退出时,则会一直阻塞住,因为waitpid使用的是0,代表阻塞等待。

改进后

#include
#include
#include
#include
#include
#include
using namespace std;void handler(int signo)
{while(true){int ret = waitpid(-1,nullptr,WNOHANG);if(ret > 0){cout<<"等待成功->"<

最常用的做法是忽略SIGCHLD信号,当它退出时自动被操作系统回收。

#include
#include
#include
#include
#include
#include
using namespace std;int main()
{signal(SIGCHLD,SIG_IGN);for(int i = 0;i <= 5; i++){if(fork() == 0){int cnt = 0;while(true){cout<<"I am a child pid: "<

 有人或许有疑惑,SIGCHLD信号的默认处理方式不是忽略吗?你使用SIG_IGN处理方式不还是忽略吗?为啥手动写就能让子进程自动回收呢?

可以把这里当成一种特殊的情况,暂且只能记住了,或许之后会找到更好的答案。

11 可重入函数

要理解重入函数,先得理解重入的概念。

重入:一个执行流还未执行完毕,另一个执行就开始执行。

可重入函数:当执行流重入函数时,该函数不会出现不同的现象或者任何问题,则称这个函数是可重入的,否则是不可重入的。

大多数函数都是不可重入的,比如链表的头插函数。

 

所有的STL容器都是不可重入的,要想变得可重入得加锁保护,但加锁会损害STL容器的效率,这对以效率著称的STL是破坏性的,因此STL容器没有加锁。

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...