从 parent 向 child 发送信号

sending signal from parent to child

我正在使用来自网站 http://www.code2learn.com/2011/01/signal-program-using-parent-child.html 的本教程,并试图了解为什么 child 没有收到信号?

代码如下:

#include <stdio.h>  
#include <signal.h>
#include <stdlib.h>  
void sighup(); /* routines child will call upon sigtrap */  
void sigint();  
void sigquit();  
void main()  
{ int pid;  
    /* get child process */  
    if ((pid = fork()) < 0) {  
        perror("fork");  
        exit(1);  
    }  
    if (pid == 0)  
    { /* child */  
        signal(SIGHUP,sighup); /* set function calls */  
        signal(SIGINT,sigint);  
        signal(SIGQUIT, sigquit);  
        for(;;); /* loop for ever */  
    }  
    else /* parent */  
    { /* pid hold id of child */  
        printf("\nPARENT: sending SIGHUP\n\n");  
        kill(pid,SIGHUP);  
        sleep(3); /* pause for 3 secs */  
        printf("\nPARENT: sending SIGINT\n\n");  
        kill(pid,SIGINT);  
        sleep(3); /* pause for 3 secs */  
        printf("\nPARENT: sending SIGQUIT\n\n");  
        kill(pid,SIGQUIT);  
        sleep(3);  
    }  
}  
void sighup()  
{ signal(SIGHUP,sighup); /* reset signal */  
    printf("CHILD: I have received a SIGHUP\n");  
}  
void sigint()  
{ signal(SIGINT,sigint); /* reset signal */  
    printf("CHILD: I have received a SIGINT\n");  
}  
void sigquit()  
{ printf("My DADDY has Killed me!!!\n");  
    exit(0);  
}  

输出:

这是一个竞争条件。您的代码假定 child 运行 是第一个并且不会被 parent 抢占,直到它安装了所有信号处理程序并开始永远循环。

如果不是这种情况,parent 可能会在 child 有机会捕捉到信号之前向 child 发送信号。因此,child 进程被终止,因为 SIGHUPSIGINTSIGQUIT 的默认操作是终止。

在您的特定情况下,您永远不会看到 child 的任何输出。这意味着 parent 将 SIGHUP 发送到 child,并且 SIGHUP 在 child 更改默认行为之前交付。所以child被杀死了。

实际上,如果您对 kill(2) 的 returning 值做了一些错误检查 - 您应该这样做 - 您会在尝试时在 parent 中看到 ESRCH发送 SIGINTSIGQUIT,因为 child 已经消失(假设系统中没有其他进程启动并且同时分配了相同的 PID)。

那么,你是如何解决的?要么使用某种形式的同步来强制 child 先到 运行 并且只让 parent 在所有信号处理程序安装之后执行,或者在 之前设置信号处理程序 分叉,然后在 parent 中取消设置。下面的代码使用后一种方法:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void sighup(int); /* routines child will call upon sigtrap */
void sigint(int);
void sigquit(int);

int main(void) {

    int pid;

    signal(SIGHUP,sighup); /* set function calls */
    signal(SIGINT,sigint);
    signal(SIGQUIT, sigquit);

    /* get child process */
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {

        /* child */
        for(;;); /* loop for ever */

    } else {

        signal(SIGHUP, SIG_DFL);
        signal(SIGINT, SIG_DFL);
        signal(SIGQUIT, SIG_DFL);

        /* parent */
        /* pid hold id of child */
        printf("\nPARENT: sending SIGHUP\n\n");
        kill(pid,SIGHUP);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGINT\n\n");
        kill(pid,SIGINT);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGQUIT\n\n");
        kill(pid,SIGQUIT);
        sleep(3);
    }

    return 0;
}

void sighup(int signo) {
    signal(SIGHUP,sighup); /* reset signal */
    printf("CHILD: I have received a SIGHUP\n");
}

void sigint(int signo) {
    signal(SIGINT,sigint); /* reset signal */
    printf("CHILD: I have received a SIGINT\n");
}

void sigquit(int signo) {
    printf("My DADDY has Killed me!!!\n");
    exit(0);
}

此外,您不应该使用 signal(2):它在很多方面都不可靠,而且它的确切语义取决于平台。为确保最大的可移植性,您应该使用 sigaction(2)。请参阅联机帮助页以了解更多信息。这是使用 sigaction(2) 代替的相同代码:

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

void sighup(int); /* routines child will call upon sigtrap */
void sigint(int);
void sigquit(int);

int main(void) {

    struct sigaction sigact;
    sigact.sa_flags = 0;
    sigemptyset(&sigact.sa_mask);

    sigact.sa_handler = sighup;
    if (sigaction(SIGHUP, &sigact, NULL) < 0) {
        perror("sigaction()");
        exit(1);
    }

    sigact.sa_handler = sigint;
    if (sigaction(SIGINT, &sigact, NULL) < 0) {
        perror("sigaction()");
        exit(1);
    }

    sigact.sa_handler = sigquit;
    if (sigaction(SIGQUIT, &sigact, NULL) < 0) {
        perror("sigaction()");
        exit(1);
    }

    pid_t pid;
    /* get child process */
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {

        /* child */
        for(;;); /* loop for ever */

    } else {

        sigact.sa_handler = SIG_DFL;
        sigaction(SIGHUP, &sigact, NULL);
        sigaction(SIGINT, &sigact, NULL);
        sigaction(SIGQUIT, &sigact, NULL);

        /* parent */
        /* pid hold id of child */
        printf("\nPARENT: sending SIGHUP\n\n");
        kill(pid,SIGHUP);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGINT\n\n");
        kill(pid,SIGINT);
        sleep(3); /* pause for 3 secs */
        printf("\nPARENT: sending SIGQUIT\n\n");
        kill(pid,SIGQUIT);
        sleep(3);
    }

    return 0;
}

void sighup(int signo) {
    signal(SIGHUP,sighup); /* reset signal */
    printf("CHILD: I have received a SIGHUP\n");
}

void sigint(int signo) {
    signal(SIGINT,sigint); /* reset signal */
    printf("CHILD: I have received a SIGINT\n");
}

void sigquit(int signo) {
    printf("My DADDY has Killed me!!!\n");
    exit(0);
}

最后但并非最不重要的一点是,您应该始终使用 -Wall 进行编译。你的程序有一些错误:

  • main()的return类型应该是int
  • 信号处理程序接收信号编号作为参数,请使用正确的原型和声明。
  • fork(2) return是pid_t,不是int,请使用正确的类型。
  • 您需要包含 unistd.h 以获得 fork(2) 的正确原型。
  • printf(3) 不 async-signal 安全,因此您不应该在信号处理程序中调用它。可以在这个玩具程序中查看信号如何协同工作,但请记住,您永远不应该在现实世界中这样做。要查看 async-signal 安全功能列表以及每个信号的默认操作,请参阅 man 7 signal

忠告:停止从该网站学习。如果你想学习这类东西,请阅读 UNIX 环境中的高级编程。直接转到第 10 章,了解为什么 signal(2) 被认为是不可靠和过时的。这是一本厚书,但值得花时间阅读。