一、信号从发送到被处理经历的过程
1、常见概念
(1) 信号阻塞
阻塞,即被进程拉黑,信号被发送后,分为两种情况,一种是被阻塞了(被拉黑了),一种是没有被阻塞。
(2) 信号未决
在信号被进程处理之前的过程,都可以称为信号未决
(3) 信号递达
信号被进程处理,我们称为信号递达
(4) 信号忽略
进程收到信号后直接忽略,不进行处理,是否还有默认处理呢?(忽略中断信号,验证是否会中断)
(5) 阻塞信号集
指一个进程中当前阻塞而不能够递送给该进程的信号集
(6) 未决信号集
指当前进程未处理的信号集,收到阻塞的信号,未决信号集该信号位会置1,解除阻塞后会置0。
2、信号保存的方式(内核级)
block表:对应的信号是否被阻塞,0表示不阻塞,1表示阻塞
pending表**:**信号是否被收到,0表示未收到该信号,1表示收到该信号
handler表:表示对应信号的处理方式(函数),存放的是函数的地址(函数指针)
3、信号从发送到被处理
第一列表示第 N 号信号,以第二行为例,阻塞信号为 1 ,说明该信号被阻塞了(相当于在进程的黑名单里),后面的未决信号是0还是1都无所谓了
以第四行为例,阻塞信号为0,该信号没有被阻塞,未决信号为1,说明收到这个信号了,对应的处理方式是 sighandler函数,那就会执行该函数
注意:
SIG_DFL(signal default):信号默认处理方式
SIG_IGN(signal ignore):忽略信号
总结
先看block,如果block为1,即信号被拉黑,是否收到信号都不重要了
如果block不为1,那就再看pending是否为1,即是否收到信号
二、信号集操作(信号保存)
阻塞信号其实就是要修改block表,但是要怎么修改呢?如果只是传递单个信号,或许可以直接以某种方式告诉OS我希望阻塞哪个信号,但是,如果要修改多个信号呢??OS给出了一种方案来解决传递单个或者多个信号,那就是直接传递位图!!
收到一个信号之后执行信号处理函数,在执行信号处理函数的过程中如果又来了一个相同的信号,那么这个信号将会被阻塞,直到信号处理函数执行完之后,再响应被阻塞的信号,注意如果信号被阻塞期间又收到了该信号,那么多个信号的处理会被合并为1次。
信号集就是用来记录当前收到了哪个信号,会把当前信号的标志位置成"正在处理",如果此时再收到该信号,那么信号就阻塞等待,使用数据类型sigset_t表示信号集,在Linux中该类型是一个32位无符号整数,这是因为在Linux中定义了32种信号,每一个信号用32位无符号整型变量中的一位来标志,如果该位置为1,那么表示正在处理该信号,如果置为0表示可以处理该信号。
未决信号集保存当前为处理的信号集。
信号屏蔽字又叫阻塞信号集,是指一个进程中当前阻塞而不能够递送给该进程的信号集。每个进程都有一个信号屏蔽字,它规定了当前要阻塞递达到该进程的信号集
1、特定的数据类型sigset_t
既然要传递位图,要用什么来表示位图呢?第一想法是int/uint32_t,不同的操作系统,实现位图的方式可能有所不同,Linux操作系统不光给我们提供了 保存位图的数据类型 sigset_t,还有对应的函数来操作位图(下面说)
------》sigset_t 可以看作是一个信号集,保存着1~31号信号的状态,0表示没收到,1表示收到了
2、操作信号集的函数
(1) sigemptyset函数,清空信号集,全部位置置0。GUN C库中的非标准函数
int sigemptyset ( sigset_t *set);
成功返回0,失败返回-1
(2) sigfillset函数:初始化信号集,全部置1。GUN C库中的非标准函数
int sigfillset( sigset_t *set);
成功返回0,失败返回-1
(3) sigaddset函数:向信号集中添加一个信号,将对应信号置1。标准函数
假设一开始所有的比特位都是 0(0000 0000 ...),如果我们希望把第二个信号阻塞,那么就需要先把信号集的第二个比特位设置为1(0100 0000....),然后再传给信号阻塞函数
int sigaddset( sigset_t *set, int signum);
第一个参数是传入信号集的地址,第二个参数是要把哪个位置(信号)设置成1,成功返回0,失败返回-1
(4) sigdelset函数:从信号集中删除一个信号,将对应信号置0。标准函数
(假设前面已经阻塞了2号信号)这个时候的比特位显示为0100 0000....,现在我们不希望阻塞2号信号,那么需要把第二个位置(信号)设置为 0 (0000 0000....)
int sigdelset( sigset_t *set, int signum);
第一个参数是传入信号集的地址,第二个参数是要把哪个位置(信号)设置成1,成功返回0,失败返回-1
(5) sigismember函数:判断某个信号是否为信号集的成员(判断是否在信号集中)。标准函数
如果对应位置(信号)的比特位为1,说明在信号集中,如果为0,说明不在信号集中
int sigismember( sigset_t *set, int signum);
成功返回1,失败返回0
如果使用sigemptyset()初始化信号集(信号集的所有位置0),此时可以接收到所有的信号
如果使用sigfillset()初始化信号集(信号集的所有位置1),此时屏蔽所有信号,当然SIGKILL和SIGQUIT信号是不能屏蔽的
sigaddset()和sigdelset()的意义在于指定屏蔽某信号或者接收某信号
int sigprocmask (int how, const sigset_t *restrict set, sigset_t *restrict oset);
how取值有下列三个:
SIG_BLOCK :set中包含的是希望阻塞的附加信号,合并set中的信号集
SIG_UNBLOCK:set中包含的是希望解除阻塞的信号,删除set中的信号集
SIG_SETMASK:set中包含的是现有屏蔽字的代替值,替换set中的信号集
若oset是非空指针,则返回进程的当前信号屏蔽字。
若set为空,则进程信号屏蔽字不变,how值无意义。
另外,注意一点,不可以阻塞SIGKILL和SIGSTOP信号。
注意:调用该接口将信号设置为非阻塞时,若该信号已经是阻塞状态,则在该函数返回前马上会发给该线程
int sigpending ( sigset_t *set );
此函数通过set返回当前检查未决的信号集。其中的各个信号对于调用进程是阻塞的而不能递送 ,因而也一定是当前未决的。
signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。而 POSIX 标准定义的信号处理接口是 sigaction 函数。
int sigaction ( int signum, const struct sigaction *act, struct sigaction *oldact ) ;
signum:要操作的信号。
act:要设置的对信号的新处理方式。
oldact:原来对信号的处理方式。
返回值:0 表示成功,-1 表示有错误发生。
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void*);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
这个结构体中,成员 sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。成员sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。
sa_flags 用于指定信号处理的行为,它可以是一下值的"按位或"组合。
◆ SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
◆ SA_NODEFER:一般情况下,当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记,那么在该信号处理函数运行时,内核将不会阻塞该信号。
◆ SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL。
◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。
re_restorer 是一个已经废弃的数据域,不要使用。
可重入函数
一个函数被重入,表示这个函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。一个函数要被重入,只有两种情况:
1、多线程同时执行这个函数
2、函数自身(可能经过多层调用之后)调用自身
一个函数可重入,表示这个函数被重入后不会产生任何不良后果。要成人可重入函数必须有以下特定:
1、不使用任何(局部)静态或全局的非const变量
2、不返回任何(局部)静态或全局的非const变量的指针
3、依赖调用方提供参数
4、不依赖任何单个资源的锁
5、不调用任何不可重入的函数