为什么程序未因信号值更改而终止?

时间:2019-11-15 15:19:01

标签: c concurrency signals

我有一个使用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之类的用户处理程序时,代码在处理程序执行后返回到哪一点?我希望它可以继续执行,但是会重新运行信号处理程序。
  2. 什么原因导致无限循环?
  3. 如何解决?
  4. 为什么发送SIGTERM不会显示“再见”? (kill -s TERM <pid>

2 个答案:

答案 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的方法。这意味着即使您修改了xx我们的volatile sig_atomtic_t,重新启动指令也将始终重新启动信号。

通常longjmp也可以离开信号处理程序。 @Bodo确认使用setjmplongjmp重新启动除法,即可获得所需的行为。


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