信号的产生、阻塞以及捕捉

用kill -l命令可以查看系统定义的信号列表:

信号_第1张图片

一、产生信号

1、如何产生信号?

(1)通过终端按键产生信号

    SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且 Core Dump,那么到底什么是Core Dump?

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

    首先用ulimit命令改变Shell进程的Resource Limit,允许core文件最大为1024K:

信号_第2张图片

    测试代码:

:信号_第3张图片

(2)调用系统函数向进程发信号

    如:

    1、kill函数:给指定的进程发送指定的信号

wKiom1ebT3rTkFHnAABBahNgb9M571.png-wh_50

    2、raise函数:可给当前进程发送指定的信号(自己给自己发送信号)

wKioL1ebT-mR7sN8AAArzLDltyg913.png-wh_50

    3、abort函数:使当前进程接受到SIGABRT信号而异常终止

wKiom1ebT_jQ34nXAABATWT1rTs713.png-wh_50

    4、signal函数:设置某一信号的对应动作

wKioL1ebUDizZi6rAAAbUxachsA820.png-wh_50

    测试代码:

信号_第4张图片信号_第5张图片

(3)由软件条件产生信号

    如:alarm函数:在seconds秒后给当前进程发送SIGALRM信号,默认终止当前进程。

wKiom1ebURORiohwAABbzywuhwg450.png-wh_50

信号_第6张图片

wKiom1ebURSy5oEdAAAJOkSl5bc169.png-wh_50

(4)kill命令行命令,如下:

wKioL1ebUbTx8sYQAAANuyxdpEM692.png-wh_50

2、如何处理信号

     (1)忽略此信号

     (2)执行该信号的默认处理动作

     (3)提供一个信号处理函数,要求内核在处理该信号是切换时切换到用户态执行这个处理函数,也称捕捉一个信号。


二、阻塞信号

    实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞 (Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞, 才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

    信号在内核中的表示示意图:

信号_第7张图片

    每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。

    1、SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。

    2、SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没 有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。

    3、SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。

     每个信号只有一个bit的未决标志和阻塞标志,非0即1,不记录该信号产生的次数。因此,未决和阻塞标志可以用相同的数据类型sigset_t储存,sigset_t称为信号集。这个类型表示信号的有效和无效状态。在阻塞信号集(信号屏蔽字)中即block表,1表示信号阻塞,0表示不阻塞。在未决信号集即pending表,1表示信号产生未决状态,0表示没有产生信号。阻塞信号集也称为当前进程的信号屏蔽字(Singal Mask)。这里的屏蔽应该理解为阻塞而不是忽略。


注:不可使用位操作操作信号集,有专有的函数操作

    信号集操作函数:

信号_第8张图片

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

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

    sigaddset函数:在该信号集中添加某一信号。

    sigdelset函数:在该信号集中删除某一个信号。

    sigismember函数:判断某一个信号是否在该信号集中。

    注意:在使用sigset_t类型的变量之前,一定要调用sigemptyset和sigfillset做初始化处理,使信号集处于确定的状态。初始化之后就可以在该信号集中增加,删除某种有效信号等操作。


    sigprocmask函数:读取或更改进程中的信号屏蔽字(即阻塞信号集)

wKioL1ebUxDAHByiAABBzvE463s289.png-wh_50

    如果set是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。

信号_第9张图片

    sigpending函数:读取当前信号集的未决信号集

wKioL1ebU5yiz47xAAAOW5fm45Q714.png-wh_50

    测试代码:

信号_第10张图片

信号_第11张图片

    运行效果:

信号_第12张图片


三、捕捉信号

     一次信号捕捉的过程需要进行4次权限的切换。

信号_第13张图片

    当一个进程接受到信号时,并不是立即进行处理,而是在合适的时候处理,所谓合适是指从内核态返回用户态时切换处理信号。当内核处理完异常或中断时,会先检查当前进程中是否有可以被递达的信号,若有,且信号的处理动作是自定义的信号处理函数,则从内核调到用户态执行代码,之后进入内核态,从内核态返回用户态即上次被中断或异常的地方,若信号的处理方式是默认,则终止进程,若忽略,则从penging表中删除该信号,即将1变为0,直接跳到用户态。


    sigaction函数:读取和修改与指定信号相关联的处理动作。

wKioL1ebVF_hSRX-AABEC4idXfo863.png-wh_50

    参数:

    signo:信号的编号

    act:非空时,根据act修改该信号的处理动作

    oact:非空时,传出该信号原来的处理动作

    act和oact结构体如下:

信号_第14张图片

    将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL 表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册 了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信 号的编号,这样就可以用同一个函数处理多种信号。显然,这也是个回调函数,不是被main 函数调用,而是被系统所调用。

sa_mask:需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

sa_flags:包含一些选项


    pause函数:使调用的进程挂起直到有信号递达。

wKioL1ebVOuyxJK9AAAnimfkCGs254.png-wh_50

    1、信号的处理动作是终止,则进程终止,不执行pause(即来不及执行该函数)。

    2、信号的处理动作是忽略,则进程继续处于挂起,pause不返回。

    3、信号的处理动作是捕捉,则调用信号处理函数后返回-1.


    测试代码:

信号_第15张图片

信号_第16张图片

    运行效果:

信号_第17张图片

    

测试普通信号中哪些可以被捕捉到,哪些不能被捕捉到。

    测试代码:

信号_第18张图片

信号_第19张图片

信号_第20张图片

    结论:除了9和19号信号,其他的普通信号都可以被捕捉到。