简体   繁体   中英

Why the program not terminated on signal value change?

I have a simple program using signal with the user's handlers.

#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");
}

While I expect it to print:

3
2
1
Goodbye

It actually prints:

3
2
1
# Infinity loop within catcher2

My questions are:

  1. On running a user handler like catcher1 , to which point the code returns after the handler's execution? I would expect it continue the execution but it re-runs the signal handler.
  2. What causes the infinity loop?
  3. How to fix it?
  4. Why sending SIGTERM won't print "Goodbye"? ( kill -s TERM <pid> )

As pointed out by AProgrammer, the program doesn't necessarily read x after returning from the handler, even if x is marked volatile (which it should be anyway). This is because the execution continues to the offending instruction. The read from memory and the actual division could be separate instructions.

To get around this you will have to continue the execution to a point before x was read from memory. You can modify your program as follows -

#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");
}

Rest of the functions can remain the same. You should also not be using printf from the signal handler. Instead use write directly to print debug messages as -

write(1, "SIGNAL\n", sizeof("SIGNAL\n"));

The handling of signals is complex and full of implementation defined, unspecified and undefined behavior. If you want to be portable, there is in fact very few things that you can do. Mostly reading and writing volatile sig_atomic_t and calling _Exit . Depending on the signal number, it is often undefined if you leave the signal handler in another way than calling _Exit .

In your case, I think FPE is one of those signals for which leaving normally the signal handler is UB. The best I can see is restarting the machine instruction which triggered the signal. Few architectures, and last I looked x86 was not one of them, provide a way to do 10/x without loading x in a register; that means that restarting the instruction will always restart the signal, even if you modify x and x us a volatile sig_atomtic_t .

Usually longjmp is also able to leave signal handler. @Bodo confirmed that using setjmp and longjmp to restart the division, you can get the behavior you want.


Note: on Unix there is another set of functions, sigaction , siglongjump and others, which is better to use. In fact I don't recommend using something else in any serious program.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM