【问题标题】:executing default signal handler执行默认信号处理程序
【发布时间】:2019-08-21 04:58:02
【问题描述】:

我编写了一个应用程序,我在 linux 中为不同的信号注册了信号处理程序的数量。 进程收到信号后,控制权将转移到我注册的信号处理程序。在这个信号处理程序中,我做了一些我需要做的工作,然后我想调用默认的信号处理程序,即 SIF_DFL 或 SIG_IGN 。 但是,SIG_DFL 和 SIG_ING 都是宏,它们分别扩展为数值 0 和 1,它们是无效的函数地址。

有什么方法可以调用默认操作,即SIG_DFL 或SIG_IGN?

为了达到SIG_DFL或SIG_ING的效果,我分别调用了exit(1)和什么都不做。但是对于像SIGSEGV 这样的信号,我也希望有核心转储。 一般来说,我希望我的默认行为与SIG_DFL 相同,并忽略与SIG_IGN 相同的行为,就像操作系统一样。

【问题讨论】:

标签: c linux signals handlers


【解决方案1】:

The GNU C Library Reference Manual 有一整章解释了有关信​​号处理的所有内容。

当您安装自己的处理程序时,您总是会获得先前设置的信号处理程序(函数指针)(请参阅手册页以了解 signal() 或 sigaction())。

previous_handler = signal(SIGINT, myhandler);

一般规则是,您始终可以重置为先前的处理程序并再次raise() 信号。

void myhandler(int sig) {
  /* own stuff .. */
  signal(sig, previous_handler);
  raise(sig);
  /* when it returns here .. set our signal handler again */
  signal(sig, myhandler);
}

一般规则有一个缺点:映射到信号的硬件异常通常分配给导致异常的特定指令。因此,当您再次发出信号时,关联的指令与最初的指令不同。这可以但不应损害其他信号处理程序。

另一个缺点是,每个提升的信号都会导致大量的处理时间。为防止过度使用raise(),您可以使用以下替代方法:

  1. 在SIG_DFL 的情况下,函数指针指向地址0(这显然不是有效地址)。因此,您必须重置处理程序并再次raise() 信号。

    if (previous_handler == SIG_DFL)
    {
      signal(sig, SIG_DFL);
      raise(sig);
      signal(sig, myhandler);
    } 
  2. SIG_IGN 的值为 1(也是无效地址)。在这里你可以返回(什么都不做)。

    else if (previous_handler == SIG_IGN)
    {
      return;
    } 
  3. 否则(SIG_IGN 和 SIG_DFL 都没有)你收到了一个有效的函数指针,你可以直接调用处理程序,

    else
    {
      previous_handler(sig);
    }

当然,您还必须考虑不同的 API(参见 signal() 和 sigaction() 的手册页)。

【讨论】:

  • 我会强烈考虑是否值得通过直接调用处理程序进行优化。这不是“面向未来的”。如果一个系统会引入另一个特殊处理程序(SIG_FUN),那么上述优化将失败,因为它会尝试在它不是有效指针时实际调用它作为函数......
  • 另外你确定这会起作用吗? GNU C 库参考声明(例如在 24.7.5 中)在执行该信号的处理程序时阻止了信号的传递。所以你的raise 只会发送信号而不是调用处理程序。然后您将重置处理程序。在您的处理程序存在后,来自raise 的信号将被传递,但会找回您自己的处理程序。
  • 您提供的链接已失效。
  • 这不应该被评价这么多。正如 Adam Badura 指出的那样,它不仅不起作用,恢复 SIGINT 并调用默认处理程序具有终止进程的效果,这使得当您的进程(正确)恢复之前的处理程序时尝试恢复以前的处理程序有点愚蠢在用户按下 Ctrl+C 或通过 kill 发送信号后消失。
【解决方案2】:

您可以保存之前的处理程序,然后在适当的时候调用它。

安装处理程序。确保保存旧的处理程序

static struct sigaction new_sa, old_sa;

new_sa.sa_handler = my_handler;
sigemptyset(&new_handler.sa_mask);

if (sigaction(signo, &new_sa, &old_sa) == -1) {
    /* handle sigaction error */
}

在您的新处理程序中,调用旧处理程序

(*old_sa.sa_handler)(signo)

你不需要再举起它或做任何乱七八糟的事情;只需调用旧的处理程序(当然,因为您保存了 sigaction,所以您可以访问旧的处置等等)。

【讨论】:

  • 您是否还需要查看sa_flags 中的SA_SIGINFO 位,然后调用sa_sigaction 或sa_handler?
  • 今天我学到的关于 Linux 信号处理的知识比我想象的要多(参见 stackoverflow.com/questions/6935988/…)。
  • 如果之前的处理程序是SIG_IGN 或SIG_DFL,这似乎是最有趣的情况。
  • 有人能解释一下为什么这对 SIG_DFL 或 SIG_IGN 不起作用吗?
  • 可能要修正这个错字: new_sa.sa_handler = my_handler
【解决方案3】:

通常的做法是重置信号处理程序,然后再次raise() 信号:

这是一个 SIGINT 处理程序示例:

void sigint_handler(int num)
{
    /* handle SIGINT */

    // call default handler
    signal(SIGINT, SIG_DFL);
    raise(SIGINT);
}

【讨论】:

  • 但是如何只调用默认处理程序而不更改处理程序呢?在您的代码中,sigint_handler 将不再用作 SIGINT 处理程序。并且没有明确的点将其设置回sigint_handler。
  • @AdamBadura - SIGINT 的默认处理程序将终止进程。在默认处理程序执行后进程将不再运行的事实确实使信号处理程序恢复有点争议。
  • @CubicleSoft 在这种情况下你是对的。但一般情况下,当您不知道默认处理程序做什么时。虽然最初的问题似乎不限于SIGINT。
  • 提升信号不会导致当前处理程序再次被调用吗?使用 sigaction 时似乎就是这种情况。
【解决方案4】:

鉴于信号处理程序是在内核中实现的,我看到的唯一方法是

  • 重置处理程序并
  • raise()又发信号了

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多