Unix/Linux Signal Handling

poornaMoksha's Avatar author of Unix/Linux Signal Handling
This is an article on Unix/Linux Signal Handling in C.
Rated 5.00 By 1 users
Signals are the interrupts. They are a way of providing asynchronous events. For example a user typing ctrl-c on terminal to stop a program. Most of the programs need to handle signals. Every signal has a name like SIGxxx. For example : SIGALRM is the alarm signal that is generated when the timer set by the alarm function goes off.

On Linux, different signals are defined in <bits/signum.h>.

The are various scenarios in which is signal is generated :
  1. When a user press a terminal key (like ctrl-c). This type of signal are known as terminal generated signals and normally cause the interrupt signal SIGINT to be generated. SO now we know how/why our programs stop when we press ctrl-c.
  2. Whenever hardware exceptions occur. Like whenever divide by zero, illegal memory access etc occur. These type of exceptions are usually detected by the hardware and then the hardware notifies the kernel. Its the kernel that notifies the process which generated this exception through a signal known as :SIGSERV.
  3. From inside a process, kill() function allows you to pass any signal to other process provided that you are the owner of that process or you are the superuser.
  4. Then there is kill command through which we can kill the processes in our system. Its just an interface to the kill() function so the conditions for killing a process remain the same(as described in point above).
  5. Scenarios like in which some software condition occurs like alarm set by the process goes off, or when a process tries to write to a pipe after the reader has been terminated etc.

Going a bit deeper



Whenever a signal is generated, the process has to tell the kernel what to do with this signal. So in all three kind of options are available for a process :
  • Catch a Signal: In order to make this happen, we tell the kernel to call a function of ours when a signal is generated. In that particular function, we can do our stuff that we want to do when a signal is generated. For example, If the SIGCHLD signal is caught, it means that a child process has terminated, so the signal-catching function can call waitpid to fetch the child's process ID and termination status. Similarly a SIGTERM signal which is sent when we issue kill command or kill function. Please note here that the two signals SIGKILL and SIGSTOP cannot be caught.
  • Ignore a signal: Well, this action works for most of the signals except the two signals SIGKILL and SIGSTOP. The reason is that these two signals provide kernel or superuser a sure-shot way of stopping any process.Please note here that if you ignore signals generated through an exception such that 'divide by zero' etc then the behavior of the process is undefined.
  • Let the default action apply: Every signal has a default action. For most of the signals, default action is to terminate the process.
Following is a list of signals and their description for your reference :

Code:
SIGABRT    abnormal termination (abort)    
SIGALRM    timer expired (alarm )          
SIGBUS     hardware fault                  
SIGCANCEL  threads library internal use    
SIGCHLD    change in status of child       
SIGCONT    continue stopped process        
SIGEMT     hardware fault                  
SIGFPE     arithmetic exception            
SIGFREEZE  checkpoint freeze               
SIGHUP     hangup                          
SIGILL     illegal instruction             
SIGINFO    status request from keyboard    
SIGINT     terminal interrupt character    
SIGIO      asynchronous I/O                
SIGIOT     hardware fault                  
SIGKILL    termination                     
SIGLWP     threads library internal use    
SIGPIPE    write to pipe with no readers   
SIGPOLL    pollable event (poll)           
SIGPROF    profiling time alarm (setitimer)
SIGPWR     power fail/restart             
SIGQUIT    terminal quit character        
SIGSEGV    invalid memory reference       
SIGSTKFLT  co-processor stack fault        
SIGSTOP    stop                                
SIGSYS     invalid system call             
SIGTERM    termination                     
SIGTHAW    checkpoint thaw                 
SIGTRAP    hardware fault                  
SIGTSTP    terminal stop character         
SIGTTIN    background read from control tty
SIGTTOU    background write to control tty
SIGURG     urgent condition (sockets)                 
SIGUSR1    user-defined signal                  
SIGUSR2    user-defined signal                  
SIGVTALRM  virtual time alarm (setitimer)       
SIGWAITING threads library internal use         
SIGWINCH   terminal window size change          
SIGXCPU    CPU limit exceeded (setrlimit)       
SIGXFSZ    file size limit exceeded (setrlimit) 
SIGXRES    resource control exceeded

Example



The function used for implementing signals is :

Code:
#include <signal.h>
void (*signal(int signo, void (*func )(int)))(int);
As it could be a bit difficult to decode this prototype, so lets start with its explanation :

The prototype states that, the function requires two arguments. The first argument is signo(an integer). The second argument is a function pointer that takes single integer and returns nothing. While the function itself returns function pointer whose return type is void.

I understand that the prototype could be still cryptic, lets try to ease out a level more by creating the following typedef :

Code:
typedef void Sigfunc(int);
Now, the prototype of the above function becomes :

Code:
Sigfunc *signal(int, Sigfunc *);
Now, I think its far more easy to understand.

The signo argument is just the name of the signal. The value of func is
  1. SIG_IGN : If we want to ignore the signal
  2. SIG_DFL: If we want default action to be applied
  3. Function pointer of a function to be called when the signal occurs.
When we specify the address of a function to be called when the signal occurs, we mean to "catch" the signal.

Lets consider the following code :

Code:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>

void sig_handler(int signo)
{
   if (signo == SIGUSR1)
       printf("received SIGUSR1\n");
   else if (signo == SIGUSR2)
       printf("received SIGUSR2\n");
   else
       printf("ERR : received signal %d\n", signo);
}

int main(void)
{
    if (signal(SIGUSR1, sig_handler) == SIG_ERR)
        printf("\ncan't catch SIGUSR1\n");
    if (signal(SIGUSR2, sig_handler) == SIG_ERR)
        printf("\ncan't catch SIGUSR2\n");
    while(1)  // Simulate a dummy wait
        sleep(1);

    return 0;
}
In the above program, we have created a sig_handler function which we have registered with kernel through signal function call for the signals SIGUSR1 and SIGUSR2. So the idea is that whenever a signal USR1 or USR2 from kill command is sent to this process, the function sig_handler should be called and we should be able to handle the signal cleanly. In this case we just print to stdout when a signal is received.

Lets run it :

Code:
./signal
Now, in some other terminal, find the PID of signal process and kill it using signal USR1.

Code:
$ ps -aef | grep signal
himanshu  5018  4659  0 08:27 pts/1    00:00:00 ./signal
himanshu  5043  4918  0 08:28 pts/2    00:00:00 grep --colour=auto signal

$ kill -USR1 5018
Now come to the same terminal where signal process was running and you will see that the process is not killed as it has handled the signal:
Code:
$ ./signal 
received SIGUSR1

Conclusion



To Conclude, signals are un-avoidable part of system programming. Almost all kind of tools/utilities etc take care of signal handling and process them accordingly.
lionaneesh like this
Invasive contributor
5Oct2011,09:13   #2
lionaneesh's Avatar
A Pretty Simple and Easy to understand tutorial! Thanks!