Showing posts with label signal. Show all posts
Showing posts with label signal. Show all posts

2010-08-24

Thread-Specific Data and Signal Handling in Multi-Threaded Applications

Here are the answers to questions about signal handling and taking care of global data when writing multi-threaded programs.



Perhaps the two most common questions I'm asked about multi-threaded programming (after “what is multi-threaded programming?” and “why would you want to do it?”) concern how to handle signals, and how to handle cases where two concurrent threads use a common function that makes use of global data, and yet the two threads need thread-specific data from that function. By definition, global data includes static local variables which are in truth a kind of global variable. In this article I'll explain how these questions can be dealt with in C programs using one of the POSIX (or almost POSIX) multi-threading packages available for Linux. I live in hope of the day when the most common question I'm asked about multi-threaded programming is, “Can we give you lots of money to write this simple multi-threaded application, please?” Hey—I can dream, can't I?
All the examples in this article make use of POSIX compliant functionality. To the best of my knowledge at the time I write this, there are no fully POSIX-compliant multi-threading libraries available for Linux. Which of the available libraries is best is something of a subjective issue. I use Xavier Leroy's LinuxThreads package, and the code fragments and examples were tested using version 0.5 of this library. This package can be obtained from http://pauillac.inria.fr/~xleroy/linuxthreads. Christopher Provenzano has a good user-level library, although the signal handling doesn't yet match the spec, and there were still a number of serious bugs the last time I used it. (These bugs, I believe, are being worked on.) Other library implementations are also available. Information on these and other packages can be found in the comp.programming.threads newsgroup and (to give a less than exhaustive list):
  • http://www.mit.edu:8001/people/proven/pthreads.html
  • http://www.aa.net/~mtp/PCthreads.html
  • ftp://ftp.cs.fsu.edu/pub/PART/PTHREADS
Thread-specific data
As I implied above, I use the term “global data” for any data which persists beyond normal scoping rules, such as static local variables. Given a piece of code like:
void foo(void)
{
        static int i = 1;
        printf( "%d\n", i );
        i = 2;
}
the first call to this function will print the value 1, and all subsequent calls will print the value 2, because the variable i and its value persist from one invocation of the function to the next, rather than disappearing in a puff of smoke as a “normal” local variable would. This, at least as far as POSIX threads are concerned, is global data.
It is commonly said (I've said it myself) that using global data is a bad practice. Whether or not this is true, it is only a rule of thumb. Certainly there are situations where using global data can avoid creating artificial circumstances. The previous article (Linux Journal Issue 34) explained how threads can share global data with careful use of mutual exclusion (mutex) functions to prevent one thread from accessing an item of global data while another thread is changing its value. In this article I will look at a different type of problem, using a real example from a recent project of mine.
Consider the case of a virtual reality system where a client makes several network socket connections to a server. Different types and priorities of data go down different sockets. High priority data, such as information about objects immediately in the field of view of the client, is sent down one socket. Lower priority data such as texture information, background sounds, or information about objects which are out of the current field of view, is sent down another socket to be processed whenever the client has available time. The server could create a collection of new threads every time a new client connects to the server, designating one thread for each of the sockets to be used to talk to each of the clients. Every one of these threads could use the same function to send a lump of data (not a technical term) to the client. The data to be sent details of the client it is to be sent to, the priority and type of data to be sent could all be held in global variables, and yet each thread will make use of different values. So how do we do it?
As a trivial example, suppose the only global data which our lump-sending function needs to use is an integer that indicates the priority of the data. In a non-threaded version, we might have a global integer called priority used as in Listing 1.


/* Code Example 1 */

/* a bit of global data */
int priority = 1;

void prepare_data( ... )
{
        ...
        priority = 1;
        ...
        lump_send( some_data );
        ...
}

void lump_send( data_t some_data )
{
        switch( priority )
        {
        case 1:  /* do one thing */
                break;
        case 2: /* do something else */
                break;
        }
}

In the multi-threaded version we don't have a global integer, instead we have a global key to the integer. It is through the key that the data can be accessed by means of a number of functions:
  1. pthread_key_create() to prepare the key for use
  2. pthread_setspecific() to set a value to thread-specific data
  3. pthread_getspecific() to retrieve the current value
pthread_key_create() is called once, generally before any of the threads which are going to use the key have been created. pthread_getspecific() and pthread_setspecific() never return an error if the key that is used as an argument has not been created. The result of using them on a key which has not been created is undefined. Something will happen, but it could vary from system to system, and it can't be caught simply by using good error handling. This is an excellent source of bugs for the unwary. So our multi-threaded version might look like Listing 2.


/* Code Example 2 */

#include <pthread.h>

/* most threads that this program will create */
#define MAX_NUMBER_OF_THREADS ...

/* function prototypes */
void* client_thread( void* );
void prepare_data( void );
void lump_send( data_t );

/* global key to the thread specific data */
pthread_key_t priority_key;

int main( void )
{
        int n;

        pthread_t
           thread_id[MAX_NUMBER_OF_THREADS];
        ...
        /* create the thread specific data key
         * before creating the threads */
        pthread_key_create( &priority_key, NULL );
        ...
        /* create thread that will use the key */
        pthread_create( &thread_id[n], NULL,
            client_thread, NULL );
        ...
}

void* client_thread( void* arg )
{
        ...
        prepare_data();
        ...
}

void prepare_data( void )
{
        data_t some_data;
        ...
        /* store the value 1.  This value is
         * globally available, but only to this
         * thread */
        pthread_setspecific( priority_key,
            (void*)1 );
        ...
        lump_send( some_data );
        ...
}

void lump_send( data_t some_data )
{
        /* get this thread's global data from
         * priority_key */
        switch( (int)pthread_getspecific(
             priority_key ))
        {
        case 1:  /* do one thing */
                break;
        case 2: /* do something else */
                break;
        }
}


There are a few things to note here:
  1. The implementation of POSIX threads can limit the number of keys a process may use. The standard states that this number must be at least 128. The number available in any implementation can be found by looking at the macro PTHREAD_KEYS_MAX. According to this macro, LinuxThreads currently allows 128 keys.
  2. The function pthread_key_delete() can be used to dispose of keys that are no longer needed. Keys, like all “normal” data items, vanish when the process exits, so why bother deleting them? Think of key handling as being similar to file handling. An unsophisticated program need not close any files that it has opened, as they will be automatically closed when the program exits. But since there is a limit to the number of files a program can have open at one time, the best policy is to close files not currently being used so that the limit is not exceeded. This policy also works well for key handling, as you may be limited in the number of thread-specific data keys a process may have.
  3. pthread_getspecific() and pthread_setspecific() access thread-specific data as void*pointers. This ability can be used directly (as in Listing 2), if the data item to be accessed can be cast as type void*, e.g., an int in most, but not necessarily all, implementations. However, if you want your code to be portable or if you need to access larger data objects, then each thread must allocate sufficient memory for the data object, and store the pointer to the object in the thread-specific data rather than storing the data itself.
  4. If you allocate some memory (using the standard function malloc(), for instance) for your thread-specific data, and the thread exits at some point, what happens to the allocated memory? Nothing happens, so it leaks, and this is bad. This is the situation where the extra parameter in the pthread_key_create() function comes into use. This parameter allows you to specify a function to call when a thread exits, and you use that function to free up any memory that has been allocated. To prevent a waste of CPU time, this destructor function is called only in the case where a thread has made use of that particular key. There's little point in tidying up for a thread that has nothing to be tidied. When a thread exits because it called one of the functions exit(), _exit() or abort(), the destructor function is not called. Also, note that pthread_key_delete() does not cause any destructors to be called, that using a key that has been deleted doesn't have a defined behavior, and that pthread_getspecific() and pthread_setspecific() don't return any error indications. Tidy up your keys carefully. One day you'll be glad you did. So a better version of our code is Listing 3.

/* Code Example 3 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUMBER_OF_KEYS_WE_USE ...
#define MAX_NUMBER_OF_THREADS ...

/* global key to the thread specific data */
pthread_key_t priority_key;

/* function prototypes */
void* client_thread( void* );
void prepare_data( void );
void lump_send( data_t );

int main( void )
{
        int n;
        pthread_t
           thread_id[MAX_NUMBER_OF_THREADS];
        ...
        /* check that the implementation can cope
         * with all the keys we need */
        if ( NUMBER_OF_KEYS_WE_USE >
             PTHREAD_KEYS_MAX ) {
                fprintf( stderr,
                "Not enough keys available\n");
                exit( -1 );
        }
        /* create the keys that we need.  We're
         * going to use "malloc()" to grab
         * some memory and point the thread specific
         * data at it. If the thread dies, we'd like
         *  the system to use "free()" to
         *  release that memory for us
         */
        pthread_key_create( &priority_key, free );
        ...
        /* create the threads */
        pthread_create( &thread_id[n], NULL,
            client_thread, NULL );
        ...
}

void* client_thread( void* arg )
{
        /* grab enough memory to store an int, and
         * store a pointer to that memory as thread
         * specific data
         */
        pthread_setspecific( priority_key,
               malloc( sizeof( int ) ) );
        ...
        prepare_data();
        ...
}

void prepare_data( void )
{
        data_t some_data;
        ...
        /* store the priority value in the
         *  memory pointed to by the thread
         *  specific data
         */

        *((int*)pthread_getspecific(
        priority_key )) = 1;
        ...
        lump_send( some_data );
        ...
}

void lump_send( data_t some_data )
{
        /* act on the value stored in the memory
         *  pointed to by the thread specific data
         */
        switch( *((int*)pthread_getspecific(
             priority_key )) )
        {
        case 1:  /* do one thing */
                break;
        case 2: /* do something else */
                break;
        }
}


Some of this code might look a little strange at first sight. Using pthread_getspecific() to store a thread specific value? The idea is to get the memory location this thread is to use, and then the thread specific value is stored there.
Even if global data is anathema to you, you might still have good use for thread-specific data. In particular, you might need to write a multi-threaded version of some existing library code that is also going to be used in a non-threaded program. A good simple example is making a version of the standard C libraries fit for use by multi-threaded programs. That friend of all C programmers, errno, is a global variable that is commonly set by library functions to indicate what went wrong during a function call. If two threads call functions which both set errno to different values, at least one of the threads is going to get the wrong information. This is solved by having thread-specific data areas for errno, rather than one global variable used by all threads.



Signal Handling
Many people find signal handling in C to be a bit tricky at the best of times. Multi-threaded applications need a little extra care when it comes to signal handling, but once you've written two programs, you'll wonder what all the fuss was about—trust me. And if you start to panic, remember—deep, slow breaths.
A quick re-cap of what signals are. Signals are the system's way of informing a process about various events. There are two types of signals, synchronous and asynchronous.
Synchronous signals are a result of a program action. Two examples are:
  1. SIGFPE, floating-point exception, is returned when the program tries to do some illegal mathematical operation such as dividing by zero.
  2. SIGSEGV, segmentation violation, is returned when the program tries to access an area of memory outside the area it can legally access.
Asynchronous signals are independent of the program. For example, the signal sent when the user gives the kill command.
In non-threaded applications there are three usual ways of handling signals:
  1. Pretend they don't exist, perhaps the most common policy, and quite adequate for lots of simple programs—at least until you want your program to be reliable and useful.
  2. Use signal() to set up a signal handler—nice and simple, but not very robust.
  3. Use the POSIX signal handling functions such as sigaction() and sigprocmask() to set up a signal handler or to ignore certain signals—the “proper” method.
If you choose the first option, then signals will have some default behavior. Typically, this default behavior will cause the program to exit or cause the program to ignore the signal, depending on what the signal is. The latter two options allow you to change the default behavior for each signal type—ignore the signal, cause the program to exit or invoke a signal-handling function to allow your program to perform some special processing. Avoid the use of the old-style signal() function. Whether you're writing threaded or non-threaded applications, the extra complications of the POSIX-style functions are worth the effort. Note that the behavior of sigprocmask(), which sets a signal mask for a process, is undefined in a multi-threaded program. There is a new function, pthread_sigmask(), that is used in much the same way as sigprocmask(), but it sets the signal mask only for the current thread. Also, a new thread inherits the signal mask of the thread that created it; so a signal mask can effectively be set for an entire process by calling pthread_sigmask() before any threads are created.
In a multi-threaded application, there is always the question of which thread the signal will actually be delivered to. Or does it get delivered to all the threads?
To answer the last question first, no. If one signal is generated, one signal is delivered, so any single signal will only be delivered to a single thread.
So which thread will get the signal? If it is a synchronous signal, the signal is delivered to the thread that generated it. Synchronous signals are commonly managed by having an appropriate signal handler set up in each thread to handle any that aren't masked. If it is an asynchronous signal, it could go to any of the threads that haven't masked out that signal usingsigprocmask(). This makes life even more complicated. For instance, suppose your signal handler must access a global variable. This is normally handled quite happily by using mutex, as follows:
void signal_handler( int sig )
{
        ...
        pthread_mutex_lock( &mutex1 );
        ...
        pthread_mutex_unlock( &mutex1 );
        ...
}
Looks fine at first sight. However, what if the thread that was interrupted by the signal had just itself locked mutex1? The signal_handler() function will block, and will wait for the mutex to be unlocked. And the thread that is currently holding the mutex will not restart, and so will not be able to release the mutex until the signal handler exits. A nice deadly embrace.
So a common way of handling asynchronous signals in a multi-threaded program is to mask signals in all the threads, and then create a separate thread (or threads) whose sole purpose is to catch signals and handle them. The signal-handler thread catches signals by calling the functionsigwait() with details of the signals it wishes to wait for. To give a simple example of how this might be done, take a look at Listing 4.


/* Code Example 4 */

#include <pthread.h>
#include <signal.h>

void* sig_handler( void* );

/* global variable used to indicate what signal
 * (if any) has been caught
 */
int handled_signal = -1;

/* mutex to be used whenever accessing the above
 * global data */
pthread_mutex_t sig_mutex = PTHREAD_MUTEX_INITIALIZER;

int main(void )
{
        sigset_t signal_set;
        pthread_t sig_thread;

        /* block all signals */
        sigfillset( &signal_set );
        pthread_sigmask( SIG_BLOCK, &signal_set,
                NULL );

        /* create the signal handling thread */
        pthread_create( &sig_thread, NULL,
                sig_handler, NULL );

        for (;;) {
            /* whatever you want your program to
             * do... */

                /* grab the mutex before looking
                 * at handled_signal */
                pthread_mutex_lock( &sig_mutex );

                /* look to see if any signals have
                 * been caught */
                switch ( handled_signal )
                {
                case -1:
                  /* no signal has been caught
                   * by the signal handler */
                  break;

                case 0:
                printf("The signal handler caught"
                " a signal I'm not interested in "
                "(%d)\n",
                 handled_signal );
                 handled_signal = -1;
                 break;

                case SIGQUIT:
                printf("The signal handler caught"
                " a SIGQUIT signal!\n" );
                 handled_signal = -1;
                 break;

                case SIGINT:
                printf(
                "The signal handler caught"
                " a SIGINT signal!\n" );
                 handled_signal = -1;
                 break;
                }
                /* remember to release mutex */
                pthread_mutex_unlock(&sig_mutex);
        }
}

void* sig_handler( void* arg )
{
        sigset_t signal_set;
        int sig;

        for(;;) {
                /* wait for any and all signals */
                sigfillset( &signal_set );
                sigwait( &signal_set, &sig );

                /* when we get this far, we've
                 * caught a signal */

                switch( sig )
                {
                /* whatever you need to do on
                 * SIGQUIT */
                case SIGQUIT:
                  pthread_mutex_lock(&sig_mutex);
                  handled_signal = SIGQUIT;
                  pthread_mutex_unlock(&sig_mutex);
                  break;

                /* whatever you need to do on
                 * SIGINT */
                 case SIGINT:
                  pthread_mutex_lock(&sig_mutex);
                  handled_signal = SIGINT;
                  pthread_mutex_unlock(&sig_mutex);
                  break;

                /* whatever you need to do for
                 * other signals */
                default:
                  pthread_mutex_lock(&sig_mutex);
                  handled_signal = 0;
                  pthread_mutex_unlock(&sig_mutex);
                  break;
                }
        }
        return (void*)0;
}


As mentioned earlier, a thread inherits its signal mask from the thread which creates it. Themain() function sets the signal mask to block all signals, so all threads created after this point will have all signals blocked, including the signal-handling thread. Strange as it may seem at first sight, this is exactly what we want. The signal-handling thread expects signal information to be provided by the sigwait() function, not directly by the operating system. sigwait() will unmask the set of signals that are given to it, and then will block until one of those signals occurs.
Also, you might think that this program will deadlock, if a signal is raised while the main thread holds the mutex sig_mutex. After all, the signal handler tries to grab that same mutex, and it will block until that mutex comes free. However, the main thread is ignoring signals, so there is nothing to prevent another thread from gaining control while the signal handling thread is blocked. In this case, sig_handler() hasn't caught a signal in the usual, non-threaded sense. Instead it has asked the operating system to tell it when a signal has been raised. The operating system has performed this function, and so the signal handling thread becomes just another running thread.


Differences in Signal Handling between POSIX Threads and LinuxThreads
Listing 4 shows how to deal with signals in a multi-threading environment that handles threads in a POSIX compliant way.
Personally, I like the kernel-level package “LinuxThreads” that makes use of Linux 2.0's clone() system call to create new threads. At some point in the future, the clone() call may implement theCLONE_PID flag which would allow all the threads to share a process ID. Until then each thread created using “LinuxThreads” (or any other packages which chooses to use clone() to create threads) will have its own unique process ID. As such, there is no concept of sending a signal to “the process.” If one thread calls sigwait() and all other threads block signals, only those signals which are specifically sent to the sigwait()-ing thread will be processed. Depending on your application, this could mean that you have no choice other than to include an asynchronous signal handler in each of the threads.
Summary
Thread specific data is easy to use—far easier than many people's first experiences may suggest. In a way, this ease of use is a disadvantage, since very often there are more elegant solutions to a problem. But in times of need, thread specific data is your friend.
On the other hand, signal handling in anger can be a little hairy. Anyone who thinks otherwise has overlooked something—either that or they're far too clever for their own good. Make life easier for yourself by consigning all the handling of asynchronous signals to one thread that sits on sigwait().
Martin McCarthy discovered multi-threaded programming while writing the server for a high-speed, multi-user, distributed, virtual-reality system. Of course, he only took that job so that he could squeeze as many buzzwords into his job description as possible. He can be reached at marty@ehabitat.demon.co.uk.

Linux Signals for the Application Programmer

Signals are a fundamental method for interprocess communication ad are used in everything from network servers to media players. Here's how you can use them in your applications.
A good understanding of signals is important for an application programmer working in the Linux environment. Knowledge of the signaling mechanism and familiarity with signal-related functions help one write programs more efficiently.
An application program executes sequentially if every instruction runs properly. In case of an error or any anomaly during the execution of a program, the kernel can use signals to notify the process. Signals also have been used to communicate and synchronize processes and to simplify interprocess communications (IPCs). Although we now have advanced synchronization tools and many IPC mechanisms, signals play a vital role in Linux for handling exceptions and interrupts. Signals have been used for approximately 30 years without any major modifications.
The first 31 signals are standard signals, some of which date back to 1970s UNIX from Bell Labs. The POSIX (Portable Operating Systems and Interface for UNIX) standard introduced a new class of signals designated as real-time signals, with numbers ranging from 32 to 63.
A signal is generated when an event occurs, and then the kernel passes the event to a receiving process. Sometimes a process can send a signal to other processes. Besides process-to-process signaling, there are many situations when the kernel originates a signal, such as when file size exceeds limits, when an I/O device is ready, when encountering an illegal instruction or when the user sends a terminal interrupt like Ctrl-C or Ctrl-Z.
Every signal has a name starting with SIG and is defined as a positive unique integer number. In a shell prompt, the kill -l command will display all signals with signal number and corresponding signal name. Signal numbers are defined in the /usr/include/bits/signum.h file, and the source file is /usr/src/linux/kernel/signal.c.
A process will receive a signal when it is running in user mode. If the receiving process is running in kernel mode, the execution of the signal will start only after the process returns to user mode.
Signals sent to a non-running process must be saved by the kernel until the process resumes execution. Sleeping processes can be interruptible or uninterruptible. If a process receives a signal when it is in an interruptible sleep state, for example, waiting for terminal I/O, the kernel will awaken the process to handle the signal. If a process receives a signal when it is in uninterruptible sleep, such as waiting for disk I/O, the kernel defers the signal until the event completes.
When a process receives a signal, one of three things could happen. First, the process could ignore the signal. Second, it could catch the signal and execute a special function called a signal handler. Third, it could execute the default action for that signal; for example, the default action for signal 15, SIGTERM, is to terminate the process. Some signals cannot be ignored, and others do not have default actions, so they are ignored by default. See the signal(7) man page for a reference list of signal names, numbers, default actions and whether they can be caught.
When a process executes a signal handler, if some other signal arrives the new signal is blocked until the handler returns. This article explains the fundamentals of the signaling mechanism and elaborates on signal-related functions with syntax and working procedures.
Signals inside the Kernel
Where is the information about a signal stored in the process? The kernel has a fixed-size array of proc structures called the process table. The u or user area of the proc structure maintains control information about a process. The major fields in the u area include signal handlers and related information. The signal handler is an array with each element for each type of signal being defined in the system, indicating the action of the process on the receipt of the signal. The proc structure maintains signal-handling information, such as masks of signals that are ignored, blocked, posted and handled.
Once a signal is generated, the kernel sets a bit in the signal field of the process table entry. If the signal is being ignored, the kernel returns without taking any action. Because the signal field is one bit per signal, multiple occurrences of the same signal are not maintained.
When the signal is delivered, the receiving process should act depending on the signal. The action may be terminating the process, terminating the process after creating a core dump, ignoring the signal, executing the user-defined signal handler (if the signal is caught by the process) or resuming the process if it is temporarily suspended.
The core dump is a file called core, which has an image of the terminated process. It contains the process' variables and stack details at the time of failure. From a core file, the programmer can investigate the reason for termination using a debugger. The word core appears here for a historical reason: main memory used to be made from doughnut-shaped magnets called inductor cores.
Catching a signal means instructing the kernel that if a given signal has occurred, the program's own signal handler should be executed, instead of the default. Two exceptions are SIGKILL and SIGSTOP, which cannot be caught or ignored.
sigset_t is a basic data structure used to store the signals. The structure sent to a process is a sigset_t array of bits, one for each signal type:
typedef struct {
                   unsigned long sig[2];
                }  sigset_t;
Because each unsigned long number consists of 32 bits, the maximum number of signals that may be declared in Linux is 64 (according to POSIX compliance). No signal has the number 0, so the other 31 bits in the first element of sigset_t are the standard first 31 signals, and the bits in the second element are the real-time signal numbers 32-64. The size of sigset_t is 128 bytes.


Handling Signals
There are many system calls and signal-supported library functions, which provide an easy and efficient way of handling the signals in a process. We start with the standard old signal system call, then we discuss some useful functions like sigaction, sigaddset, sigemptyset, sigdelset, sigismember and kill.
The Signal System Call
The signal system call is used to catch, ignore or set the default action of a specified signal. It takes two arguments: a signal number and a pointer to a user-defined signal handler. Two reserved predefined signal handlers are available in Linux: SIG_IGN and SIG_DFL. SIG_IGN will ignore a specified signal, and SIG_DFL will set the signal handler to the default action for that signal (see man 2 signal).
On success, the system call returns the previous value of the signal handler for the specified signal. If the signal call fails, it returns SIG_ERR. Listing 1 explains how to catch, ignore and set the default action of SIGINT. Try pressing Ctrl-C, which sends SIGINT, during each part.
Listing 1. Catching and Ignoring a Signal


Listing 1. Catching and Ignoring a Signal

#include <signal.h>

void my_handler (int sig); /* function prototype */

int main ( void ) {

/* Part I: Catch SIGINT */
    signal (SIGINT, my_handler);
    printf ("Catching SIGINT\n");
    sleep(3);
    printf (" No SIGINT within 3 seconds\n");

/* Part II: Ignore SIGINT */
    signal (SIGINT, SIG_IGN);
    printf ("Ignoring SIGINT\n");
    sleep(3);
    printf ("No SIGINT within 3 seconds\n");

/* Part III: Default action for  SIGINT */
    signal (SIGINT, SIG_DFL);
    printf ("Default action for SIGINT\n");
    sleep(3);
    printf ("No SIGINT within 3 seconds\n");
    return 0;
}

/* User-defined signal handler function */
void my_handler (int sig) {
    printf ("I got SIGINT, number %d\n", sig);
    exit(0);
}
sigaction
The sigaction system call can be used instead of signal because it has lot of control over a given signal. The syntax of sigaction is:
int sigaction ( int signum,
                const struct sigaction *act,
                struct sigaction *oldact);
The first argument, signum, is a specified signal; the second argument, sigaction, is used to set the new action of the signal signum; and the third argument is used to store the previous action, usually NULL.
The sigaction structure is defined as:
struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
}
The members of the sigaction structure are described as follows.
sa_hander: a pointer to a user-defined signal handler or predefined signal handler (SIG_IGN or SIG_DFL).
sa_mask: specifies a mask of signals when the signal is handled. To avoid the blocking of signals, the SA_NODEFER or SA_NOMASK flags can be used.
sa_flags: specifies the action of signal. Sets of flags are available for controlling the signal in a different manner. More than one flag can be used by ORing:
  • SA_NOCLDSTOP: if we specify the SIGCHLD signal, when the child has stopped its execution it does not receive notification.
  • SA_ONESHOT or SA_RESETHAND: restores the default action of the signal after the user-defined signal handler is executed. To avoid setting the default action, SA_RESTART can be used.
  • SA_NOMASK or SA_NODEFER prevents masking the signal. SA_SIGINFO is used to receive signal-related information.
sa_sigaction: if the SA_SIGINFO flag is used in sa_flags, instead of specifying the signal handler in sa_handler, sa_sigaction should be used.
sa_sigaction is a pointer to a function that takes three arguments, not one as sa_handler does, for example:
void my_handler (int signo, siginfo_t *info,
                     void *context)
Here, signo is the signal number, and info is a pointer to the structure of type siginfo_t, which specifies the signal-related information; and context is a pointer to an object of type ucontext_t, which refers to the receiving process context that was interrupted with the delivered signal.
Listing 2 is similar to Listing 1 but uses the sigaction system call instead of the signal system call. Listing 3 explains signal-related information using the SIG_INFO flag.
Listing 2. Same as Listing 1, but with Sigaction


Listing 2. Same as Listing 1, but with sigaction

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

void my_handler (int sig); /* function prototype */

int main ( void ) {

    struct sigaction my_action;

/* Part I: Catch SIGINT */

    my_action.sa_handler = my_handler;
    my_action.sa_flags = SA_RESTART;
    sigaction (SIGINT, &my_action, NULL);
    printf ("Catching SIGINT\n");
    sleep(3);
    printf (" No SIGINT within 3 seconds\n");


/* Part II: Ignore SIGINT */

   my_action.sa_handler = SIG_IGN;
   my_action.sa_flags = SA_RESTART;
   sigaction (SIGINT, &my_action, NULL);
   printf ("Ignoring SIGINT\n");
   sleep(3);
   printf (" Sleep is over\n");


/* Part III: Default action for  SIGINT */

  my_action.sa_handler = SIG_DFL;
  my_action.sa_flags = SA_RESTART;
  sigaction (SIGINT, &my_action, NULL);
  sleep(3);
  printf ("No SIGINT within 3 seconds\n");
}

void my_handler (int sig) {
    printf ("I got SIGINT, number %d\n", sig);
    exit(0);
}



Listing 3. Using SA_SIGINFO and sa_sigaction to Extract Information from a Signal


Listing 3. Using SA_SIGINFO and sa_sigaction to Extract Information from a Signal

#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <bits/siginfo.h>
#include <stdio.h>

void handler (int signo, siginfo_t *info,
              void *context);

main () {

   struct sigaction my_action;

   my_action.sa_flags = SA_SIGINFO;
   my_action.sa_sigaction = handler;

   sigaction(SIGINT, &my_action, NULL);

   printf ("Catching SIGINT\n");
   sleep(5);
   printf ("Done.\n");
}

void handler (int signo, siginfo_t *info,
              void *context)
 {
    printf ("Signal number: %d\n", info->si_signo);

 /* Elements of the siginfo_t structure are listed
    in man 2 sigaction.
 */
}


Sending Signals
Until now, we've been pressing Ctrl-C to send SIGINT from the shell. To do it from a program, use the kill system call, which accepts two arguments, process ID and signal number:
int kill ( pid_t process_id, int signal_number );
If the pid is positive, the signal is sent to a particular process. If the pid is negative, the signal is sent to the process whose group ID matches the absolute value of pid.
As you might expect, the kill command, which exists as a standalone program (/bin/kill) and is also built into bash (try help kill) uses the kill system call to send a signal.
Not all processes can send signals to each other. In order for one process to send a signal to another, either the sender must be running as root, or the sender's real or effective user ID must be the same as the real or saved ID of the receiver. This means your shell, running as you, can signal a setuid program that you started, but that is now running as root, for example:
cp /bin/sleep ~/rootsleep
sudo chmod u+s ~/rootsleep
./rootsleep 40
killall rootsleep
rm ~/rootsleep
A normal user can't send signals to system processes such as swapper and init.
You also can use kill to find out if a process exists. Specify a signal number of 0, and if the process exists, the kill returns zero; if it doesn't, kill returns -1.
Listing 4. Programs to Send and Receive SIGINT


Listing 4. Programs to Send and Receive SIGINT

#include <signal.h>

main ( ) {
    int process_id;
    printf ("Enter process_id which you want "
            "to send a signal : ");
    scanf ("%d", &process_id);

   if (!(kill ( process_id, SIGINT)))
       printf ("SIGINT sent to %d\n", process_id);
   else if (errno == EPERM)
       printf ("Operation not permitted.\n");
   else
       printf ("%d doesn't exist\n", process_id);
}

/* Listing 4a. This program will run until it
   receives SIGINT */

#include <signal.h>

 main ( ) {
   printf (" This process id is %d. "
   "Waiting for SIGINT.\n", getpid());
   for (;;);
}

Listings 4 and 4a explain how to use the kill system call. First, execute the 4a program in one window and get its process ID. Now, run the Listing 4 program in another window and give the 4a example's pid as the input.
This article should help you understand the fundamental concept of a signal and some of its importance. Try the sample programs, and see the man pages for the system calls and the references in Resources for more information.
Resources
email: balasubramanian.thangaraju@wipro.com
Dr B. Thangaraju received a PhD in Physics and worked as a research associate for five years at the Indian Institute of Science, India. He is presently working as a manager at Talent Transformation, Wipro Technologies, India. He has published many research papers in renowned international journals. His current areas of research, study and knowledge dissemination are the Linux kernel, device drivers and real-time Linux.