欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

信号详解

程序员文章站 2022-03-19 13:21:13
...

什么是信号?
在生活中,我们会遇到各种信号,比过马路遇到的红绿灯,下课的铃声,水开的声音,这些信号都预示着不同的信息。接下来我们学习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信号的处理函数改回去。