Search code examples
clinuxbsdsignal-handling

Why signal handling is malfunctioning?


I have a signal handling snippet but it is somehow malfunctioning on my Mac and virtual Linux box at koding.com but on my office Linux PC it is working..Can someone please tell me why..

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

void my_isr(int n){
   printf("Hello World");
   signal(SIGINT, SIG_DFL);

}


int main(){
   signal(SIGINT, my_isr);
   printf("pid = %d\n", getpid());
   while(1);

   return 0;
 }

When I am pressing Ctrl+C it is not printing Hello World on the first time but it is re-modifying the SIGINT signal action & hence it is exiting the program when I press Ctrl+C second time. Can someone explain me why?


Solution

  • You are not allowed to call every function in a signal handler.

    Read signal(7). Only async signal safe functions can be called (directly or indirectly) from a signal handler, and printf is not such a function. If you really want to reliably "print" something from inside a signal handler (which I don't recommend), you can only use the low-level write(2) syscall (it is async signal safe).

    So you've got undefined behavior. This explains why it is so bad.

    The recommended way is to set a volatile sigatomic_t flag in your signal handler, and to test it outside of it (e.g. in your while loop...). And you forgot to call fflush(3). You might be more lucky by ending your printf format string with \n since stdout is line-buffered!

    Of course, changing your printf inside your signal handler is still UB, even with a \n, but very often it would appear to work.

    Here is a conforming version of your program....

    #include <signal.h>
    #include <unistd.h>
    #include <stdio.h>
    
    volatile sig_atomic_t got_signal;
    
    void my_sigint_handler (int signum) {
      if (signum == SIGINT) // this is always true!
        got_signal = 1;
    #define INTERRUPT_MESSAGE "Interrupted!\n"
      write(STDOUT_FILENO, INTERRUPT_MESSAGE, strlen(INTERRUPT_MESSAGE));
    };
    
    int main(int argc, char**argv) {
      struct sigaction act_int;
      memset (&act_int, 0, sizeof(act_int));
      act_int.sa_handler = my_sigint_handler;
      if (sigaction(SIGINT, &act_int, NULL)) {
         perror("sigaction"); exit(EXIT_FAILURE);
      };
      printf ("start %s pid %d\n", argv[0], (int)getpid());
      while (!got_signal) {
      };
      printf ("ended %s after signal\n", argv[0]);
      return 0;
    }
    

    A useful (and permissible) trick could be to write(2) a single byte -inside your signal handler- on a pipe(7) to self (you set up that pipe using pipe(2) early at program initialization), and in your event loop poll(2) the read end of that pipe.