来自父节点的SIGTERM信号不会调用子节点中的信号处理程序?

时间:2018-03-26 08:45:57

标签: c linux signals signal-handling sigterm

我正在编写一个程序,其中子进程和父进程都可以向子进程发送SIGTERM信号。

信号处理程序是这样的:

void custom_signal_handler(int signum, siginfo_t* info, void* ptr) {
    if (signum == SIGTERM) {
        printf("1\n");
    }
    else if (signum == SIGCONT) {
        printf("2\n");
    }
}

(我已经简化了if中的打印,以使代码更简单。)

对于SIGCONT信号 - 只有父母可以使用kill(childPid, SIGCONT)调用此信号。发生这种情况时,孩子的信号处理程序会按预期打印“2”。

但是,对于SIGTERM信号,父母可以通过调用kill(childPid, SIGTERM)来发送raise(SIGTERM)和孩子来调用它。问题是“1”仅在孩子提出SIGTERM信号时打印,而不是在父母调用它时打印。

我已经让孩子的信号处理程序陷入困境:

// set up signal handler
struct sigaction custom_action;
memset(&custom_action, 0, sizeof(custom_action));
custom_action.sa_sigaction = custom_signal_handler;
custom_action.sa_flags = SA_SIGINFO;
// assign signal handlers
    if (0 != sigaction(SIGCONT, &custom_action, NULL)) {
        printf("Signal registration failed: %s\n",strerror(errno));
        return -1;
    }
    if (0 != sigaction(SIGTERM, &custom_action, NULL)) {
        printf("Signal registration failed: %s\n",strerror(errno));
        return -1;
    }

有什么想法吗?谢谢!

1 个答案:

答案 0 :(得分:1)

在对问题的评论中,OP陈述

  

当相关孩子处于“加注(SIGSTOP)”时,我从父母发送SIGTERM。我认为因为孩子在SIGSTOP中它不运行信号处理程序。

正确。当流程停止时,它不会收到SIGCONTSIGKILL以外的信号(加SIGSTOPSIGTSTPSIGTTINSIGTTOU忽略)。当进程继续时,所有其他信号应变为挂起。 (但是,标准POSIX信号不会排队,因此您只能依赖一个标准POSIX信号进行处理。)

  

但是,我只需要在子进程SIGSTOP时发送SIGTERM,而不是之前发送SIGCONT。

目标流程只有在继续之后才会收到SIGTERM。这就是停止进程的行为。

  

有解决方法吗?

也许;这取决于要求。但是请注意,您的预期用例涉及不符合POSIX的行为(即,您希望停止的流程对除了继续或直接杀死之外的其他事情做出反应);这就是你遇到问题的直接原因。

最简单的方法是使用SIGCONT的变体而不是SIGTERM来控制流程的终止;例如,通过sigqueue(),提供一个有效载荷标识符,告诉SIGCONT信号处理程序将其视为SIGTERM信号(从而区分正常的SIGCONT信号,以及SIGTERM的替身信号)。 p>

更复杂的是让进程分叉一个特殊的监视子进程,定期发送特殊的“检查挂起的SIGTERM信号” SIGCONT信号,并在父进程死亡时死掉。子进程可以通过管道连接到父进程(父进程具有写结束,子进程被读取),这样当父进程终止时,子进程上的read()返回0,子进程可以退出太。父进程SIGCONT处理程序只需要检测子进程是否发送了信号 - si_pid结构的siginfo_t字段只应与子进程ID匹配 - 如果由子进程发送 - ,如果所以,检查SIGTERM是否有待处理,如果是,则处理它;否则只是举起SIGSTOP。由于竞赛窗口的许多可能性,这种方法非常脆弱 - 特别是在接收SIGCONT之后提升SIGSTOP 。 (在信号处理程序中阻塞SIGCONT是必不可少的。此外,监视子进程应该位于一个单独的进程组中,而不是连接到任何终端,以避免被针对整个进程组的SIGSTOP停止。)

请注意,应该只在信号处理程序中使用异步安全函数,并保持errno不变,以保持一切正常工作。

为了将消息打印到标准错误,我经常使用

#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

static int wrerr(const char *msg)
{
    const int   saved_errno = errno;
    const char *end = msg;
    ssize_t     count;
    int         retval = 0;

    /* Find end of string. strlen() is not async-signal safe. */
    if (end)
        while (*end)
            end++;

    while (msg < end) {
        count = write(STDERR_FILENO, msg, (size_t)(end - msg));
        if (count > 0)
            msg += count;
        else
        if (count != -1) {
            retval = EIO;
            break;
        } else
        if (errno != EINTR) {
            retval = errno;
            break;
        }
    }

    errno = saved_errno;
    return retval;
}

不仅是异步信号安全,而且还保持errno不变。如果成功则返回0,否则返回errno错误代码。

如果为了清晰起见,我们稍微扩展了打印件,OP的自定义信号处理程序就变成了例如

void custom_signal_handler(int signum, siginfo_t* info, void* context) {
    if (signum == SIGTERM) {
        wrerr("custom_signal_handler(): SIGTERM\n");
    } else
    if (signum == SIGCONT) {
        wrerr("custom_signal_handler(): SIGCONT\n");
    }
}

请注意,在使用此功能时,一个程序根本不应使用stderr(来自<stdio.h>),以避免混淆。