SIGCLDSIGCHLD这两个信号很容易被混淆。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 也没有出现此问题,其原因是,虽然SIGCLDSIGCHLD 定义为相同的值,但当一个进程安排捕捉SIGCHLD,并且已经有进程准备好由其父进程等待时,该系统并不调用SIGCHLD信号的处理程序。Solaris 10在此种情况时确实调用该信号处理程序,但在内核中增加了避免此问题的代码。

虽然本书说明的所有4种平台都解决了这一问题,但是应当意识到没有解决这一问题的平台(如AIX)依然存在。

此程序的问题是:在信号处理程序的开始处调用signal,按照上述第2种方式,内核检查是否有需要等待的子进程(因为我们正在处理一个SIGCLD信号,所以确实有这种子进程),所以它产生另一个对信号处理程序的调用。信号处理程序调用 signal,整个过程再次重复。

为了解决这一问题,应当在调用wait取到子进程的终止状态后再调用signal。此时仅当其他子进程终止,内核才会再次产生此种信号。

如果为SIGCHLD建立了一个信号处理程序,又存在一个已终止但父进程尚未等待它的进程,则是否会产生信号?POSIX.1对此没有做说明。这就允许前面所述的工作方式。但是,POSIX.1在信号发生时并没有将信号处理重置为其默认值(假定正调用POSIX.1的sigaction函数设置其配置),于是在SIGCHLD处理程序中也就不必再为该信号指定一个信号处理程序。

?: 务必了解你所用的系统实现中SIGCHLD 信号的语义。也应了解在某些系统中#define SIGCHLDSIGCLD或反之。更改这种信号的名字使你可以编译为另一个系统编写的程序,但是如果这一程序使用该信号的另一种语义,程序有可能不会正常工作。

在本书说明的4种平台上,只有Linux 3.2.0和Solaris 10定义了SIGCLD,SIGCLD等同于SIGCHLD。

results matching ""

    No results matching ""