我有一个使用signal
和用户处理程序的简单程序。
#include <signal.h>
#include <stdio.h>
#include <zconf.h>
int x = 0;
int i = 3;
void catcher3(int signum) {
i = 1;
}
void catcher2(int signum) {
// Stuck in infinity loop here.
// Happens even with i == 0
if (i != 0) {
x = 5;
}
}
void catcher1(int signum) {
printf("i = %d\n", i);
i--;
if (i == 0) {
signal(SIGFPE, catcher2);
signal(SIGTERM, catcher3);
}
}
int main() {
signal(SIGFPE, catcher1);
x = 10 / x;
printf("Goodbye");
}
我希望它能打印:
3
2
1
Goodbye
它实际打印:
3
2
1
# Infinity loop within catcher2
我的问题是:
catcher1
之类的用户处理程序时,代码在处理程序执行后返回到哪一点?我希望它可以继续执行,但是会重新运行信号处理程序。SIGTERM
不会显示“再见”? (kill -s TERM <pid>
)答案 0 :(得分:1)
正如AProgrammer所指出的那样,即使x
被标记为易失性(无论如何也应该如此),程序从处理程序返回后也不一定读取x
。这是因为执行继续到有问题的指令。从内存中读取数据和实际划分可能是单独的指令。
要解决此问题,您必须继续执行到从存储器中读取x
之前的某个时刻。
您可以按以下步骤修改程序-
#include <csetjmp>
jmp_buf fpe;
volatile int x = 0; // Notice the volatile
volatile int i = 3;
void catcher2(int signum) {
if (i != 0) {
x = 5;
longjump(fpe, 1);
}
}
int main() {
signal(SIGFPE, catcher1);
setjump(fpe);
x = 10 / x;
printf("Goodbye");
}
其余功能可以保持不变。
您也不应使用信号处理程序中的printf
。而是直接使用write
将调试消息打印为-
write(1, "SIGNAL\n", sizeof("SIGNAL\n"));
答案 1 :(得分:1)
信号的处理非常复杂,并且充满了实现定义的,未指定的和未定义的行为。如果您想携带便携式设备,实际上可以做的事情很少。主要是读写volatile sig_atomic_t
和呼叫_Exit
。根据信号号的不同,是否以调用_Exit
以外的其他方式离开信号处理程序,通常是不确定的。
在您的情况下,我认为FPE
是通常留给UB的那些信号之一。我所看到的最好的是重新启动触发信号的机器指令。很少有架构,最后我发现x86不是其中的一种,它提供了一种无需将x加载到寄存器中即可执行10/x
的方法。这意味着即使您修改了x
和x
我们的volatile sig_atomtic_t
,重新启动指令也将始终重新启动信号。
通常longjmp
也可以离开信号处理程序。 @Bodo确认使用setjmp
和longjmp
重新启动除法,即可获得所需的行为。
注意:在Unix上,还有另外一组函数sigaction
,siglongjump
和其他函数比较好用。实际上,我不建议在任何严肃的程序中使用其他内容。