Topic : Unix Signals Programming
Author : LUPG
Page : << Previous 2  Next >>
Go to page :


signal handler to 'catch_int' */
signal(SIGINT, catch_int);

/* now, lets get into an infinite loop of doing nothing. */
for ( ;; )
    pause();






The complete source code for this program is found in the catch-ctrl-c.c file.

Notes:

the pause() system call causes the process to halt execution, until a signal is received. it is surely better then a 'busy wait' infinite loop.
the name of a function in C/C++ is actually a pointer to the function, so when you're asked to supply a pointer to a function, you may simply specify its name instead.
On some systems (such as Linux), when a signal handler is called, the system automatically resets the signal handler for that signal to the default handler. Thus, we re-assign the signal handler immediately when entering the handler function. Otherwise, the next time this signal is received, the process will exit (default behavior for INT signals). Even on systems that do not behave in this way, it still won't hurt, so adding this line always is a good idea.




Pre-defined Signal Handlers
For our convenience, there are two pre-defined signal handler functions that we can use, instead of writing our own: SIG_IGN and SIG_DFL.

SIG_IGN:
Causes the process to ignore the specified signal. For example, in order to ignore Ctrl-C completely (useful for programs that must NOT be interrupted in the middle, or in critical sections), write this:

signal(SIGINT, SIG_IGN);

SIG_DFL:
Causes the system to set the default signal handler for the given signal (i.e. the same handler the system would have assigned for the signal when the process started running):

signal(SIGTSTP, SIG_DFL);




Avoiding Signal Races - Masking Signals
One of the nasty problems that might occur when handling a signal, is the occurrence of a second signal while the signal handler function executes. Such a signal might be of a different type then the one being handled, or even of the same type. Thus, we should take some precautions inside the signal handler function, to avoid races.

Luckily, the system also contains some features that will allow us to block signals from being processed. These can be used in two 'contexts' - a global context which affects all signal handlers, or a per-signal type context - that only affects the signal handler for a specific signal type.




Masking signals with sigprocmask()
the (modern) "POSIX" function used to mask signals in the global context, is the sigprocmask() system call. It allows us to specify a set of signals to block, and returns the list of signals that were previously blocked. This is useful when we'll want to restore the previous masking state once we're done with our critical section. sigprocmask() accepts 3 parameters:

int how
defines if we want to add signals to the current mask (SIG_BLOCK), remove them from the current mask (SIG_UNBLOCK), or completely replace the current mask with the new mask (SIG_SETMASK).
const sigset_t *set
The set of signals to be blocked, or to be added to the current mask, or removed from the current mask (depending on the 'how' parameter).
sigset_t *oldset
If this parameter is not NULL, then it'll contain the previous mask. We can later use this set to restore the situation back to how it was before we called sigprocmask().

Note: Older systems do not support the sigprocmask() system call. Instead, one should use the sigmask() and sigsetmask() system calls. If you have such an operating system handy, please read the manual pages for these system calls. They are simpler to use then sigprocmask, so it shouldn't be too hard understanding them once you've read this section.

You probably wonder what are these sigset_t variables, and how they are manipulated. Well, i wondered too, so i went to the manual page of sigsetops, and found the answer. There are several functions to handle these sets. Lets learn them using some example code:




/* define a new mask set */
sigset_t mask_set;
/* first clear the set (i.e. make it contain no signal numbers) */
sigemptyset(&mask_set);
/* lets add the TSTP and INT signals to our mask set */
sigaddset(&mask_set, SIGTSTP);
sigaddset(&mask_set, SIGINT);
/* and just for fun, lets remove the TSTP signal from the set. */
sigdelset(&mask_set, SIGTSTP);
/* finally, lets check if the INT signal is defined in our set */
if (sigismember(&mask_set, SIGINT)
    printf("signal INT is in our set\n");
else
    printf("signal INT is not in our set - how strange...\n");
/* finally, lets make the set contain ALL signals available on our system */
sigfillset(&mask_set)






Now that we know all these little secrets, lets see a short code example that counts the number of Ctrl-C signals a user has hit, and on the 5th time (note - this number was "Stolen" from some quite famous Unix program) asks the user if they really want to exit. Further more, if the user hits Ctrl-Z, the number of Ctrl-C presses is printed on the screen.





/* first, define the Ctrl-C counter, initialize it with zero. */
int ctrl_c_count = 0;
#define CTRL_C_THRESHOLD 5

/* the Ctrl-C signal handler */
void catch_int(int sig_num)
{
    sigset_t mask_set; /* used to set a signal masking set. */
    sigset_t old_set; /* used to store the old mask set.   */

    /* re-set the signal handler again to catch_int, for next time */
    signal(SIGINT, catch_int);
    /* mask any further signals while we're inside the handler. */
    sigfillset(&mask_set);
    sigprocmask(SIG_SETMASK, &mask_set, &old_set);
    
    /* increase count, and check if threshold was reached */
    ctrl_c_count++;
    if (ctrl_c_count >= CTRL_C_THRESHOLD) {
 char answer[30];

 /* prompt the user to tell us if to really exit or not */
 printf("\nRealy Exit? [y/N]: ");
 fflush(stdout);
 gets(answer);
 if (answer[0] == 'y' || answer[0] == 'Y') {
     printf("\nExiting...\n");
     fflush(stdout);
     exit(0);
 }
 else {
     printf("\nContinuing\n");
     fflush(stdout);
     /* reset Ctrl-C counter */
     ctrl_c_count = 0;
 }
    }
    /* restore the old signal mask */{{/COMMENT_FONT}*/
    sigprocmask(SIG_SETMASK, &old_set, NULL);
}

/* the Ctrl-Z signal handler */
void catch_suspend(int sig_num)
{
    sigset_t mask_set; /* used to set a signal masking set. */
    sigset_t old_set; /* used to store the old mask set.   */

    /* re-set the signal handler again to catch_suspend, for next time */
    signal(SIGTSTP, catch_suspend);
    /* mask any further signals while we're inside the handler. */
    sigfillset(&mask_set);
    sigprocmask(SIG_SETMASK, &mask_set, &old_set);

    /* print the current Ctrl-C counter */
    printf("\n\nSo far, '%d' Ctrl-C presses were counted\n\n", ctrl_c_count);
    fflush(stdout);

    /* restore the old signal mask */
    sigprocmask(SIG_SETMASK, &old_set, NULL);
}

.
.
/* and somewhere inside the main function... */
.
.

/* set the Ctrl-C and Ctrl-Z signal handlers */
signal(SIGINT, catch_int);
signal(SIGTSTP, catch_suspend);
.
.
/* and then the rest of the program */
.
.






The complete source code for this program is found in the count-ctrl-c.c file.

You should note that using sigprocmask() the way we did now does not resolve all possible race conditions. For example, It is possible that after we entered the signal handler, but before we managed to call the sigprocmask() system call, we receive another signal, which WILL be called. Thus, if the user is VERY quick (or the system is very slow), it is possible to get into races. In our current functions, this will probably not disturb the flow, but there might be cases where this kind of race could cause problems.

The way to guarantee no races at all, is to let the system set the signal masking for us before it calls the signal handler. This can be done if we use the sigaction() system call to define both the signal handler function AND the signal mask to be used when the handler is executed. You would probably be able to read the manual page for sigaction() on your own, now that you're familiar with the various concepts of signal handling. On old systems, however, you won't find this system call, but you still might find the sigvec() call, that enables a similar functionality.




Implementing Timers Using Signals
One of the weak aspects of Unix-like operating systems is their lack of proper support for timers. Timers are important to allow one to check timeouts (e.g. wait for user input up to 30 seconds, or exit), check some conditions on a regular basis (e.g. check every 30 seconds that a server we're talking to is still active, or close the connection and notify the user about the problem), and so on. There are various ways to get around the problem for programs that use an "event loop" based on the select() system call (or its new replacement, the poll() system call), but not all programs work that way, and this method is too complex for short and simple programs.

Yet, the

Page : << Previous 2  Next >>