(Updated April 20, 2024)
Overview
As we have seen, many hardware and software components vie for CPU time. In addition to hardware interrupts when a device needs servicing and software interrupts that perform privileged actions within the OS, signal handling components allow programs to try to intercept some of the kinds of interrupts that may occur.
Simply put, there are actions within a system that you can control to a certain degree.
Signals
The use and management of signals are well defined in the C documentation and are extensions of those found in the operating system. It does not have to be a Linux system, as all operating systems have interrupts of some form.
You can find a good amount of brief information on program support. There is also some good documentation on signal handling as well as how to cause a signal to occur in a program.
Generally speaking, there are a handful of predefined signals within the C language and many defined by the OS and listed in the signal.h
header file.
Here are a few taken from the /usr/include/asm-generic/signal.h
on an Ubuntu Linux system:
#define SIGHUP 1
#define SIGINT 2
#define SIGQUIT 3
#define SIGILL 4
#define SIGTRAP 5
#define SIGABRT 6
#define SIGIOT 6
#define SIGBUS 7
#define SIGFPE 8
#define SIGKILL 9
#define SIGUSR1 10
#define SIGSEGV 11
#define SIGUSR2 12
#define SIGPIPE 13
#define SIGALRM 14
#define SIGTERM 15
As with most systems, there are names assigned to numbers to hide the details from the application writer and provide a standardized interface.
Testing Signals
As this is an investigation into how signals work we have provided a simple program to begin the deeper understanding. This example also uses the alarm()
function defined by the POSIX standard to set a timer that raises a signal after a specified number of seconds.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
/*
* This function is multi-purpose
* it will handle multiple signals
* and perform an appropriate action.
*/
void sig_handler(int signum){
if (signum == SIGALRM)
printf("WAKE UP! (received signal #%d)\nPress CTRL-C to exit.\n", signum);
if (signum == SIGINT) {
printf("Received CTRL-C (signal #%d)! Exiting!\n", signum);
exit(0);
}
}
int main(void) {
int n;
signal(SIGALRM,sig_handler); // Signal handler for SIGALRM
signal(SIGINT,sig_handler); // Same handler for SIGINT
printf("Enter the number of seconds before the alarm rings: ");
scanf("%d",&n);
printf("The alarm will be schedule to happen in %d seconds.\n", n);
alarm(n); // Scheduled alarm after n seconds
for(int i=1;;i++){
printf("%d : sleeping...\n",i);
sleep(1); // Sleep for 1 second
}
return 0;
}
The program will set up signal handlers for SIGALRM
and SIGINT
. Now, SIGALRM
is the signal for the alarm going off after a timed interval, and it only occurs once; it does not repeat.
The SIGINT
signal results from the end user pressing CTRL-C on the keyboard to attempt to stop the program. There are several ways to send a signal to a program to terminate it. This one can be caught and acted upon. You could technically make it so that CTRL-C is not a means to end the program. We will not do that here.
When the user runs the program, it prompts for the number of seconds before the alarm goes off. When it does go off, the user is given instructions on terminating the program. When the user attempts that, it traps for the CTRL-C, announces that it caught it, and ends the program.
Sample output is shown below:
w.jojo@acadnx:~$ ./alarm Enter the number of seconds before the alarm rings: 6 The alarm will be schedule to happen in 6 seconds. 1 : sleeping... 2 : sleeping... 3 : sleeping... 4 : sleeping... 5 : sleeping... 6 : sleeping... WAKE UP! (received signal #14) Press CTRL-C to exit. 7 : sleeping... 8 : sleeping... 9 : sleeping... ^CReceived CTRL-C (signal #2)! Exiting! w.jojo@acadnx:~$
Read up on additional signals that can and cannot be caught. Have fun with this!