一、信号处理情况分析
在 signal 处理机制下,还有许多特殊情况需要考虑:
- 注册一个信号处理函数,并且处理完毕一个信号之后,是否需要重新注册,才能够捕捉下一个信号?(不需要)
- 如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个同类型的信号,这时该怎么处理?(挨着执行)
- 如果信号处理函数正在处理信号,并且还没有处理完毕时,又发生了一个不同类型的信号,这时怎么处理?(跳转去执行另一个信号,之后再执行剩下的没有处理完的信号)
- 如果程序阻塞在一个系统调用(如 read(…) )上时,发生了一个信号,这时是让系统调用返回错误,再接着进入信号处理函数,还是先跳转到信号处理函数?(等信号处理完毕后,系统再调用返回)
二、sigaction 信号处理注册
sigaction 函数原型为:
#include <signal.h>
int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact);
sigaction 也用于注册一个信号处理函数。
参数 signum 为需要捕捉的信号;
参数 act 是一个结构体,里面包含信号处理函数地址、处理方式等信息;
参数 oldact 是一个传出参数,sigaction 函数调用成功后,oldact 里面包含以前对 signum 的处理方式的信息,通常为 NULL。
如果调用成功,将返回 0,否则返回 -1。
结构体 struct sigaction(注意名称与函数 sigaction 相同)的原型为:
struct sigaction {
void( *sa_handler)(int); //老类型的信号处理函数指针
void( *sa_sigaction)(int, siginfo_t *, void *); //新类型的信号处理函数指针
sigset_t sa_mask; //将要被阻塞的信号集合
int sa_flags; //信号处理方式掩码(=>SA_SIGINFO)
void( *sa_restorer)(void); //保留,不使用
};
该结构体的各字段含义及使用方式:
(1)字段 sa_handler 是一个函数指针,用于指向原型为 void handler(int) 的信号处理函数地址,即老类型的信号处理函数(如果用这个再将 sa_flags=0,就等同于 signal() 函数)。
(2)字段 sa_sigaction 也是一个函数指针,用于指向原型为:
void handler(int iSignNum, siginfo_t * pSignInfo, void * pReserved)
的信号处理函数,即新类型的信号处理函数,该函数的三个参数含义为:
iSignNum 为传入的信号;
pSignInfo 为与该信号相关的一些信息,是个结构体;
pReserved 保留,暂时无用,通常为 NULL。
(3)字段 sa_handler 和 sa_signaction 只应该有一个生效,如果想采用老的信号处理机制,就应该让 sa_handler 指向正确的信号处理函数,并且让字段 sa_flags 为 0 ;否则应该让 sa_sigaction 指向正确的信号处理函数,并且让字段 sa_flags 包含 SA_SIGINFO 选项。
(4)字段 sa_mask 是一个包含信号集合的结构体,该结构体内的信号表示正在进行信号处理时,将要被阻塞的信号。针对 sigset_t 结构体,有一组专门的函数对它进行处理,它们是:
#include <signal.h>
int sigemptyset(sigset_t * set); //清空信号集合 set
int sigfillset(sigset_t * set); //将所有信号填充进 set 中
int sigaddset(sigset_t * set, int signum); //往 set 中添加信号 signum
int sigdelset(sigset_t * set, int signum); //从 set 中移除信号 signum
int sigismember(const sigset_t * set, int signum); //判断 signum 是不是包含在 set 中
int sigpending(sigset_t * set); //将被阻塞的信号集合由参数 set 指针返回
例如,在处理信号 SIGINT 时,只阻塞对 SIGQUIT 信号的处理,可以用如下方法:
struct sigaction act;
act.sa_flags=SA_SIGINFO;
ac.sa_sigaction=newHandler;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
sigaction(SIGINT, &act, NULL);
(5)字段 sa_flags 是一组掩码的合成值,指示信号处理时所应该采取的一些行为,各掩码的含义如下:
掩码 | 描述 |
---|---|
SA_RESETHAND | 处理完要捕捉的信号后,将自动撤销信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已被废弃 |
SA_NODEFER | 在处理信号时,如果又发生了其他的信号,则立即进入其他信号的处理,等其他信号处理完毕后,再继续处理当前信号,即递归地处理。如果 sa_flags 包含了该掩码,则结构体 sigaction 的 sa_mask 将无效,不常用 |
SA_RESTART | 如果在发生信号时,程序正阻塞在某个系统调用中,例如,调用 read() 函数,则在处理完信号后,接着从阻塞的系统返回。如果不指定该参数,中断处理完毕之后,read 函数读取失败 |
SA_SIGINFO | 指示结构体的信号处理函数指针哪个有效,如果 sa_flags 包含该掩码,则 sa_sigaction 指针有效,否则 sa_handler 指针有效,常用 |
三、sigprocmask 信号阻塞
函数 sigaction 中设置的被阻塞信号集合只是针对于要处理的信号,例如:
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
sigaction(SIGINT, &act, NULL);
表示只有在处理信号 SIGINT 时,才阻塞信号 SIGQUIT。
函数 sigprocmask 是全程阻塞,在 sigprocmask 中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合,它的原型为:
#include <signal.h>
int sigprocmask(int how, const sigset_t * set, sigset_t * oldset);
参数 how 的值为如下 3 者之一:
- SIG_BLOCK,将参数 2 的信号集合添加到进程原有的阻塞信号集合中。
- SIG_UNBLOCK,从进程原有的阻塞信号集合移除参数 2 中包含的信号。
- SIG_SETMASK,重新设置进程的阻塞信号集为参数 2 的信号集。
参数 set 为阻塞信号集;
参数 oldset 是传出参数,存放进程原有的信号集,通常为 NULL。