【问题标题】:SIGTERM signal from parent does not invoke signal handler in child?来自父级的 SIGTERM 信号不会在子级中调用信号处理程序?
【发布时间】:2018-09-04 08:30:47
【问题描述】:

我正在编写一个程序,其中子进程和父进程都可以向子进程发送 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");
    }
}

(我已经简化了ifs 中的打印以使这里的代码更简单)。

对于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;
    }

有什么想法吗?谢谢!

【问题讨论】:

  • custom_action是如何初始化的?您能尝试创建一个Minimal, Complete, and Verifiable Example 并展示给我们看吗?如果您这样做raise(SIGTERM),会发生任何事情吗?您是否检查过(可能使用调试器)根本没有调用信号处理程序?你检查过raise 返回的内容吗?
  • 在这种情况下,你应该关心刷新输出。
  • @Someprogrammerdude 我已经编辑了问题以包含 sigaction 初始化。对于调试,我检查了 kill(childPid,SIGTERM) 是否被调用。 raise(SIGTERM) 有效(我在问题中写过)。谢谢!
  • 哦,请记住 printf 不是 signal-safe 函数。没有真正的 C stdio 函数。
  • @Someprogrammerdude 使用sprintf 后跟写应该是安全的 “可能是安全的”可能更准确。 Linux man page 没有提到 sprintf() 是异步信号安全的。例如,Solaris man page 明确声明“sprintf()snprintf() 函数是异步信号安全的。”。由于字符串是固定的,因此不需要任何 *printf() 函数。 write( 2, "1\n", strlen( "1\n" ) ); 就足够了。

标签: c linux signals signal-handling sigterm


【解决方案1】:

在对问题的评论中,OP 声明

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

正确。当一个进程停止时,它不会收到除SIGCONTSIGKILL 以外的信号(加上SIGSTOPSIGTSTPSIGTTINSIGTTOU 被忽略)。所有其他信号都应变为待处理,并在进程继续时传递。 (不过,标准 POSIX 信号没有排队,因此您只能依赖一个标准 POSIX 信号等待处理。)

但是,我确实需要仅在孩子处于 SIGSTOP 时发送 SIGTERM,而不需要之前发送 SIGCONT。

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

有解决办法吗?

也许;这取决于要求。但请注意,您的预期用例涉及不符合 POSIX 的行为(即,您希望停止的进程对某些事情做出反应,而不仅仅是继续或直接终止);这就是你遇到问题的直接原因。

最简单的就是用SIGCONT的变体代替SIGTERM,来控制进程的终止;例如,通过sigqueue(),提供一个有效载荷标识符,告诉 SIGCONT 信号处理程序将其视为 SIGTERM 信号(从而区分正常的 SIGCONT 信号和 SIGTERM 的替代信号)。

一个更复杂的方法是让进程派生一个特殊的监控子进程,它定期发送特殊的“检查挂起的 SIGTERM 信号” SIGCONT 信号,并在父进程死亡时终止。子进程可以通过管道连接到父进程(父进程有写端,子进程有读端),这样当父进程死亡时,子进程的read()返回0,子进程也可以退出。父进程 SIGCONT 处理程序只需要检测信号是否是由子进程发送的——siginfo_t 结构的si_pid 字段应该只匹配子进程 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(来自&lt;stdio.h&gt;),以避免混淆。

【讨论】:

  • strlen() 不是异步信号安全的 是的,但我对此很感兴趣。你必须故意在实现中做一些疯狂的事情来使strlen() async-signal-unsafe。甚至是specified as being async-signal-safe on some OSes
  • @AndrewHenle:在我看来,不是真的,绝对不是 LOL 材料。例如,考虑 x86 和 x86-64 上的字符串方向标志(这会影响 stosmovs 指令是自动递增还是自动递减地址)。在大致相似的架构上,方向不是标准标志的一部分,ABI 可以说普通函数不需要保留方向,但异步信号安全函数是。在通用strlen() 中避免额外成本(保持方向)是有意义的,使其成为非异步信号安全的。
  • strlen 在第 7 期最终被认为是异步安全的。austin-group-l.opengroup.narkive.com/jBp07fPN/… 包含了 2013 年的一些讨论。
  • @MarkPlotnick:他们是否考虑过除了 x86 之外的其他架构?
  • (我最喜欢的抱怨之一是 malloc() 不需要为向量类型返回足够对齐的内存,因为向量类型不是标准类型。它类似于 Firefox 和 Chrome 开发人员,他们决定你不应该使用 UTF-8 作为默认字符集,因为默认字符集必须是遗留字符集,而 UTF-8 是唯一的非遗留字符集。有些人可能完全是功能失调的白痴,而被视为你看,非常聪明和富有成效。)
猜你喜欢
  • 1970-01-01
  • 2016-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多