执行默认信号处理程序

24

我编写了一个应用程序,其中在 Linux 中注册了多个不同信号的信号处理程序。当进程接收到信号时,控制权将转移到我注册的信号处理程序。在这个信号处理程序中,我需要做一些工作,然后我想调用默认信号处理程序,即 SIG_DFLSIG_IGN 。但是,SIG_DFLSIG_ING 都是宏,它们都扩展为数字值 0 和 1,这些值是无效的函数地址。

有没有办法调用默认操作,即 SIG_DFLSIG_IGN

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


4个回答

15

GNU C 库参考手册 中有一个完整的章节详细讲解了关于信号处理的一切。

当你安装自己的信号处理程序时(参见 manpages 的 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(也是无效的地址)。在这里,您可以什么也不做(仅返回即可)。

  3. else if (previous_handler == SIG_IGN)
    {
      return;
    } 
    否则(既不是SIG_IGN也不是SIG_DFL),您已经收到一个有效的函数指针,可以直接调用处理程序。
    else
    {
      previous_handler(sig);
    }
    当然,你也需要考虑不同的API(参见signal()sigaction()的manpage)。

当然,您还需要考虑不同的API(请参阅signal()sigaction()的man页面)。


1
我会认真考虑是否值得通过直接调用处理程序来进行优化。这不是“未来可靠的”。如果出现一个引入另一个特殊处理程序(SIG_FUN)的系统,那么上述优化将失败,因为它将尝试将其作为函数实际调用,而它不是有效的指针... - Adam Badura
12
你确定这样会奏效吗?GNU C库参考手册中指出(例如在24.7.5节),当信号处理程序执行时,该信号的传递会被阻止。因此,你的raise函数只会发送信号而不会调用处理程序。然后,你将重置回处理程序。在处理程序退出后,来自raise函数的信号将被传递,但会再次找到你自己的处理程序。 - Adam Badura
您提供的链接已失效。 - Koray Tugay
1
这个不应该被评价得那么高。正如Adam Badura所指出的那样,恢复SIGINT并调用默认处理程序会导致进程终止,这使得在进程因用户按下Ctrl+C或通过kill发送信号而(正确地)退出时尝试恢复先前的处理程序有点愚蠢。 - CubicleSoft

12

你可以保存先前的处理程序,在合适的时候调用它。

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

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 ,您可以访问旧的状态等)。


1
你也需要查看 sa_flags 中的 SA_SIGINFO 位,并调用 sa_sigactionsa_handler 吗? - Greg Hewgill
今天我正在学习有关Linux信号处理的知识,比我原本认为需要了解的还要多(请参见https://dev59.com/jVnUa4cB1Zd3GeqPe-m4)。 - Greg Hewgill
9
似乎如果之前的信号处理程序是SIG_IGNSIG_DFL,那么这段代码将无法正常工作,而这些情况往往是最有趣的。 - Adam Badura
1
有人能解释一下为什么这对SIG_DFL或SIG_IGN无效吗? - HardcoreHenry
可能需要修复这个打字错误: < new_sa.handler = my_handler
new_sa.sa_handler = my_handler
- ByteMe95

7
常规的方法是重置信号处理程序,然后再次使用raise()信号:
这是一个SIGINT处理程序示例:
void sigint_handler(int num)
{
    /* handle SIGINT */

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

但是如何在不更改处理程序的情况下仅调用默认处理程序?在您的代码中,sigint_handler 将不再用作 SIGINT 处理程序。而且没有明确的时间点可以将其设置回 sigint_handler - Adam Badura
@AdamBadura - 默认的SIGINT处理程序将终止进程。事实上,进程在默认处理程序执行后不再运行,这使得信号处理程序的恢复有点无意义。 - CubicleSoft
@CubicleSoft 在那种情况下你是正确的。但是当你不知道默认处理程序的作用时,通常并非如此。虽然原始问题似乎不仅限于 SIGINT - Adam Badura
提高信号不会导致当前处理程序再次被调用吗?当使用sigaction时似乎是这种情况。 - hookenz

2

考虑到信号处理程序是在内核中实现的,我唯一能想到的方法是:

  • 重置处理程序并
  • raise()再次发出该信号

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接