10.7 SIGCHLD定义
经常混淆的两个信号就是SIGCLD以及SIGCHLD,信号SIGCLD源于System V,该信号的含义与源自BSD的信号SIGCHLD不一致。同时POSIX.1信号也称为SIGCHLD.源自BSD的信号SIGCHLD的语义比较正常,当该信号出现的时候,表示子进程的状态发生了变化,然后我们需要调用一个wait函数来查看究竟发生了什么。
System V对于SIGCLD的处理历史以来都与其他信号不同,基于SVR4i的系统保持了这个有问题的传统,如果我们使用函数signal或者是sigset(SVR3中用于设置信号处理函数的函数)设置信号的处理函数,SIGCLD早期的处理包含如下行为:
- 如果将信号处理函数指定为SIG_IGN,调用进程的子进程将永远不会产生僵尸进程。注意这与该信号的默认处理时不同的,在图10.1中提到其默认处理时忽略。取而代之的是,当子进程终止的时候,子进程的状态将被抛弃,如果其父进程接着调用其中一个wait函数,调用进程将被阻塞直到所有子进程终止,然后wait函数返回-1,并且errno被设置为ECHILD.(注意:信号的默认处理时忽略信号,但是该默认处理方式并不会产生上述行为,而是我们必须将该信号的处理函数设置为SIG_IGN.)
POSIX.1并没有指定当SIGCHLD被忽略的时候会发生什么,所以上述处理也是被允许的,而XSI选项要求这一处理被信号SIGCHLD支持。
4.4BSD在SIGCHLD被忽略的时候总是生成僵尸进程,如果我们想要避免僵尸进程的产生,我们必须wait所有的子进程,在SVR4中,只要调用signal或者是sigset中的一个将SIGCHLD的处理函数设置为SIG_IGN,僵尸进程就永远不会产生,本书中描述的4个平台都与SVR4的相同。
使用函数sigaction而言,我们可以设置SA_NOCLDWAIT标志来避免僵尸进程,该行为对于本书中介绍的4个平台都是支持的。 - 如果我们设置SIGCLD的处理函数,内核就会立即检查是否有子进程处于等待处理的情况,若有,则立即调用SIGCLD函数。
上述第二项改变了我们编写信号处理函数的方式,正如下面的例子中将会讲到的一样.
Example
在10.4节中提到,在信号处理函数中的第一件事就是再次调用signal函数,这样处理时为了最小化信号处理函数被还原为默认处理的时间窗口,图10.6就是按照这一规则进行编写的程序,但是该程序在传统的System V平台上并不能很好地工作,其输出将是连续不断的字符串”SIGCLD received”,最会进程由于堆栈空间溢出而异常终止。
#include "apue.h"
#include <sys/wait.h>
static void sig_cld(int signo);
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 process*/
sleep(2);
_exit(0);
}
pause(); /*parent process*/
exit(0);
}
static void sig_cld(int signo)
{
pid_t pid;
int status;
printf("SIGCLD received\n");
if(signal(SIGCLD, sig_cld) == SIG_ERR) /*reestablish handler*/
perror("signal error");
if((pid = wait(&status)) < 0) /*fetch child status*/
perror("wait error");
printf("pid = %d\n", pid);
}
上述程序的问题是:在信号处理函数的开始位置调用了函数signal,这对应于上面讨论的第二项–内核检查是否有子进程需要wait for(在上述程序中符合这一情况,因为我们正在处理SIGCLD信号),所以对于signal函数的调用会产生另一次对于信号处理函数的调用,信号处理函数反过来又会调用signal函数,于是整个进程就会不断地重复运行。
为了解决上述程序的问题,我们必须将对函数signal的调用移动到wait函数之后,通过这样做我们在捕获到子进程的终止状态以后再调用signal函数,该信号仅仅在其他子进程终止的时候才会再次产生。
POSIX.1声明:在我们建立SIGCHLD的信号处理函数的时候,如果已经存在一个还没有被wait的终止的子进程,信号是否需要生成并没有指定,因此上述情况是允许的,但是对于POSIX.1而言,在信号产生以后并不会复位信号处理函数为默认函数(假设我们正在使用的是POSIX.1的sigaction函数来设置信号处理函数),便没有需要在信号处理函数中再次调用函数signal了。于是上述问题就消失了。