为什么不处理SIGUSR1信号?

时间:2016-12-05 23:06:53

标签: c

我正在编写一个Unix程序,其中父进程分叉一个子进程,之后它必须发送一个由handler_sigusr1处理的SIGUSR1信号和一个SIGKILL信号来完成该程序。 我的问题是,当我运行程序时,子程序应该接收SIGUSR1然后接收SIGKILL,但它只接收SIGKILL。 我的代码有问题吗?谢谢你的帮助!

void handler_sigusr1()
{
    printf("Signal SIGUSR1\n");
}

int main(int argc, char **argv)
{
    struct sigaction action;
    sigset_t new_mask, oldmask;
    int status;

    action.sa_flags = 0;

    sigaction(SIGTERM, &action, NULL);
    sigemptyset(&new_mask);
    sigaddset(&new_mask, SIGTERM);

    sigaction(SIGUSR1, &action, NULL);
    action.sa_handler = handler_sigusr1;
    sigaddset(&new_mask, SIGUSR1);

    child1 = fork();
    if(child1 == 0)
    {
        sigprocmask(SIG_BLOCK, &new_mask, &oldmask);
        sigsuspend(&new_mask);
    }
    if(child1 != 0)
    {
        kill(child1, SIGUSR1);
        kill(child1, SIGKILL);
    }
    wait(&status);
}

3 个答案:

答案 0 :(得分:7)

有4个问题:

第1期

设置信号处理程序的代码是错误的。

这两行的顺序错误:

    sigaction(SIGUSR1, &action, NULL);
    action.sa_handler = handler_sigusr1;

他们应该

    action.sa_handler = handler_sigusr1;
    sigaction(SIGUSR1, &action, NULL);

第2期

您的父进程在发送SIGUSR1后立即将SIGKILL发送给您的孩子。 SIGKILL将终止您的子进程 - 不能阻止或忽略它。

除非你很幸运,否则你的孩子的sigusr1处理程序将无法在父发送SIGKILL之前运行或完成 - 这会立即终止子进程。

您可以通过插入类似的延迟来大大增加SIGUSR1的交付和处理机会:

kill(child1, SIGUSR1);
sleep(1);
kill(child1, SIGKILL);

第3期

您已阻止SIGUSR1,因此无法将其传送到您的流程。

删除行

 sigaddset(&new_mask, SIGUSR1);

请注意,当使用sigprocmask()sigsuspend()设置信号掩码时,掩码中设置的信号是阻止的信号。

第4期

您无法在信号处理程序中安全地使用printf()。

printf()不是信号异步安全,也不能在信号处理程序中使用。如果您不幸,它可能不会像在信号处理程序中使用时那样表现。使用write()代替直接写入stdout,例如

 write(1, "Signal SIGUSR1\n", 15);

答案 1 :(得分:4)

第一次通过

这里是您的代码的修改版本,似乎有效。请注意,没有进程可以阻止SIGKILL或SIGSTOP。我删除了SIGTERM代码,因为没有使用。信号处理程序调用{​​{1}}而不是write(),因为printf()是异步信号安全的。它将信号编号手动编码到消息中,因为write()等函数不是异步信号安全的(正式都不是sprintf()) - 有关详细信息,请参阅How to avoid using printf() in a signal handler

strlen()

示例运行:

#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

static void handler_sigusr1(int signum)
{
    char msg[] = "Signal ?? SIGUSR1\n";
    msg[7] = (signum / 10) + '0';
    msg[8] = (signum % 10) + '0';
    write(2, msg, sizeof(msg) - 1);
}

int main(void)
{
    struct sigaction action;

    action.sa_flags = 0;
    action.sa_handler = handler_sigusr1;
    sigemptyset(&action.sa_mask);

    sigaction(SIGUSR1, &action, NULL);

    int child1 = fork();
    if (child1 == 0)
    {
        sigset_t new_mask, oldmask;
        sigemptyset(&new_mask);
        sigaddset(&new_mask, SIGUSR1);
        sigprocmask(SIG_BLOCK, &new_mask, &oldmask);
        sigsuspend(&new_mask);
        printf("Child (%d) unsuspended\n", (int)getpid());
    }

    if (child1 != 0)
    {
        kill(child1, SIGUSR1);
        sleep(1);
        kill(child1, SIGKILL);
        int status;
        int corpse = wait(&status);
        printf("Child (%d) - corpse = %d (0x%.4X)\n", child1, corpse, status);
    }
    return 0;
}

显然,第一条消息来自孩子的信号处理程序。它表明已收到SIGUSR1。第二条消息来自父母。十六进制数表示孩子死于信号9(又名SIGKILL)。

第二次通过

子进程中的代码是做什么的?答:困惑我​​(也可能是你)。我在Signal 30 SIGUSR1 Child (41875) - corpse = 41875 (0x0009) 之前添加了额外的printf(),然后运行代码并显示:

sigsuspend()

Signal 30 SIGUSR1 Child (42688) suspending Child (42688) - corpse = 42688 (0x0009) 调用会阻止SIGUSR1,但我的计算机上的父级太快,在生效之前获取信号。 sigprocmask()然后在SIGUSR1被阻止的情况下进入睡眠状态;由于SIGKILL,这个过程就会消失。

此代码变体根本不使用sigsuspend()。它有一个非常不同的效果:

sigprocmask()

示例输出:

#include <signal.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

static void signal_handler(int signum)
{
    char msg[] = "Signal ??\n";
    msg[7] = (signum / 10) + '0';
    msg[8] = (signum % 10) + '0';
    write(2, msg, sizeof(msg) - 1);
}

int main(void)
{
    struct sigaction action;

    action.sa_flags = 0;
    action.sa_handler = signal_handler;
    sigemptyset(&action.sa_mask);

    sigaction(SIGUSR1, &action, NULL);
    sigaction(SIGTERM, &action, NULL);

    int child1 = fork();
    if (child1 == 0)
    {
        sigset_t new_mask;
        sigemptyset(&new_mask);
        printf("Child (%d) suspending\n", (int)getpid());
        sigsuspend(&new_mask);
        printf("Child (%d) unsuspended\n", (int)getpid());
    }
    else
    {
        kill(child1, SIGUSR1);
        sleep(1);
        kill(child1, SIGTERM);
        sleep(1);
        kill(child1, SIGUSR1);
        sleep(1);
        kill(child1, SIGKILL);
        int status;
        int corpse = wait(&status);
        printf("Child (%d) - corpse = %d (0x%.4X)\n", child1, corpse, status);
    }

    return 0;
}

这次,孩子在报告已取消暂停之后正常死亡,因为Signal 30 Child (42969) suspending Signal 15 Child (42969) unsuspended Child (42969) - corpse = 42969 (0x0000) SIGTERM未被阻止。

您可以响应变体,将SIGUSR1和/或SIGTERM添加到传递给SIGUSR1的信号掩码中,可能是为了响应不同的命令行选项。

我注意到macOS Sierra上sigsuspend()的手册页说:

  

信号掩码设置通常为空,表示在呼叫期间所有信号都将被解锁。

答案 2 :(得分:0)

简单 - 子进程在被杀死之前没有时间处理SIGUSR1信号。