Linux provides a mechanism to interrupt a running program. Some of the interrupts are the result of user programs doing something bad, and some of the interrupts are external events that are intending to get the attentions of a running program.
We are going to start out with the more traditional signal calling mechanism. A lot of existing programs use this interface. However, signal calls are considered unreliable and a newer mechanism is now available which is considered more robust --- we will cover this later in this set of notes.
Each of the above signals have a specific number. If you have a process associated with process ID 1648, then you can send a signal to a process with a command like:
kill -9 1648
This command sends signal number 9 (which happens to be SIGKILL) to process ID 1648. If you don't know what number is associated with a desired signal, you can issue the command to list the signal numbers for you:
kill -l
If a process doesn't handle a signal, then the process is terminated. For example, most processes don't handle SIGINT and that's why control-c terminates most processes. However, some processes want to use control-c for something else like maybe copy. In this case the application handles control-c and takes whatever action is desired for the SIGINT signal.
Consider the following example
/* We'll start by writing the function which reacts to the signal which is passed in the parameter sig. This is the function we will arrange to be called when a signal occurs. We print a message, then reset the signal handling for SIGINT (by default generated by pressing CTRL-C) back to the default behavior. Let's call this function ouch. */ #include <signal.h> #include <stdio.h> #include <unistd.h> void ouch(int sig) { printf("OUCH! - I got signal %d\n", sig); (void) signal(SIGINT, SIG_DFL); } /* The main function has to intercept the SIGINT signal generated when we type Ctrl-C . For the rest of the time, it just sits in an infinite loop, printing a message once a second. */ int main() { (void) signal(SIGINT, ouch); while(1) { printf("Hello World!\n"); sleep(1); } }
This program will print out Hello World forever until something interrupts it.
Instead of issuing the kill command from the command line, you can also call
kill from your C program. For example, the following C call will issue a SIGTERM
to process 1648:
kill( 1648, SIGTERM);
This command has other features that can be discoved from the man pages --- for example, the kill command can be used to find out if a process still exists. Note that waitpid can be used for child processes to accomplish this need.
Another couple of useful routines:
This example is going to demonstrate that a signal handler can process more than one signal type. Your handler can discriminate which signal was actually called by looking at the integer passed in. Also this example is going to illustrate the SIGCLD signal which goes off when one of your child processes terminates. SIGCLD is very handy to know about because it gives you a better way to "harvest" your terminated children (remember if you don't do anything you leave around Zombie/Defunct processes).
There is an ALARM signal which can sometimes be useful. Here is the example from your book.
/* In alarm.c, the first function, ding, simulates an alarm clock. */ #include <sys/types.h> #include <sys/wait.h> #include <signal.h> #include <stdio.h> #include <unistd.h> static int alarm_fired = 0; void mysig(int sig) { int status; pid_t pid; printf("Signal %d \n", sig); if (sig == SIGALRM) { alarm_fired = 1; } if (sig == SIGCLD) { // harvest terminated DEFUNCT child process pid = waitpid(-1, &status, WNOHANG); printf(" Child Process(%d) terminated with a status of %d\n", pid, status); } } /* In main, we tell the child process to wait for five seconds before sending a SIGALRM signal to its parent. */ int main() { int pid; printf("alarm application starting\n"); if((pid = fork()) == 0) { // Child process sleep(5); // Wake up parent process with a SIGALRM kill(getppid(), SIGALRM); exit(0); } /* The parent process arranges to catch SIGALRM with a call to signal and then waits for the inevitable. */ printf("waiting for alarm to go off\n"); (void) signal(SIGALRM, mysig); (void) signal(SIGCLD, mysig); do { pause(); // Wait for any signal call } while(!alarm_fired); printf("Ding!\007\n"); printf("done\n"); exit(0); }
One of the problems with the old way of doing signals is the fact that while processing a signal, you can get another signal. Also, there are times where it is very awkward dealing with signals.
So to fix this we need to use a Mask to specify which signals are masked. In the following example we are repeating one of our previous examples using the new method. In this case we will make sure that if we are processing on Control-C, we can't get a second Control-C signal.
Instead of a signal call, we use a sigaction call. You pass a structure called sigaction that contains 3 fields:
To set the sa_mask we can use the following calls:
#include <signal.h> #include <stdio.h> #include <unistd.h> struct sigaction act_open; void ouch(int sig) { printf("OUCH! - I got signal %d\n", sig); // Set SIGINT back to the default action act_open.sa_handler = SIG_DFL; sigaction(SIGINT, &act_open, 0); } int main() { act_open.sa_flags = 0; // Create a mostly open mask -- only masking SIGINT sigemptyset(&act_open.sa_mask); sigaddset(&act_open.sa_mask, SIGINT); act_open.sa_handler = ouch; sigaction(SIGINT, &act_open, 0); while(1) { printf("Hello World!\n"); sleep(1); } }
An additional feature that the new interface provides is a way to block specified signals during critical portions of code. This is very good, because it is tough to recover from signals in certain situations.
To block or unblock signals, use the sigprocmask routine.
The first parameter can take on values of:
#include <signal.h> #include <stdio.h> #include <unistd.h> struct sigaction act_protected; void ouch(int sig) { printf("OUCH! - I got signal %d\n", sig); } int main() { struct sigaction act_open; sigset_t signals_open, signals_protected; // Create a protected interupt mask -- only unmasking SIGSEGV sigfillset(&signals_protected); sigdelset(&signals_protected, SIGSEGV); // Create a mostly open interupt mask -- only masking SIGINT sigemptyset(&signals_open); sigaddset(&signals_open, SIGINT); // Fill in sigaction -- only masking SIGINT act_open.sa_flags = 0; sigemptyset(&act_open.sa_mask); sigaddset(&act_open.sa_mask, SIGINT); act_open.sa_handler = ouch; // during SIGINT call, further SIGINT calls are blocked sigaction(SIGINT, &act_open, 0); while(1) { sigprocmask(SIG_SETMASK, &signals_open, NULL); // Now all signals are allowed except SIGINT printf("slightly critical only SIGINT blocked\n"); sleep(5); // the following enables SIGINT sigprocmask(SIG_UNBLOCK, &signals_open, NULL); // Now all signals are allowed including SIGINT printf("Open completely to interrupts\n"); sleep(5); sigprocmask(SIG_BLOCK, &signals_protected, NULL); // All signals are blocked except SIGSEGV printf("IMPORTANT DATABASE WORK complete protected -- except SIGSEGV\n"); sleep(5); } }
Linux/Unix provides you with 2 signals that you can do whatever you want with. The following example demonstrates a parent and a child alternately scheduling each other with sigusr1 signals.
#include <unistd.h> #include <signal.h> #include <sys/types.h> int ntimes=0; main() { pid_t pid, ppid; void p_action(int), c_action(int); static struct sigaction pact, cact; /* set SIGUSR1 action for parent */; pact.sa_handler = p_action; sigaction(SIGUSR1, &pact, NULL); switch (pid = fork()) { case -1: // Shouldn't get here perror("synchro"); exit(1); case 0: // child cact.sa_handler = c_action; sigaction(SIGUSR1, &cact, NULL); // get parent process-id ppid = getppid(); for (;;) { sleep(1); // wake up parent kill (ppid, SIGUSR1); pause(); // wait for parent signal } default: // parent for (;;) { pause(); // wait for child signal sleep(1); // wake up child kill (pid, SIGUSR1); } } } // end of main void p_action(int sig) { printf("Parent caught signal %d\n", ++ntimes); } void c_action(int sig) { printf("Child caught signal %d\n", ++ntimes); }
What happens if you eliminate the command inside of the child logic that does the following (WHY?):
//sigaction(SIGUSR1, &cact, NULL);