为什么程序没有因信号值变化而终止?

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

我的问题是:

  1. 在 运行 上,像 catcher1 这样的用户处理程序,处理程序执行后的代码 returns 到哪一点?我希望它继续执行,但它会重新运行信号处理程序。
  2. 无限循环的原因是什么?
  3. 如何解决?
  4. 为什么发送 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 加载到寄存器中;这意味着重新启动指令将始终重新启动信号,即使您将 xx 修改为 volatile sig_atomtic_t.

通常longjmp也是可以离开signal handler的。 @Bodo 确认使用 setjmplongjmp 重新启动除法,你可以获得你想要的行为。


注:在Unix上还有另外一组函数,sigactionsiglongjump等,比较好用。事实上,我不建议在任何严肃的程序中使用其他东西。