【问题标题】:signal handling信号处理
【发布时间】:2011-02-01 13:55:33
【问题描述】:

我只是在 Mac OS X 中玩信号。

为什么在我的信号处理程序完成后,以下代码不会产生 SIGSEGV 的默认行为? 在 Linux 下,代码运行良好。

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

void err_crash();

void my_signal_handler (int signal)
{
    fprintf(stderr, "signal == %i\n", signal);
    fflush(stderr);
    err_crash();
}

void err_crash()
{
    static int count= 0;
    if (count)
        signal(SIGSEGV, SIG_DFL);       /* break recursion loop if we recursed */
    count++;

    // one of the two should really crash ;)
    ((void)(*((int volatile *)NULL)));
    *((int *)NULL) = 42;

    abort();                            /* in case we're not crashed yet... */
}

int main ()
{
    signal(SIGSEGV, my_signal_handler);
    err_crash();
    return 0;
}

编辑: 我得到的输出如下:

bonecrusher:devel sw$ g++ signal_problems.cpp -o foo
bonecrusher:devel sw$ ./foo 
signal == 11
^C
bonecrusher:devel sw$

问题是我希望程序在signal == 11 的输出之后终止,但它永远运行,我不得不中断它。

【问题讨论】:

  • 你为什么期望 ((void)(*((int volatile *)NULL)));坠毁?您不要将其用作左值或右值或函数指针的调用。 volatile 无济于事,因为您什么也没做。
  • @datenwolf:这不是问题,看我的编辑。
  • 奇怪。我的 Mac 上只有 Bus error

标签: c++ c linux macos signals


【解决方案1】:

这实际上让我大脑冻结了几分钟,而在这个时代永远不要使用signal()的原因只会让我变得更强大。

首先,来自signal() 的手册页

signal() 的行为因人而异 UNIX 版本,并且也有所不同 历史上跨越不同版本 Linux 的。避免使用:使用 sigaction(2) 代替。

再往下:

  • 如果处置设置为函数,则首先 处置重置为 SIG_DFL,或 信号被阻塞(请参阅便携性 下面),然后调用处理程序 带参数符号。如果调用 处理程序的原因导致信号 阻塞,则信号畅通 从处理程序返回时。

在最初的 Unix 系统中,当安装了处理程序时,处置被重置为 SIG_DFL, 阻止 same 类型的传入信号,然后运行处理函数。 System V 提供了这一点,linux 内核也提供了这一点。

这意味着,一旦代码在linux系统上运行,一旦调用了第二个异常,它将直接退出。

现在进入有趣的部分。 BSD 试图改善这种行为。再次从手册页:

在 BSD 上,当信号处理程序 调用时,信号处置不是 重置,以及进一步的实例 信号被阻止 在处理程序运行时交付 正在执行。

而且由于 mac osx 部分基于 BSD,一旦代码在 mac osx 上运行,一旦调用了第二个异常,它将 pending 并等待第一个异常的处理程序出口。但是由于您永远不会退出,因此您陷入了僵局。

这就是为什么应该使用sigaction() 而永远不要使用signal()

现在给一些提示:

处理程序应该很短,并且可以快速返回。如果您正在执行计算并调用其他函数,您可能做错了什么。信号不能替代事件驱动的框架。

调用非异步安全的函数是不好的。考虑一下如果在调用fprintf 期间发生异常,并且在处理程序中再次调用fprintf 会发生什么情况。信号处理程序和程序数据都可能被破坏,因为它们在流本身上运行。

更多阅读:"Do" and "Don't" inside A Signal Handler

【讨论】:

    【解决方案2】:

    根据POSIX

    如果在阻塞时生成任何 SIGFPE、SIGILL、SIGSEGV 或 SIGBUS 信号,则结果未定义,除非该信号是由 kill() 函数、sigqueue() 函数或 raise() 生成的函数。

    因为 SIGSEGV 在 SIGSEGV 信号处理程序中被阻塞,所以在这种情况下结果是未定义的,任何行为都是有效的。如果您不希望它被阻塞,您可以使用带有SA_NODEFER 标志的sigaction() 安装信号处理程序,或者使用sigprocmask() 在信号处理程序中解除对信号的阻塞。

    【讨论】:

      【解决方案3】:

      在调试器中运行它并单步执行您预计会崩溃的指令。

      不保证写入无效地址一定会产生分段错误。也许 Mac OS X 会为您映射这些地址,而您正在覆盖一些良性的内容。

      【讨论】:

      • 我已经尝试过了,出现分段违规。我在控制台上得到signal == 11 作为输出,但随后程序并没有中止而是永远运行。
      • 顺便说一句:使用gdb(在 Mac OS X 上)进行调试没有帮助,因为它安装了自己的信号处理程序。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-07
      • 2010-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多