【问题标题】:How to repeatedly restart a program via signal如何通过信号反复重启程序
【发布时间】:2020-03-15 08:29:50
【问题描述】:

我想要一种方便的方法来重新启动程序。我以为我可以捕获一个信号(示例中为 USR1)并调用 exec。

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

char* const* args;
void restart() {
    printf("restarting\n");
    execv(args[0], args);
}

int main(int argc, char* const argv[]) {
    printf("Starting\n");
    args = argv;
    signal(SIGUSR1, restart);
    raise(SIGUSR1); // could use pkill -SIGUSR1 file-name instead
    pause();
    printf("Terminated normally\n");
    return 0;
}

上面的例子有点工作。输出是

Starting
restarting
Starting

然后挂起。仍然可以接收其他信号。

我想我只是没能清除信号。预期的行为是程序无限期地重新启动。

【问题讨论】:

  • printf() 不保证是异步信号安全的。不要从信号处理程序中调用它。安全功能列表在这里:man7.org/linux/man-pages/man7/signal-safety.7.html
  • 有没有想过使用 setjmp 和 longjmp?将jmp设置为程序的开头,当您收到信号时,将longjmp设置为程序的开头。这样,每次获得 SIGUSR1 时,您只会在内存中拥有一个进程,而不是一个新进程。
  • @cup:也许,但这不会重新启动程序。无论如何,它不会改变 OP 的问题;处理程序仍然不会触发第二次。
  • raise() 不会生成异步信号——但在信号处理程序中使用printf() 仍然不是一个好主意。见:How to avoid using printf() in a signal handler?

标签: c linux signals posix


【解决方案1】:

Linux 的man 2 signal 解释了当您使用signal 设置信号处理程序时会发生什么。有两种可能:

  1. 信号的处置被重置为SIG_DFL,然后调用处理程序。要再次处理此信号,您​​需要重新建立信号处理程序。这会按您的预期工作。

或者:

  1. 信号被阻塞,然后调用处理程序。当处理程序返回时,信号被解除阻塞。如果处理程序没有返回,信号仍然被阻塞,这就是发生在你身上的事情。

因此,一个适用于您的代码,而另一个则不适用。但这两者中的哪一个适用?没有可靠的知道方法。 Posix 允许两种可能性,并且两种可能性都存在于不同的平台上。为此,Linux 手册页建议:

signal() 的唯一可移植用途是将信号的处置设置为SIG_DFLSIG_IGN...[D]不要将其用于[建立信号处理程序的目的]。

POSIX.1 通过指定sigaction(2) 解决了可移植性问题,这在调用信号处理程序时提供了对语义的显式控制;使用该界面而不是signal()

这是个好建议。当您更改为使用sigaction 时,您可能想要选择上面的第一个选项,这需要:

sa.sa_flags = SA_RESETHAND | SA_NODEFER

这也来自 Linux 联机帮助页,非常值得完整阅读。 (当然,sigaction 联机帮助页更相关。)

【讨论】:

  • #1 不太密切——程序的每个执行生命周期处理一个且仅一个 SIGUSR1 实例,然后仅在指定用户定义的处理程序进行处置之后,因此无需担心 SIG_DFL行为。
  • @pilcrow:如果 OP 的平台选择选项 1,就可以了。但事实并非如此,确保这种行为的唯一可移植方法是使用sigaction。我引用了这两个行为来强调signal 不能便携使用,我认为这是这里的重点。一旦你决定使用sigaction,解决方案很简单。
  • @pilcrow:话虽如此,如果你不指定SA_RESETHAND,你就会让处理程序被来自不同进程的大量信号递归地输入。所以这并不是完全不相关的。
  • 也许这很明显,但man sigaction 提供了如何使用 sigaction 的完整示例,并且使用此答案中提供的标志为我提供了解决方案
【解决方案2】:

在您的平台上,SIGUSR1 隐藏在信号处理程序中,这是 signal 的典型但不是强制行为(相比之下,请参阅 SA_NODEFER and sigaction)。

这个mask is inherited across the execve,因此 SIGUSR1 在您的第二次执行中处于等待状态并且从未交付。

main() 的顶部尝试这样的事情,也许在处理程序内部,看看发生了什么:

static int
is_blocked(int sig) {
  sigset_t ss;
  sigemptyset(&ss);
  (void)sigprocmask(SIG_BLOCK, NULL, &ss);
  return sigismember(&ss, sig);
}

【讨论】:

    【解决方案3】:

    不知道为什么会这样 - 用 sigset 替换信号

    首先,定义 __USER_XOPEN_EXTENDED 否则 sigset 未定义

    #define __USE_XOPEN_EXTENDED
    #include <signal.h>
    

    将信号更改为 sigset

    ...
    sigset(SIGUSR1, restart);
    ...
    

    然后它会继续重新启动,直到它被杀死。也许有人可以解释为什么 sigset 有效而 signal 无效。

    【讨论】:

    • 过时的sigset(signal, disposition) 函数可以工作,因为如果 dispo 不是 SIG_HOLD,则从调用线程的掩码中删除 signalspecified .因此,在 USR1 被阻止的情况下调用处理程序,在 USR1 仍然被阻止的情况下重新执行程序,然后对 sigset 的调用将其解除阻止以允许继续传递。
    猜你喜欢
    • 2012-04-28
    • 1970-01-01
    • 2013-10-26
    • 1970-01-01
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多