Each signal may have a signal handler, which is a function that gets called when the process receives that signal. The function is called in "asynchronous mode", meaning that no where in your program you have code that calls this function directly. Instead, when the signal is sent to the process, the operating system stops the execution of the process, and "forces" it to call the signal handler function. When that signal handler function returns, the process continues execution from wherever it happened to be before the signal was received, as if this interruption never occurred.
Note for "hardwarists": If you are familiar with interrupts (you are,
right?), signals are very similar in their behavior. The difference is
that while interrupts are sent to the operating system by the hardware,
signals are sent to the process by the operating system, or by other processes.
Note that signals have nothing to do with software interrupts, which are
still sent by the hardware (the CPU itself, in this case).
kill -<signal> <PID>For example, in order to send the INT signal to process with PID 5342, type:
kill -INT 5342
This has the same affect as pressing Ctrl-C in the shell that runs that
process.
If no signal name or number is specified, the default is to send a
TERM signal to the process, which normally causes its termination,
and hence the name of the kill command.
#include <unistd.h> /* standard unix functions, like getpid() */ #include <sys/types.h> /* various type definitions, like pid_t */ #include <signal.h> /* signal name macros, and the kill() prototype */ /* first, find my own process ID */ pid_t my_pid = getpid(); /* now that i got my PID, send myself the STOP signal. */ kill(my_pid, SIGSTOP);An example of a situation when this code might prove useful, is inside a signal handler that catches the TSTP signal (Ctrl-Z, remember?) in order to do various tasks before actually suspending the process. We will see an example of this later on.
STOP is also a signal that a process cannot catch, and forces the process's suspension immediately. This is useful when debugging programs whose behavior depends on timing. Suppose that process A needs to send some data to process B, and you want to check some system parameters after the message is sent, but before it is received and processed by process B. One way to do that would be to send a STOP signal to process B, thus causing its suspension, and then running process A and waiting until it sends its oh-so important message to process B. Now you can check whatever you want to, and later on you can use the CONT signal to continue process B's execution, which will then receive and process the message sent from process A.
Now, many other signals are catchable, and this includes the famous
SEGV and BUS signals. You probably have seen numerous
occasions when a program has exited with a message such as 'Segmentation
Violation - Core Dumped', or 'Bus Error - core dumped'. In the
first occasion, a SEGV signal was sent to your program due to
accessing an illegal memory address. In the second case, a BUS
signal was sent to your program, due to accessing a memory address with
invalid alignment. In both cases, it is possible to catch these signals
in order to do some cleanup - kill child processes, perhaps remove temporary
files, etc. Although in both cases, the memory used by your process is
most likely corrupt, it's probable that only a small part of it was corrupt,
so cleanup is still usually possible.
#include <stdio.h> /* standard I/O functions */
#include <unistd.h> /* standard unix functions, like getpid() */
#include <sys/types.h> /* various type definitions, like pid_t */
#include <signal.h> /* signal name macros, and the signal() prototype */
/* first, here is the signal handler */
void catch_int(int sig_num)
{
/* re-set the signal handler again to catch_int, for next time */
signal(SIGINT, catch_int);
/* and print the message */
printf("Don't do that");
fflush(stdout);
}
.
.
.
/* and somewhere later in the code.... */
.
.
/* set the INT (Ctrl-C) 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:
signal(SIGINT, SIG_IGN);
signal(SIGTSTP, SIG_DFL);
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.
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.
Yet, the operating system gives us a simple way of setting up timers
that don't require too much hassles, by using special alarm signals. They
are generally limited to one timer active at a time, but that will suffice
in simple cases.
#include <unistd.h> /* standard unix functions, like alarm() */
#include <signal.h> /* signal name macros, and the signal() prototype */
char user[40]; /* buffer to read user name from the user */
/* define an alarm signal handler. */
void catch_alarm(int sig_num)
{
printf("Operation timed out. Exiting...\n\n");
exit(0);
}
.
.
/* and inside the main program... */
.
.
/* set a signal handler for ALRM signals */
signal(SIGALRM, catch_alarm);
/* prompt the user for input */
printf("Username: ");
fflush(stdout);
/* start a 30 seconds alarm */
alarm(30);
/* wait for user input */
gets(user);
/* remove the timer, now that we've got the user's input */
alarm(0);
.
.
/* do something with the received user name */
.
.
The complete source code for this program is found in the use-alarms.c file.
As you can see, we start the timer right before waiting for user input.
If we started it earlier, we'll be giving the user less time then promised
to perform the operation. We also stop the timer right after getting the
user's input, before doing any tests or processing of the input, to avoid
a random timer from setting off due to slow processing of the input. Many
bugs that occur using the alarm() system call occur due to forgetting
to set off the timer at various places when the code gets complicated.
If you need to make some tests during the input phase, put the whole piece
of code in a function, so it'll be easier to make sure that the timer is
always set off after calling the function. Here is an example of how NOT
to do this:
int read_user_name(char user[])
{
int ok_len;
int result = 0; /* assume failure */
/* set an alarm to timeout the input operation */
alarm(3); /* Mistake 1 */
printf("Enter user name: "); /* Mistake 2 */
fflush(stdout);
if (gets(user) == NULL) {
printf("End of input received.\n");
return 0; /* Mistake 3 */
}
/* count the size of prefix of 'user' made only of characters */
/* in the given set. if all characters in 'user' are in the */
/* set, then ok_len with be equal to the length of 'user'. */
ok_len = strspn(user, "abcdefghijklmnopqrstuvwxyz0123456789");
if (ok_len == strlen(user)) {
/* check if the user exists in our database */
result = find_user_in_database(user); /* Mistake 4 */
}
alarm(0);
return result;
}
Lets count the mistakes and bad programming practices in the above function:
3 seconds might be enough for superman to type his user name, but a normal user obviously needs more time. Such a timeout should actually be tunable, because not all people type at the same pace, or should be long enough for even the slowest of users.
Norty Norty. This printing should have been done before starting up the timer. Printing may be a time consuming operation, and thus will leave less time then expected for the user to type in the expected input.
This kind of mistake is hard to catch. It will cause the program to randomally exit somewhere LATER during the execution. If we'll trace it with a debugger, we'll see that the signal was received while we were already executing a completely different part of the program, leaving us scratching our head and looking up to the sky, hoping somehow inspiration will fall from there to guide us to the location of the problem.
As if it's not enough that we gave the poor user a short time to check for the input, we're also inserting some database checking operation while the timer is still ticking. Even if we also want to timeout the database operation, we should probably set up a different timer (and a different ALRM signal handler), so as not to confuse a slow user with a slow database server. It will also allow the user to know why we are timing out. Without this information, a person trying to figure out why the program suddenly exits, will have hard time finding where the fault lies.