curl -O -s https://www.labs.cs.uregina.ca/330/Signals/Posix/Lab10.zip unzip Lab10.zip
When a process terminates abnormally it tries to send a signal
indicating what went wrong.
C programs and UNIX can catch these signals for
diagnostics.
The kernel can send signals. There are two kinds of kernel signals:
You can also send your own signals through:
Signals are software generated interrupts that are sent to a process when
a event occurs.
Signals are the software version of a hardware interrupt. Just like a
hardware interrupt,
a signal can be blocked from being delivered in the future.
Each signal has a default action which is one of the following:
Signals are listed in a system header file:
There is also usually a man page with a list of signals and their default actions:
NOTE: Certain signals cannot be caught or ignored. These are SIGKILL and SIGSTOP.
Let's experiment with signals on the shell.
To do this, we will use an infinitely looping program that prints an incrementing number every second:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main (void) { int i; for( i = 0;; ++i) { printf("%i\n",i); sleep(1); } }
We are going to experiment with keyboard sequences and the kill command.
Once you have compiled the above program, run it.
It will loop infinitely counting up from 0.
How can we stop it?
Try:
These keyboard sequences are actually mapped to signals. To see the current mappings of keystrokes for the interrupt and quit signals, use the following command:
stty -a
The general format of the kill command is:
% kill [-signal] pid
When issued, the kill command will send a signal to the indicated process. The signal can be an integer or the symbolic signal name with the SIG prefix removed. If no signal is specified then SIGTERM (terminate) is sent by default.
To find the pid, you can use the ps command.
Try running the program given above and use the following signals to give you the four possible actions of a signal:
To give you a list of signals that you can use with kill use:
kill -l
A few notes on kill:
In addition to working with signals on the command line, we can also implement signals in C code.
We will be looking at one system call for sending signals:
We will also be looking at the POSIX signal handling interface handling and catching signals:
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);
The kill system call can be used to send any signal to any process group or process.
If pid is positive, then signal sig is sent to pid.
If pid equals 0, then sig is sent to every process in the process group of the current process. (the man pages on kill)
Example:
kill(getpid(), SIGINT);
would send the interrupt signal to the id
of the calling process.
-This would have an effect similar to the exit() command.
Note: kill(pid, SIGKILL); sends a signal value 9 which is a sure kill. This signal cannot be caught or ignored
For completeness, there is another function, called raise(), which also sends signals. However, raise() is a library function rather than a system call.
#include <signal.h> int raise(int sig);
The raise() function sends a signal to the current process. It is equivalent to
kill(getpid(), sig);
In the beginning, we mentioned that each signal has a default action. Sometimes, you may want to overwrite this default action so that you can control what will happen when a certain signal is received.
You can overwrite the action of all signals except for SIGKILL and SIGSTOP.
To give a signal a new action, you first have to create a new signal set, which is a bit mask indicating which signal(s) you will be working with:
The prototypes for sigemptyset and sigaddset are below.
#include <signal.h> int sigemptyset (sigset_t *signal_set); int sigaddset (sigset_t *signal_set, int signal_num);
These two functions return 0 on success. Otherwise, they return -1.
For instance, if you want to work with overwritting the default action of SIGINT, you would have to add that signal to a "cleared" or "emptied" signal set. The partial code might look something like this:
//Step 1: declare your signal set variable sigset_t interruptMask; //Step 2: clear the bitmask of the signal set variable sigemptyset (&interruptMask); //Step 3: add the SIGINT signal to the signal set variable sigaddset (&interruptMask, SIGINT);
Of course, you would want to build in error checking to ensure that everything went well.
You are only half-way there. You still have to indicate what you want to do if you do receive the signal that you are interested in. This involves a further three steps:
 
First, let's look at what the prototype for our function will look like:
static void your_own_signal_handler (int signal_number, siginfo_t *signal_info, void *context);
 
Second, let's look at what is inside a struct sigaction (that we have to fill with information about our function)
struct sigaction { int sa_flags; void (*sa_handler) (int); void (*sa_sigaction) (int, siginfo_t *, void *); sigset_t sa_mask; }
 
Third, let's look at the prototype for sigaction() (that will hook the signal up to our signal handler):
#include <signal.h> int sigaction(int sig_num, const struct sigaction *act, struct sigaction *old_act);
 
To continue the example from above, let's say instead of terminating your process on a SIGINT, you want to print a message instead:
//Define what your function will do when it receives the signal static void yourSignalHandler (int signalNo, siginfo_t *info, void *context) { printf ("\nSignal %d received. \n", signalNo); } int main() { struct sigaction act; //Do all the bitmask stuff for setting the stage //Load the sigaction structure act.sa_sigaction = &yourSignalHandler; act.sa_mask = interruptMask; act.sa_flags = SA_SIGINFO; //Use sigaction to hook up the signal to the function sigaction (SIGINT, &act, NULL) }
Of course, you would want to build in error checking to ensure that everything went well.
By setting this code up, you are saying that if the process receives a signal SIGINT, then a call will be made to the user-defined function called yourSignalHandler.
A Complete Example:
/* set CTRL-C and CTRL-\ to be trapped by a function called signal_catcher */ #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> static void yourSignalHandler (int signalNo, siginfo_t *info, void *context); int main (void) { int i; //Variables needed for signals sigset_t interruptMask; struct sigaction act; //Set the stage sigemptyset (&interruptMask); sigaddset (&interruptMask, SIGINT); sigaddset (&interruptMask, SIGQUIT); //Hook up the signalhandler to SIGINT and SIGQUIT act.sa_sigaction = &yourSignalHandler; act.sa_mask = interruptMask; act.sa_flags = SA_SIGINFO; if (sigaction (SIGINT, &act, NULL) == -1) { perror("sigaction cannot set SIGINT"); exit(SIGINT); } if (sigaction (SIGQUIT, &act, NULL) == -1) { perror("sigaction can not set SIGQUIT"); exit(SIGQUIT); } for( i = 0; ; ++i) { printf("%i\n",i); sleep(1); } } static void yourSignalHandler (int signalNo, siginfo_t *info, void *context) { printf("\nSignal %d received. \n", signalNo); if (signalNo == SIGQUIT) exit(1); }
Note: instead of the signal names, the following integers could have been used:
SIGINT - 2 - traps CTRL-C
SIGQUIT - 3 - traps CTRL-\
A process can send signals to other processes
To send a signal, use the kill command (really a misnomer in this case)
Remember there are different ways of getting process id's from within a C program:
you can use getpid()--to get the current process id
you can use getppid()--to get the parent process id
you can capture the return from fork(). In the parent process, the return from fork() is the process id of the child that has just been spawned.
What does the following code do?
// original code from http://jan.netcomp.monash.edu.au/OS/l8_1.html // now available from: https://jan.newmarch.name/ProgrammingUnix/processes/lecture.html // modified for Posix signals #include <stdio.h> #include <stdlib.h> #include <unistd.h> //added this to the original #include <signal.h> #include <sys/types.h> static void intHandler (int signalNo, siginfo_t *info, void *context) { printf("Ouch - shot in the ...\n"); exit(2); } int main(int argc, char *argv[]) { pid_t pid; //Variables needed for signals sigset_t interruptMask; struct sigaction act; if ((pid = fork()) < 0) { fprintf(stderr, "fork failed\n"); exit(1); } if (pid == 0) { //set the stage (the bitmask) sigemptyset (&interruptMask); sigaddset (&interruptMask, SIGINT); //Hook up the signalhandler to SIGINT act.sa_sigaction = &intHandler; act.sa_mask = interruptMask; act.sa_flags = SA_SIGINFO; if (sigaction (SIGINT, &act, NULL) == -1) { perror("sigaction cannot set SIGINT"); exit(SIGINT); } while (1) printf("Running amok!!!\n"); } sleep(3); kill(pid, SIGINT); exit(0); }
Interprocess Communications in Unix--The Nooks and Crannies by John Shapley Gray (pages 100 to 117)
man pages
Dr. Hilderman's notes