信号详解

什么是信号?
在生活中,我们会遇到各种信号,比过马路遇到的红绿灯,下课的铃声,水开的声音,这些信号都预示着不同的信息。接下来我们学习Linux中的信号。
每个信号都有一个名字,均以SIG开头且为大写,可以用kill -l命令查看系统定义的信号列表:

这里写图片描述
可以发现没有32 33信号,其中1-31号为普通信号,不支持排队等待,可能会造成信号丢失。
34-64为实时信号,支持排队等待,不会造成信号丢失。

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。
这里写图片描述
进程怎么处理信号?
(1)认识信号
(2)记录信号(PCB结构保存数据,位图放在进程的PCB中 )
(3)响应信号

注:信号的底层的数据结构是一个位图,就拿普通信号来说,用32个比特位来表示1-31的信号,全0时表示没有收到信号,当某一位被置为1时,表示收到了对应的信号。

先通过一个场景来熟悉进程中的信号。(括号里为生活中对应的类似情景)
(1)用户输入命令,在shell下启动一个前台进程。
(2)用户按下Ctrl+C,这个键盘输入产生一个硬件中断。(快递员打电话让取快递)
这里写图片描述
(3)如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断。(从图书馆出来 去学校门口)
(4)终端驱动程序将Ctrl+C解释成一个SIGINT信号,记在该进程的PCB中。(保存之前在图书馆写好的代码,以便回来继续敲)

信号的产生?
(1)用户在终端按下某些键时,终端驱动程序会发送信号给前台进程。
Ctrl+C产生SIGINT(2号信号),用来终止信号。
Ctrl+\产生SIGQUIT信号(3号信号),用来捕捉信号。
Ctrl+Z产生SIGTSTP信号(20号信号),可以让前台的进程终止。
(2)硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。
a,浮点数异常错误
这里写图片描述
再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
b,段错误
这里写图片描述
(3)软件条件产生信号。
例如闹钟超时产生的SIGALRM信号
这里写图片描述
结果:
这里写图片描述
注:alarm是闹钟信号,告诉内核在second秒之后给当前进程发送SIGALRM信号,该信号默认动作是终止当前进程。返回值为0或者上次闹钟剩下的秒数。
这里写图片描述
(4)调用系统函数向进程发数据
首先写一个死循环程序,前台运行这个程序,然后在终端键入Ctrl + \ :
这里写图片描述
同样可以用kill命令给它法SIGSEGV信号。
这里写图片描述

信号的处理?
(1)忽略此信号
(2)执行该信号的默认处理动作,每个信号都有对应的一种操作。
(3)提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理方式,这种方式称为捕捉到一个信号。

信号的状态?
信号抵达:实际执行信号的处理动作
信号未决:信号从产生到抵达之间的状态
信号阻塞:被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行抵达的动作。

注:被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是是在递达之后可选的一种处理动作。
在内核中的表示:
这里写图片描述
信号集操作函数:
上图中,每个信号只有一个bit的未决标志,非0即1,阻塞标志也是这样,因此未决和阻塞标志
可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的有效或无效状态。
这里写图片描述
以上函数均为成功返回0,出错返回-1.
最后一个sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含返回1,不包含则返回0,出错返回-1.
sigprocmask
这里写图片描述
参数①:how参数指定了这个函数工作的方式
参数②:set指针,指向一个合适的信号屏蔽字。
参数③:oldset指针,当我们修改了当前的信号屏蔽字之后,需要保存之前的信号屏蔽字,以便回复之前的工作状态。
sigpending
读取当前进程的未决信号集
这里写图片描述
接下来我实现一个关于信号阻塞的实例:
这里写图片描述
结果:
这里写图片描述
程序运行时,每秒钟把信号的阻塞状态打印一遍,由于我们阻塞了SIGINT信号,按下Ctrl+C使SIGINT信号处于未决状态,按下Ctrl+\ 仍然可以终止程序,因为SIGQUIT信号没有被阻塞。
捕捉信号
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
这里写图片描述
signal函数:
这里写图片描述

实例:
这里写图片描述
sigaction函数
这里写图片描述
pause函数

#include <unistd.h>  
int pause(void);  

pause函数使调用函数挂起直到有信号递达。
如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则函数继续挂起,pause不返回;如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,error设置为EINTR,所以pause只有出错的返回值。

下面我们用alarm函数和pause函数实现sleep(2)函数。(2秒的闹钟)
这里写图片描述
mysleep函数的返回值:判断是否到休眠时间;
mysleep函数返回前要恢复SIGALRM信号原来的sigaction:如果不恢复,当信号的处理函数改变之后,以后所有用到这个信号的处理函数的地方都变成了sig_alrn,这样显然不合理,所以需要将SIGALRM信号的处理函数改回去。

猜你喜欢

转载自blog.csdn.net/zjx624bjh/article/details/79850544