为什么这个程序在用户的信号处理程序之后没有终止

Why this program not terminated after user's signal handler

调试下一个代码片段时,调试器 returns 到行 x=10/xx=5 并且仍然得到 SIGFPE.

#include <signal.h>

volatile sig_atomic_t x = 0;

void sigfpe_handler(int signum) {
    x = 5;

  // Notice that there is no exit()
}

int main() {
    signal(SIGFPE, sigfpe_handler);
    x = 10 / x;
    return 0;
}

您通常 returns 为 SIGFPE 安装一个信号处理程序,然后执行一些操作使您的系统生成该信号。

来自 POSIX 规范:

The behavior of a process is undefined after it returns normally from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or SIGSEGV signal that was not generated by kill(), sigqueue(), or raise().

C 标准中有类似的措辞。所以接下来发生的事情不是您可以控制的,因为它是未定义的行为。您的程序可能会继续运行,可能会进入无限循环等。

此外,您的 x 变量需要是 volatile sig_atomic_t 类型,如果您希望在处理程序中对其所做的更改能够在处理程序外部可靠且可移植地看到(假设所述处理程序不否则会像本例中那样导致未定义的行为)。 C11 无锁原子类型也可以。

如前所述,这在技术上是未定义的行为。

发生的事情是 SIGFPE 重复出现。如果从信号处理程序添加打印语句,您会看到:

#include <signal.h>

volatile sig_atomic_t x = 0;

void sigfpe_handler(int signum) {
    x = 5;
    write(2, "Received SIGFPE\n", sizeof "Received SIGFPE\n" - 1 );
}

int main() {
    signal(SIGFPE, sigfpe_handler);
    x = 10 / x;
    return 0;
}

这是因为当信号发生时,控制被转移到信号处理程序,一旦它返回,"main"代码从指令恢复执行(不是语句) 执行被中断(通过信号)。在这种情况下,它会继续进行除法,并且会出现 SIGFPE 并继续循环。

你可以看到确切的指令,它从 gdb 重复:

Program received signal SIGFPE, Arithmetic exception.
0x00000000004005a6 in main ()
(gdb) disass
Dump of assembler code for function main:
   0x000000000040057c <+0>:     push   %rbp
   0x000000000040057d <+1>:     mov    %rsp,%rbp
   0x0000000000400580 <+4>:     sub    [=11=]x10,%rsp
   0x0000000000400584 <+8>:     mov    [=11=]x40054c,%esi
   0x0000000000400589 <+13>:    mov    [=11=]x8,%edi
   0x000000000040058e <+18>:    callq  0x400430 <signal@plt>
   0x0000000000400593 <+23>:    mov    0x20042b(%rip),%edx        # 0x6009c4 <x>
   0x0000000000400599 <+29>:    mov    %edx,-0x4(%rbp)
   0x000000000040059c <+32>:    mov    [=11=]xa,%eax
   0x00000000004005a1 <+37>:    mov    %eax,%edx
   0x00000000004005a3 <+39>:    sar    [=11=]x1f,%edx
=> 0x00000000004005a6 <+42>:    idivl  -0x4(%rbp)
   0x00000000004005a9 <+45>:    mov    %eax,0x200415(%rip)        # 0x6009c4 <x>
   0x00000000004005af <+51>:    mov    [=11=]x0,%eax

(gdb) p $rbp
 = (void *) 0x7fffffffada0
(gdb) p *(int*)$rbp
 = 0

如果您注意到,x 的值已经加载到寄存器中,这就是它所使用的。这就是为什么对 x 的更改没有达到您预期的效果。

您可以执行 "jump" 然后跳过导致代码的 FPE:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <setjmp.h>

sigjmp_buf jbuf;

volatile sig_atomic_t x = 0;

void sigfpe_handler(int signum) {
    x = 5;
    write(2, "Received SIGFPE\n", sizeof "Received SIGFPE\n" - 1 );
    siglongjmp(jbuf, 1);
}

int main() {
    signal(SIGFPE, sigfpe_handler);
    if (sigsetjmp(jbuf, 0) == 0)
        x = 10 / x;
    else
        printf("Returned from siglongjmp\n");
    return 0;
}

请阅读有关 sigsetjmp/siglongjmp 的手册以了解注意事项。