一、SIGCLD、SIGHCLD信号
- SIGCLD和SIGHCLD这两个信号很容易混淆:
- SIGCLD是System V采用的信号名
- SIGCHLD是BSD采用的信号名。POSIX.1也采用BSD的SIGCHLD信号
- 只有Linux 3.2.0和Solaris 10定义了SIGCLD,SIGCLD等同于SIGCHLD
- 务必了解你所使用的系统实现中SIGCHLD或SIGCLD信号的语义。也应了解在某些系统中#define SIGCHLD SIGCLD或#define SIGCLD SIGCHLD。更改这种信号的名字使你可以编译为另一个系统编写的程序,但是如果这一程序使用该信号的另一种语义,程序有可能不会正常工作。例如下面在Ubuntu下编译使用SIGCHLD信号的程序,出错
二、功能与特点
- 子进程状态改变(终止或停止)后产生此信号(SIGCLD/SIGHCLD),传递给父进程
- 如果父进程想要捕捉此信号,可以捕获该信号并进行处理
三、处理方式
- SIGCLD信号的捕捉处理必须在子进程fork之前完成
方式一:
- 如果进程明确地将该信号配置为SIG_IGN,则调用进程的子进程将不产生僵死进程(注意:SIG_IGN与默认处理方式SIG_DFL不同)。子进程在终止时,将其状态丢弃
- 如果调用进程随后调用一个wait()函数,那么它将阻塞直到所有的子进程都终止,然后该wait返回-1,并将errno设置为ECHILD
方式二:
- 父进程为SIGCLD设置一个信号捕捉函数,在信号捕捉函数中调用wiat()或waittpid()获取子进程的终止状态与ID
四、一个重要案例
- 下面的程序父进程为SIGCLD信号绑定信号处理函数,在信号处理函数中等待子进程结束
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.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) {
sleep(2);
_exit(0); //子进程终结
}
/* parent */
//pause:使进程挂起,直到接收到一个信号并从信号处理函数中返回才结束挂起状态
pause();
exit(0);
}
static void sig_cld(int signo)
{
pid_t pid;
int status;
printf("SIGCLD received\n");
if (signal(SIGCLD, sig_cld) == SIG_ERR)
perror("signal error");
if ((pid = wait(&status)) < 0) //阻塞等待子进程,获得其进程号和终止状态
perror("wait error");
printf("pid = %d\n", pid);
}
演示结果:
此程序在传统的System V平台上工作:
- 会一直输出SIGCLD received,最后进程用完其栈空间并异常终止
- 原因:信号处理程序第一次调用signal函数并且执行sig_cld处理函数,进入之后,内核检查是否有需要等待的子进程(因为我们正在处理一个SIGCLD信号,所以确实有这种子进程),所以它产生另一个对信号处理程序的调用,于是在sig_cld函数内继续调用sig_cld函数。整个过程再次重复
- 解决办法:应当在调用wait取到子进程的终止状态后再调用signal。这样仅当其他子进程终止,内核才会再次产生此种信号
其他系统:
- 不会产生上面这种问题,例如下面在Ubuntu中运行
- 为什么举这个例子:虽然大多数系统都不会产生这种问题,但是还是有一些系统会产生这种问题