Can exit() fail to terminate process?

2019-02-09 03:58发布

问题:

I have a registered a signal handler in my program. Upon receiving an undesired signal (SIGABRT), i call 'exit(-1)' in signal handler to exit the process. But as noticed on few ocassions, it calls exit() but fails to terminate the process.

The issue was randomly generated and I strongly suspect on execution of exit().

Can there be any reasons or cases in which the exit() can fail to terminate the process.

Thanks.

回答1:

Are you calling exit() from the signal handler?

In man 7 signal, section Async-signal-safe functions you can see all the functions that are guaranteed to work when called from an signal handler:

A signal handler function must be very careful, since processing elsewhere may be interrupted at some arbitrary point in the execution of the program. POSIX has the concept of "safe function". If a signal interrupts the execution of an unsafe function, and handler calls an unsafe function, then the behavior of the program is undefined.

POSIX.1-2004 (also known as POSIX.1-2001 Technical Corrigendum 2) requires an implementation to guarantee that the following functions can be safely called inside a signal handler:

There you can see functions _Exit(), _exit() and abort(), but notably not exit(). So you should not call it from a signal handler.

The nasty thing is that even if you call an unsafe function from a signal handler (printf() any?) it will just work most of the time... but not always.



回答2:

Yes, there are some circumstances, such as:

The exit() function shall first call all functions registered by atexit(), in the reverse order of their registration, except that a function is called after any previously registered functions that had already been called at the time it was registered. Each function is called as many times as it was registered. If, during the call to any such function, a call to the longjmp() function is made that would terminate the call to the registered function, the behavior is undefined.

If a function registered by a call to atexit() fails to return, the remaining registered functions shall not be called and the rest of the exit() processing shall not be completed. If exit() is called more than once, the behavior is undefined.

See the POSIX page on exit.

For more information, attach a debugger when you reach the situation and take a look at the call stack.



回答3:

I had analogous problem to the one described by Madar. I needed to perform an action for every signal and quit properly. I wondered through a couple of answers to similar issues and came up with the following explanation/solution.

Explanation: One issue is that exit() should not be used in signal handlers because it is not one of the async-signal-safe functions (see man signal-safety). This is to say that it may but is not guaranteed to work in signal handlers. As a result you would need to call _exit()/_Exit() (which are async-signal-safe). These however terminate the process instantly, without calling the atexit callbacks and static destructors. My understanding is that for some signals a bit more cleaning can be done than what those functions provide.

Solution: The solution I came up with is to register your signal handler for all signals and do any extra steps. Then you can reset to the default handler and call raise(signal_number), which is async-signal-safe, to re-send the singal and so execute the default handler.

Here is a working example that runs default handler only on SIGINT. I think this is too simple to experience the "failing" exit() if you used it in the handler. I tested similar code with an alternative stack to also handle SIGSEGV.

Note If you want this to work properly in multi-threaded context (e.g. multiple threads causing SIGSEGV at the same time) you need to take some care about synchronization. Threads share the same handler but have separate signal masking.

#include <csignal>
#include <cstdlib>
#include <cstring>

#include <vector>

#include <unistd.h>

// The actual signal handler
extern "C" void handleSignal(int sig, siginfo_t *siginfo, void *) {
  // Cannot use printf() - not async-signal-safe 
  // For simplicity I use a single call to write here
  // though it is not guaranteed to write the whole message
  // You need to wrap it in a loop

  // Die only on Ctrl+C
  if(sig == SIGINT) {
    const char *msg = "Die\n";
    write(STDERR_FILENO, msg, ::strlen(msg));
    // Reset to use the default handler to do proper clean-up
    // If you want to call the default handler for every singal
    // You can avoid the call below by adding SA_RESETHAND to sa_flags
    signal(sig, SIG_DFL);
    raise(sig);
    return;
  }

  // Here we want to handle the signal ourselves
  // We have all the info available
  const char *msg = "Continue\n";
  write(STDERR_FILENO, msg, ::strlen(msg));
}

int main() {
  // You might want to setup your own alternative stack
  // eg. to handle SIGSEGV correctly
  // sigaltstack() + SA_ONSTACK flag in sa_flag

  // Prepare a signal action for handling any signal
  struct sigaction signal_action;

  signal_action.sa_sigaction = ::handleSignal;
  signal_action.sa_flags = SA_SIGINFO;
  ::sigfillset(&signal_action.sa_mask);
  // A vector of all signals that lead to process termination by default
  // (see man -s 7 signal)
  const int TERM_SIGNALS[] = {
        SIGHUP,  SIGINT,  SIGQUIT, SIGILL,    SIGABRT, SIGFPE, SIGSEGV,
        SIGPIPE, SIGALRM, SIGTERM, SIGUSR1,   SIGUSR2, SIGBUS, SIGPOLL,
        SIGPROF, SIGSYS,  SIGTRAP, SIGVTALRM, SIGXCPU, SIGXFSZ};

  // Register the signal event handler for every terminating signal
  for (auto sig : TERM_SIGNALS) {
    ::sigaction(sig, &signal_action, 0);
  }

  while(true);

  return 0;
}