SIGCLD
和SIGCHLD
这两个信号很容易被混淆。SIGCLD
(没有H)是System V的一个信号名,其语义与名为SIGCHLD
的BSD信号不同。POSIX.1采用BSD的SIGCHLD
信号。
BSD的SIGCHLD
信号语义与其他信号的语义相类似。子进程状态改变后产生此信号,父进程需要调用一个wait函数以检测发生了什么。
System V处理SIGCLD
信号的方式不同于其他信号。如果用signal或sigset(早期设置信号配置的,与SRV3兼容的函数)设置信号配置,则基于SVR4的系统继承了这一具有问题色彩的传统(即兼容性限制)。对于SIGCLD
的早期处理方式是:
(1) 如果进程明确地将该信号的配置设置为SIG_IGN
,则调用进程的子进程将不产生僵死进程。注意,这与其默认动作(SIG_DFL
)“忽略”(见图10-1)不同。子进程在终止时,将其状态丢弃。如果调用进程随后调用一个wait函数,那么它将阻塞直到所有子进程都终止,然后该wait会返回−1,并将其errno设置为ECHILD。(此信号的默认配置是忽略,但这不会使上述语义起作用。必须将其配置明确指定为SIG_IGN才可以。)
POSIX.1 并未说明在 SIGCHLD 被忽略时应产生的后果,所以这种行为是允许的。Single UNIX Specification的XSI扩展选项要求对于SIGCHLD支持这种行为。
如果SIGCHLD被忽略,4.4BSD总是产生僵死进程。如果要避免僵死进程,则必须等待子进程。在SVR4中,如果调用sig-nal或sigset将SIGCHLD的配置设置为忽略,则决不会产生僵死进程。本书讨论的4种平台在此方面都追随SVR4的行为。
使用sigaction可设置SA_NOCLDWAIT标志(见图10-6)以避免进程僵死。本书讨论的4种平台都支持这一点。
(2) 如果将SIGCLD
的配置设置为捕捉,则内核立即检查是否有子进程准备好被等待,如果是这样,则调用SIGCLD
处理程序。
第2种方式改变了为此信号编写处理程序的方法,这一点可在下面的实例中看到。
实例
10.4节曾提到,进入信号处理程序后,首先要调用signal函数以重新设置此信号处理程序(在信号被重置为其默认值时,它可能会丢失,立即重新设置可以减少此窗口时间)。图10-6展示了这一点。但此程序不能在某些传统的 System V 平台上正常工作。程序一行行地不断重复输出“SIGCLD received”,最后进程用完其栈空间并异常终止。
#include "apue.h"
#include <sys/wait.h>
static void sig_cld(int);
int main(){
pid_t pid;
if(signal(SIGCLD,sig_cld) == SIG_ERR){
perror("signal error");
}
if((pid = fork()) < 0){
perror("fork error");
}else if(pid == 0){// child
sleep(2);
_exit(0);
}
pause();
exit(0);
}
static void sig_cld(int signo){
pid_t pid;
int status;
printf("SIGCLD received");
if(signal(SIGCLD, sig_cld) == SIG_ERR){// re establish handler
perror("signal error");
}
if((pid = wait(&status)) < 0){
perror("wait error");
}
printf("pid = %d\n",pid);
}
图10-6 不能正常工作的System V SIGCLD处理程序
因为基于BSD的系统通常并不支持早期System V的SIGCLD语义,所以FreeBSD 8.0和Mac OS X 10.6.8 并没有出现此问题。Linux 3.2.0 也没有出现此问题,其原因是,虽然
SIGCLD
和SIGCHLD
定义为相同的值,但当一个进程安排捕捉SIGCHLD
,并且已经有进程准备好由其父进程等待时,该系统并不调用SIGCHLD信号的处理程序。Solaris 10在此种情况时确实调用该信号处理程序,但在内核中增加了避免此问题的代码。虽然本书说明的所有4种平台都解决了这一问题,但是应当意识到没有解决这一问题的平台(如AIX)依然存在。
此程序的问题是:在信号处理程序的开始处调用signal,按照上述第2种方式,内核检查是否有需要等待的子进程(因为我们正在处理一个SIGCLD
信号,所以确实有这种子进程),所以它产生另一个对信号处理程序的调用。信号处理程序调用 signal,整个过程再次重复。
为了解决这一问题,应当在调用wait取到子进程的终止状态后再调用signal。此时仅当其他子进程终止,内核才会再次产生此种信号。
如果为
SIGCHLD
建立了一个信号处理程序,又存在一个已终止但父进程尚未等待它的进程,则是否会产生信号?POSIX.1对此没有做说明。这就允许前面所述的工作方式。但是,POSIX.1在信号发生时并没有将信号处理重置为其默认值(假定正调用POSIX.1的sigaction函数设置其配置),于是在SIGCHLD处理程序中也就不必再为该信号指定一个信号处理程序。?: 务必了解你所用的系统实现中
SIGCHLD
信号的语义。也应了解在某些系统中#define SIGCHLD
为SIGCLD
或反之。更改这种信号的名字使你可以编译为另一个系统编写的程序,但是如果这一程序使用该信号的另一种语义,程序有可能不会正常工作。在本书说明的4种平台上,只有Linux 3.2.0和Solaris 10定义了SIGCLD,SIGCLD等同于SIGCHLD。