为什么程序没有因信号值变化而终止?
Why the program not terminated on signal value change?
我有一个使用 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
这样的用户处理程序,处理程序执行后的代码 returns 到哪一点?我希望它继续执行,但它会重新运行信号处理程序。
- 无限循环的原因是什么?
- 如何解决?
- 为什么发送
SIGTERM
不会打印 "Goodbye"? (kill -s TERM <pid>
)
正如 AProgrammer 所指出的,程序在从处理程序返回后不一定会读取 x
,即使 x
被标记为 volatile(无论如何它应该是)。这是因为执行会继续到有问题的指令。从内存中读取和实际除法可以是单独的指令。
要解决这个问题,您必须继续执行到 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"));
信号的处理很复杂,并且充满了实现定义的、未指定的和未定义的行为。如果你想便携,实际上你能做的事情很少。主要是读写volatile sig_atomic_t
和调用_Exit
。根据信号编号,如果您以另一种方式离开信号处理程序而不是调用 _Exit
.
,则通常是未定义的
在你的情况下,我认为 FPE
是那些通常离开信号处理程序是 UB 的信号之一。我能看到的最好的是重新启动触发信号的机器指令。很少有架构,最后我看到 x86 不是其中之一,提供了一种方法来执行 10/x
而无需将 x 加载到寄存器中;这意味着重新启动指令将始终重新启动信号,即使您将 x
和 x
修改为 volatile sig_atomtic_t
.
通常longjmp
也是可以离开signal handler的。 @Bodo 确认使用 setjmp
和 longjmp
重新启动除法,你可以获得你想要的行为。
注:在Unix上还有另外一组函数,sigaction
、siglongjump
等,比较好用。事实上,我不建议在任何严肃的程序中使用其他东西。
我有一个使用 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
这样的用户处理程序,处理程序执行后的代码 returns 到哪一点?我希望它继续执行,但它会重新运行信号处理程序。 - 无限循环的原因是什么?
- 如何解决?
- 为什么发送
SIGTERM
不会打印 "Goodbye"? (kill -s TERM <pid>
)
正如 AProgrammer 所指出的,程序在从处理程序返回后不一定会读取 x
,即使 x
被标记为 volatile(无论如何它应该是)。这是因为执行会继续到有问题的指令。从内存中读取和实际除法可以是单独的指令。
要解决这个问题,您必须继续执行到 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"));
信号的处理很复杂,并且充满了实现定义的、未指定的和未定义的行为。如果你想便携,实际上你能做的事情很少。主要是读写volatile sig_atomic_t
和调用_Exit
。根据信号编号,如果您以另一种方式离开信号处理程序而不是调用 _Exit
.
在你的情况下,我认为 FPE
是那些通常离开信号处理程序是 UB 的信号之一。我能看到的最好的是重新启动触发信号的机器指令。很少有架构,最后我看到 x86 不是其中之一,提供了一种方法来执行 10/x
而无需将 x 加载到寄存器中;这意味着重新启动指令将始终重新启动信号,即使您将 x
和 x
修改为 volatile sig_atomtic_t
.
通常longjmp
也是可以离开signal handler的。 @Bodo 确认使用 setjmp
和 longjmp
重新启动除法,你可以获得你想要的行为。
注:在Unix上还有另外一组函数,sigaction
、siglongjump
等,比较好用。事实上,我不建议在任何严肃的程序中使用其他东西。