为什么我在 Linux 信号处理时遇到意外行为?
Why am I experiencing unexpected behavior with Linux signal handling?
我生活在 Win7/MSVC 2010sp1 的环境中,两个不同的 Linux 框(Red Hat)g++ 版本(4.4.7、4.1.2)和 AIX xlc++(08.00. 0000.0025).
不久前有人要求我们将一些代码从 AIX 移到 Linux。没过多久就发现 Linux 有点不一样了。通常当一个信号被抛出时,我们处理它并抛出一个 C++ 异常。那没有按预期工作。
Long story short, throwing c++ exceptions from a signal handler isn't going to work.
稍后,我整理了一个修复程序,使用 setjmp/longjmp 将异常移出处理程序。经过一些测试,该死的东西适用于所有平台。在进行了一轮强制性的立方体快乐舞蹈之后,我继续设置一些单元测试。哎呀
我的一些测试在 Linux 上失败了。我观察到的是 raise 函数只工作一次。使用 SIGILL 进行了两次测试,第一个通过了,第二个失败了。我拿出一把斧头,开始砍掉代码,尽可能多地去除杂物。这就产生了这个较小的例子。
#include <csetjmp>
#include <iostream>
#include <signal.h>
jmp_buf mJmpBuf;
jmp_buf *mpJmpBuf = &mJmpBuf;
int status = 0;
int testCount = 3;
void handler(int signalNumber)
{
signal(signalNumber, handler);
longjmp(*mpJmpBuf, signalNumber);
}
int main(void)
{
if (signal(SIGILL, handler) != SIG_ERR)
{
for (int test = 1; test <= testCount; test++)
{
try
{
std::cerr << "Test " << test << "\n";
if ((status = setjmp(*mpJmpBuf)) == 0)
{
std::cerr << " About to raise SIGILL" << "\n";
int returnStatus = raise(SIGILL);
std::cerr << " Raise returned value " << returnStatus
<< "\n";
}
else
{
std::cerr << " Caught signal. Converting signal "
<< status << " to exception" << "\n";
std::exception e;
throw e;
}
std::cerr << " SIGILL should have been thrown **********\n";
}
catch (std::exception &)
{ std::cerr << " Caught exception as expected\n"; }
}
}
else
{ std::cerr << "The signal handler wasn't registered\n"; }
return 0;
}
对于 Windows 和 AIX 机器,我得到了预期的输出。
Test 1
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected Test 2
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected Test 3
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected
对于两个 Linux 框,它看起来像这样。
Test 1
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected
Test 2
About to raise SIGILL
Raise returned value 0
SIGILL should have been thrown **********
Test 3
About to raise SIGILL
Raise returned value 0
SIGILL should have been thrown **********
所以,我真正的问题是“这是怎么回事?”
我的反问是:
- 还有其他人观察到这种行为吗?
- 我应该怎么做才能解决这个问题?
- 我还应该注意什么?
您必须使用 sigsetjmp
/siglongjmp
来确保混合信号和跳转时的正确行为。如果您更改代码,它将在 Linux.
下正常工作
您还使用了不推荐的旧信号 API。我鼓励您使用更可靠的 sigaction
界面。第一个好处是您不再需要在处理程序中重置信号捕获...
我生活在 Win7/MSVC 2010sp1 的环境中,两个不同的 Linux 框(Red Hat)g++ 版本(4.4.7、4.1.2)和 AIX xlc++(08.00. 0000.0025).
不久前有人要求我们将一些代码从 AIX 移到 Linux。没过多久就发现 Linux 有点不一样了。通常当一个信号被抛出时,我们处理它并抛出一个 C++ 异常。那没有按预期工作。
Long story short, throwing c++ exceptions from a signal handler isn't going to work.
稍后,我整理了一个修复程序,使用 setjmp/longjmp 将异常移出处理程序。经过一些测试,该死的东西适用于所有平台。在进行了一轮强制性的立方体快乐舞蹈之后,我继续设置一些单元测试。哎呀
我的一些测试在 Linux 上失败了。我观察到的是 raise 函数只工作一次。使用 SIGILL 进行了两次测试,第一个通过了,第二个失败了。我拿出一把斧头,开始砍掉代码,尽可能多地去除杂物。这就产生了这个较小的例子。
#include <csetjmp>
#include <iostream>
#include <signal.h>
jmp_buf mJmpBuf;
jmp_buf *mpJmpBuf = &mJmpBuf;
int status = 0;
int testCount = 3;
void handler(int signalNumber)
{
signal(signalNumber, handler);
longjmp(*mpJmpBuf, signalNumber);
}
int main(void)
{
if (signal(SIGILL, handler) != SIG_ERR)
{
for (int test = 1; test <= testCount; test++)
{
try
{
std::cerr << "Test " << test << "\n";
if ((status = setjmp(*mpJmpBuf)) == 0)
{
std::cerr << " About to raise SIGILL" << "\n";
int returnStatus = raise(SIGILL);
std::cerr << " Raise returned value " << returnStatus
<< "\n";
}
else
{
std::cerr << " Caught signal. Converting signal "
<< status << " to exception" << "\n";
std::exception e;
throw e;
}
std::cerr << " SIGILL should have been thrown **********\n";
}
catch (std::exception &)
{ std::cerr << " Caught exception as expected\n"; }
}
}
else
{ std::cerr << "The signal handler wasn't registered\n"; }
return 0;
}
对于 Windows 和 AIX 机器,我得到了预期的输出。
Test 1
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected Test 2
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected Test 3
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected
对于两个 Linux 框,它看起来像这样。
Test 1
About to raise SIGILL
Caught signal. Converting signal 4 to exception
Caught exception as expected
Test 2
About to raise SIGILL
Raise returned value 0
SIGILL should have been thrown **********
Test 3
About to raise SIGILL
Raise returned value 0
SIGILL should have been thrown **********
所以,我真正的问题是“这是怎么回事?”
我的反问是:
- 还有其他人观察到这种行为吗?
- 我应该怎么做才能解决这个问题?
- 我还应该注意什么?
您必须使用 sigsetjmp
/siglongjmp
来确保混合信号和跳转时的正确行为。如果您更改代码,它将在 Linux.
您还使用了不推荐的旧信号 API。我鼓励您使用更可靠的 sigaction
界面。第一个好处是您不再需要在处理程序中重置信号捕获...