Signals

What good is it using signals?

With signals we mean the "software interrupts" that you also send from the shell using (the build-in function) kill pid. They are used by the user to interrupt or quit a program. The kernel uses them for passing hardware errors and when a process requests so.

Besides that they are useful when all you need is the possibility of sending a couple of predefined messages. They can not be used for passing streams of data.

There is a limit to the number of functions; they are numbered from 0 to 31 and most have already been defined by the OS in the file signal.h header file. Also, on the shell prompt kill -l can be entered for a list.

How to set up signalling

With the function sigaction() you can set up so a called signal handler. The function has the following syntax:

 int sigaction (int signum,  
                const  struct  sigaction  *act,
                struct sigaction *oldact);

Explanation of the parameters:

 int signum

Of course the signal you want to catch. See signal.h for the list.

 const struct sigaction *act

Used to tell which function should be started, with which flags, if the signal signum comes in; can be NULL together with oldact, so you can test whether the system supports the signal.

 struct sigaction *oldact

You can leave this one NULL, but if you pass it, the previously installed action is saved in the struct pointed to by oldact.

The struct sigaction is defined as:

 struct sigaction {
         void (*sa_handler)(int);
         void (*sa_sigaction)(int, siginfo_t *, void *);
         sigset_t sa_mask;
         int sa_flags;
         void (*sa_restorer)(void);
 }
      

The members of this struct:

 void (*sa_handler)(int);

This is a pointer to a function where you handle the signal. The function must have one parameter, the signal number.

Instead of a pointer to a function, you can pass SIG_DFL, then the default action is carried out, for example SIGHUP3 (quit) causes the process to quit. If this parameter is SIG_IGN, then the signal is ignored.

You can pass NULL for this parameter, but then you need to fill in the second.

 void (*sa_sigaction)(int, siginfo_t *, void *);

This is the alternative for the first parameter. Pass a pointer to a function where you handle the signal, but besides of the signal number as the only parameter, you will receive a filled-in siginfo_t struct, which contains more information. The third parameter will be filled with a pointer to type ucontext_t (cast to void *), which contains more information on the current process -- if you need to know more about this parameter, check the libc manual.

You can pass NULL for this parameter, but then you need to fill in the first.

 sigset_t sa_mask;

This mask is used to block other signals, besides the one which will be handled. Fill in 0 for no other blocking.

 int sa_flags;

POSIX.1 defines only SA_NOCLDSTOP, which will result in not sending SIGCHLD when a child process is stopped. But there are lots of other flags which are OS specific; see your manual pages. Fill in 0 for unchanged behaviour.

 void (*sa_restorer)(void);

Example signal receiving program

This is a very simple program, just setting up a handler, raising it and then handling it ourselves.

 #include <stdio.h>
 #include <signal.h>
 #include <unistd.h>
 #include <sys/types.h>
 
 #define MYSIG SIGUSR1
 
 void setup_handler ();
 void my_handler (int signum);
 
 int main (void)
 { 
         setup_handler();
         printf("sigaction setup complete, waiting for signal... \n");
         raise(MYSIG);
         return 0;
 }
 
 void setup_handler()
 {
         struct sigaction new_action;    /* this struct has to be set up with
                                               parameters to call sigaction() */
                                          
         sigset_t block_mask;
 
         /* sigemptyset makes block_mask empty collection of flags */ 
         sigemptyset (&block_mask);  
 
         /* function my_handler should be started when MYSIG is received */
         new_action.sa_handler = my_handler;
 
         /* 
          * with block_mask representing an empty collection of flags, this as-
          * signment will make our handler not block any signals while running 
          */
         new_action.sa_mask = block_mask;
 
         /* don't pass any additional flags */
         new_action.sa_flags = 0;
 
         /* now that all the parameters have been taken care of, set it up */
         sigaction (MYSIG, &new_action, NULL);
 }
 
 void my_handler (int signum)
 {
         printf("You INTERRUPTED me! Signal %d received\n",signum);
 }

The above example can be adapted, shortened and now with a handler which gets more information. The changes are marked.

 #include <stdio.h>
 #include <signal.h>
 #include <unistd.h>
 #include <sys/types.h>
 
 #define MYSIG SIGUSR1
 void setup_handler ();
 void my_handler (int signum, siginfo_t *, void *);  /* Changed */
 
 int main (void)
 { 
         setup_handler();
         printf("sigaction setup complete, waiting for signal... \n");
         raise(MYSIG);
         return 0;
 }
 void setup_handler()
 {
         struct sigaction new_action;                                          
         sigset_t block_mask;
         sigemptyset (&block_mask);  
         new_action.sa_sigaction = my_handler;   /* Changed */
         new_action.sa_mask = block_mask;
         new_action.sa_flags = SA_SIGINFO;       /* Changed */
         sigaction (MYSIG, &new_action, NULL);
 }
 void my_handler (int signum, siginfo_t *siginfo,
     void *context)                              /* Changed */
 {
         printf("You INTERRUPTED me! Signal %d received\n",
            siginfo->si_signo);                  /* Changed */
 }
 

Blocking signals

Often you want to temporarily block signals, for instance when you're reading something from a device. To test this, put the following code in the main() function we discussed, instead of the line where we use raise().

        sigset_t block_sigs;
        sigemptyset(&block_sigs);
        sigaddset(&block_sigs, MYSIG);
        sigprocmask(SIG_BLOCK, &block_sigs, NULL);
        raise(MYSIG);
        sleep(5);
        printf("sleep finished, unblocking\n");
        sigprocmask(SIG_UNBLOCK, &block_sigs, NULL);
        printf("exiting\n");

Although we do the raise() before the sleep(), you will see that running the program will show a sleep before the raise is handled -- we blocked it for the time of the sleep.

Monitoring directories

From version 2.4, it's possible on Linux to receive a signal whenever a directory is modified. The following example code does this:

  struct sigaction new_action;
  int    data_dir;
  // Create file descriptor
  data_dir = open("/tmp/newdata", O_RDONLY);
  if(data_dir < 0) {
    exit(1);
  }
  // Handler for notification. We assume there's a function defined that's
  // called "new_data_handler()".
  new_action.sa_handler = new_data_handler;
  sigemptyset(&new_action.sa_mask);
  new_action.sa_flags = 0;
  sigaction(SIGRTMIN, &new_action, NULL);
  // Configure F_NOTIFY. Passing SIGRTMIN will cause it to queue events.
  fcntl(data_dir, F_SETSIG, SIGRTMIN);
  // Now tell which events should be signalled.
  fcntl(data_dir, F_NOTIFY, DN_CREATE|DN_DELETE|DN_MULTISHOT);

You can check the man page of fcntl() to see what the flags do exactly

Sidenote: System V signals

It's also possible to use SRV4 signal() calls. This method is not POSIX compliant and it's also unreliable (check out The Linux Programmer's Guide for more information). So in terms of reliability and portability, the POSIX definition is superior, but there are still many applications around which use the signal() functionality.