TLPI 22.4:处理由硬件产生的信号

代码(为网站上获取)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#define _GNU_SOURCE     /* Get strsignal() declaration from <string.h> */
#include <string.h>
#include <signal.h>
#include <stdbool.h>
#include "tlpi_hdr.h"

/*
    SIGFPE信号处理器函数
*/
static void sigfpeCatcher(int sig)
{
    printf("Caught signal %d (%s)\n", sig, strsignal(sig));
    sleep(1);                   /* Slow down execution of handler */
}

int main(int argc, char *argv[])
{
    /*
        参数有三种选择,
        第一种,无参数,为捕获信号并进入处理器函数
        第二种,-i,为忽略信号
        第三种,-b,为阻塞信号
    */
    if (argc > 1 && strchr(argv[1], 'i') != NULL) { // strchr用于查找第一次出现'i'的位置,并返回以其为首的字符串
        printf("Ignoring SIGFPE\n");
        if (signal(SIGFPE, SIG_IGN) == SIG_ERR)    // i-忽略SIGFPE
             errExit("signal");
    } else {
        printf("Catching SIGFPE\n");

        struct sigaction sa;
        sigemptyset(&sa.sa_mask);   // 不阻塞任何信号
        sa.sa_flags = SA_RESTART;   // 自动重启
        sa.sa_handler = sigfpeCatcher;
        if (sigaction(SIGFPE, &sa, NULL) == -1)
            errExit("sigaction");
    } 
        
    bool blocking = argc > 1 && strchr(argv[1], 'b') != NULL;   // b-阻塞信号
    sigset_t prevMask;
    if (blocking) {
        printf("Blocking SIGFPE\n");

        sigset_t blockSet;
        sigemptyset(&blockSet);
        sigaddset(&blockSet, SIGFPE);
        if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
            errExit("sigprocmask");
    }

    printf("About to generate SIGFPE\n");   // 准备生成SIGFPE
    int x, y;
    y = 0;
    x = 1 / y;  // 除零操作,生成SIGFPE
    y = x;      /* Avoid complaints from "gcc -Wunused-but-set-variable" */

    if (blocking) {
        printf("Sleeping before unblocking\n");
        sleep(2);
        printf("Unblocking SIGFPE\n");
        if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
            errExit("sigprocmask");
    }

    printf("Shouldn't get here!\n");
    exit(EXIT_FAILURE);
}

解释

根据参数,进行后续步骤:

  • 若无参数,则直接为其建立信号处理器函数,内容为打印出相关信息;
  • 若为-i,则为忽略 SIGFPE 信号,使用signal(SIGFPE, SIG_IGN)这行代码来忽略浮点数异常;
  • 若为-b,则为阻塞信号,先将 SIGFPE 放入信号掩码,然后在错误的浮点数运算之后取消阻塞

运行结果

默认情况

默认情况下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Catching SIGFPE
About to generate SIGFPE
Caught signal 8 (Floating point exception)
Caught signal 8 (Floating point exception)
Caught signal 8 (Floating point exception)
Caught signal 8 (Floating point exception)
Caught signal 8 (Floating point exception)
Caught signal 8 (Floating point exception)
Caught signal 8 (Floating point exception)
^\[1]    116013 quit       ./DEMO_SIGFPE

在除零操作之后,产生异常,产生了 SIGFPE 信号,将其捕获,打印信息,并睡眠1s。 运行结果不断出现信息的原因是,在退出信号处理器函数之后,程序又回到终端出重新运行,在本程序中,将会不断地重新进行除零操作,从而不断产生异常。

忽略信号

在这种情况下:

1
2
3
Ignoring SIGFPE
About to generate SIGFPE
[1]    113466 floating point exception  ./DEMO_SIGFPE -i

结果表明,并没有成功忽略该信号,这也验证了 TLPI 中的描述:

当由于硬件异常而产生上述信号之一时,Linux 会强制传递 信号,即使程序已经请求忽略此类信号。

阻塞信号

在这种情况下:

1
2
3
Blocking SIGFPE
About to generate SIGFPE
[1]    116212 floating point exception  ./DEMO_SIGFPE -b

同样地,也并没有阻塞该信号。

始于 Linux 2.6,如果信号遭到阻塞,那么该信号总是会立刻杀死进程,即使进程已经为此信号安装了处理器函数。