从父进程发送信号到子进程

7
我正在使用来自网站 http://www.code2learn.com/2011/01/signal-program-using-parent-child.html 的教程,尝试理解为什么子进程没有接收到信号?
以下是代码:
#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);  
}  

输出: 输入图像说明
1个回答

14

这是一种竞态条件。你的代码假定子进程先运行,并且在安装所有信号处理程序并开始无限循环之前,不会被父进程抢占。

但事实并非如此,父进程可能会在子进程有机会捕获信号之前向子进程发送信号。因此,子进程被杀死,因为 SIGHUPSIGINTSIGQUIT 的默认动作是终止进程。

在你的特定情况下,你从未看到子进程的任何输出。这意味着父进程向子进程发送了 SIGHUP,并且在子进程更改默认行为之前,SIGHUP 已经被传递。所以子进程被杀死了。

实际上,如果你对 kill(2) 返回值进行了一些错误检查(你应该这样做),你会在父进程中看到 ESRCH,当尝试发送 SIGINTSIGQUIT 时,因为子进程已经消失了(假设在此期间没有其他进程启动并被分配相同的 PID)。

那么,你该如何解决它呢?要么使用某种形式的同步来强制子进程先运行,并且只有在安装所有信号处理程序之后才允许父进程执行,要么在分叉之前设置信号处理程序,然后在父进程中取消它们。下面的代码使用后者的方法:

#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()的返回类型应该为int
  • 信号处理程序将信号号码作为参数接收,请使用正确的原型和声明。
  • fork(2)返回一个pid_t而不是int,请使用正确的类型。
  • 你需要包含unistd.h以获取fork(2)的正确原型。
  • printf(3)不是异步信号安全的,因此在信号处理程序中不应调用它。在这个玩具程序中查看信号如何共同工作是可以的,但请记住在现实世界中永远不要这样做。要查看异步信号安全函数的列表以及每个信号的默认操作,请参见man 7 signal

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


谢谢兄弟,真的帮了我很多。如果可能的话,我会额外投票给你推荐好书的建议。 - user3162878
不如阅读书籍而非浏览网站(和其他详尽的、有力的解释)。 - msw
1
我有一些类似但可能不同的问题,你能帮我吗?https://stackoverflow.com/questions/54268717/why-i-cant-catch-the-signal-in-the-child-which-is-sent-by-parent - Gábor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接