Linux (八)进程信号(信号产生,阻塞信号,捕捉信号)

信号的基本概念

为了理解信号,举一个我们熟悉的例子
用户输入命令,在shell下启动一个前台进程
用户按下CTRL-C,这个键盘输入产生一个硬件终端
如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断
终端驱动程序将CTRL-C解释成一个SIGINT信号,记在该进程的PCB中,当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理。而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行

其中Ctrl-C产生的信号只能发给前台进程
shell可以同时运行一个前台进程和任意多个后台进程
信号相对于进程的控制流程来说是异步的
用Kill-l就可以查看系统定义的信号列表

产生信号的三种方式
1:通过终端按键产生信号
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,ulimit 命令改变了shell进程的Resource Limit,当Core Dump的时候我们就可以使用core文件了
我们用gdb-file core.core文件名,就可以直接调试出错误出现在哪里

2:调用系统函数向进程发信号
kill命令是调用kill函数实现的,kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前程序发送指定的信号

#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1
#include<stdlib.h>
void abort(void);
就像exit函数一样,abort函数总是会成功,所以没有返回值

3:由软件条件产生信号
SIGPIPE是一种由软件条件产生的信号,下面介绍alarm函数

#include<unistd.h>
unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个闹钟,就是告诉内核seconds秒后给当前进程发一个SIGALRM信号,该信号的默认处理动作是终止当前进程

信号处理常见方式概览
忽略此信号;
执行该信号的默认动作;
提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号

阻塞信号

1:信号其他相关常见概念
实际执行信号的处理动作称为信号抵达(Delivery)
信号从产生到抵达之间的状态称为信号的未决(pending)
进程可以选择阻塞(block)某个信号
被阻塞的信号产生时保持在未决状态,直到进程接触对次信号的阻塞。才执行抵达的动作
注意,阻塞和忽略是不同的,知道信号被阻塞就不会抵达,而忽略是在抵达之后,可选的一种处理方式

信号在内核中表示示意图
这里写图片描述
每个信号都有两个标志位,分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,知道信号抵达才清楚该标志。在上面的图中,SIGHUP信号未阻塞也未产生过。当它抵达时执行默认处理动作。SIGINT信号产生过,但是正在被阻塞,所以暂时不能抵达,虽然它的处理动作是忽略,但是在没有解除阻塞之前是不能忽略这个信号,因为进程是有机会改变处理动作再解除阻塞的。SIGUIT信号未产生过,一旦产生这个SIGUIT信号将被阻塞,它的处理动作是用户自定义函数Sighandler。

信号集概念
信号的阻塞和未决标志都只用一个bit位来表示,即非零即一,所以我们可以用相同的数据类型来存储,它就叫做sigset_t信号集。在阻塞信号集中有效和无效表示该信号是否被阻塞。在未决信号集中有效和无效表示该信号是否处于未决状态
在使用sigset_t类型变量之前,首先要初始化,使信号集状态处于确定状态。初始化后就可以相关函数在该信号集中添加或这删除某种有效信号
sigprocmask
调用函数sigprocmask可以读取或更改进程的信号屏蔽字

#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_ *oset);
返回值:成功返回0;失败返回-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出,如果set是非空指针,则更改进程的信号屏蔽字,参数how只是如何更改。如果oset和set都不是空指针,则先将原来的信号屏蔽字被分到oset里,然后根据set和how参数更改信号屏蔽字。

信号的捕捉

1。内核如何实现信号捕捉
这里写图片描述

从图中可以看出来程序共在内核与用户之间切了四次

2。sigaction

#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
sigaction 函数可以读取和修改与指定信号相关联的处理动作。调用成功返回0;失败返回-1

signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体

当某个信号的处理函数被调用时。内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动回复为原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这个信号再次产生,那么它就会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽外,还希望自动屏蔽另一些信号,就用sammask字段说明这些需要和外屏蔽的信号,当信号处理函数返回时自动回复原来的信号屏蔽字

3。pause

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

pause 函数使调用进程挂起直到有信号抵达,如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号处理动作是捕捉,则调用信号处理函数之后pause返回-1,errno设置为EINTR所以pause只有出错的返回值(以前说过的替换函数exec)

猜你喜欢

转载自blog.csdn.net/mignatian/article/details/80044974