早期的UNIX信号机制是不可靠的,原因在于信号可能丢失。主要体现在:① signal函数中对信号处理的设置 ② 无法阻塞信号
讨论不可靠机制之前,先介绍一下signal函数和sigaction函数,signal函数接口为void ( *singal(int signo,void (*func)(int)))(int) 看起来非常复杂,大意就是signal函数有两个参数,一个是int型的signo(也就是某类信号)一个void型的函数指针(该函数有一个int参数)。signal函数返回一个void型的函数指针,该函数同样有一个int参数。由于上面的声明太过复杂,一般都会把上面的声明改为
sigaction的接口为int sigaction(int signo,const struct sigaction*act,struct sigaction*oact)
与sigaction函数相关联的一个结构是sigaction结构。该结构有4个成员:① void (*sa_handler)(int) ② sigset_t sa_mask ③ int sa_flags ④ void (*sa_sigaction)(int,siginfo_t*,void*)
其中sa_sigaction与sa_flags暂时不讨论
sa_handler就是信号处理函数,而sa_mask在信号处理函数调用时会自动加入到信号掩码中(这样就能在处理某个信号期间阻塞其他信号,同类信号会自动加入,无需设置),退出时恢复为原值。
如果act不为NULL,则sigaction把signo的设置改为act的内容,如果oact不为NULL,则sigaction把原先signo的设置放到oact中。
早期的signal函数实现的是不可靠的信号机制。sigaction函数的出现替代了了早期的signal函数,现在很多系统使用sigaction实现signal
APUE里给出了用sigaction实现的signal函数
早期的不可靠信号机制的一个方面体现在,对于一个信号,即使设置了捕获函数,当信号被捕获后,其信号处理会自动恢复为默认值
因此往往出现这样的程序
但是这样的程序存在一个问题,在sigint开始到信号处理函数设置signal前,中间有一段时间窗口,如果这期间又发生了SIGINT信号,则会导致进程停止。
可能有些人会感到疑惑,在sigint中不是会导致SIGINT的阻塞吗?为什么一个新的SIGINT会导致进程停止?那是因为在早期的信号机制中,信号是无法阻塞的,而这同样会导致一些问题(例如上面那段)
看下面一段代码
在程序检测sig_int_flag以及调用pause之间有一个时间窗口,如果信号发生在这段时间,并且不再发生,则程序将永远阻塞。
我们需要某种机制,使得我们可以先阻塞某种信号,然后原子的解除信号阻塞并调用pause函数。UNIX系统因此提供了sigsuspend函数。
网上有这么一种说法,认为对于信号的排队问题也是导致不可靠信号的原因。个人认为这并不正确
考虑这样一个问题,如果程序进入了一个信号处理函数,在这期间发生了一到多个同样的信号,会怎么样??
使用signal函数或者sigaction函数设置信号处理函数后,在信号处理函数期间,该信号会自动加入到信号掩码中,一退出信号处理函数,信号掩码则复位为原先的值。因此,当在信号处理函数中,同类信号的发生会被屏蔽而保持未决状态,如果发送了多个此类信号的话,当信号不再被阻塞时,一般只发送信号一次(也就是所谓的丢失)。然而早期信号是无法阻塞的,既然是无法阻塞的,那么信号也就不可能排队,因此上面的说法并不成立。
有意思的是,UNIX对前32种信号不支持排队,而后32种信号(也被称为实时信号)则支持排队。
最后,考虑一个问题,使用signal函数和sigaction函数哪个更好??个人认为sigaction对比起signal函数有三个优点,第一个是有些系统为了保持向后兼容,支持旧的不可靠信号语义函数signal。第二个是signal函数无法做到在不改变信号当前设置的前提下,获取信号的设置,而sigaction则可以 第三 sigaction提供了一些额外的设置,能做到一些signal无法做到的事情,这些将在后面说到