C Programming

How to use signal handlers in C language?

In this article we are going to show you how to use signal handlers in Linux using C language. But first we will discuss what is signal, how it will generate some common signals which you can use in your program and then we will look how various signals can be handled by a program while the program executes. So, let’s start.

Signal

A signal is an event which is generated to notify a process or thread that some important situation has arrived. When a process or thread has received a signal, the process or thread will stop what its doing and take some action. Signal may be useful for inter-process communication.

Standard Signals

The signals are defined in the header file signal.h as a macro constant. Signal name has started with a “SIG” and followed by a short description of the signal. So, every signal has a unique numeric value. Your program should always use the name of the signals, not the signals number. The reason is signal number can differ according to system but meaning of names will be standard.

The macro NSIG is the total number of signal defined. The value of NSIG is one greater than the total number of signal defined (All signal numbers are allocated consecutively).

Following are the standard signals:

Signal Name Description
SIGHUP Hang-up the process. The SIGHUP signal is used to report disconnection of the user’s terminal, possibly because a remote connection is lost or hangs up.
SIGINT Interrupt the process. When the user types the INTR character (normally Ctrl + C) the SIGINT signal is sent.
SIGQUIT Quit the process. When the user types the QUIT character (normally Ctrl + \) the SIGQUIT signal is sent.
SIGILL Illegal instruction. When an attempt is made to execute garbage or privileged instruction, the SIGILL signal is generated. Also, SIGILL can be generated when the stack overflows, or when the system has trouble running a signal handler.
SIGTRAP Trace trap. A breakpoint instruction and other trap instruction will generate the SIGTRAP signal. The debugger uses this signal.
SIGABRT Abort. The SIGABRT  signal is generated when abort() function is called. This signal indicates an error that is detected by the program itself and reported by the abort() function call.
SIGFPE Floating-point exception. When a fatal arithmetic error occurred the SIGFPE signal is generated.
SIGUSR1 and SIGUSR2 The signals SIGUSR1 and SIGUSR2 may be used as you wish. It is useful to write a signal handler for them in the program that receives the signal for simple inter-process communication.

Default Action Of Signals

Each signal has a default action, one of the following:

Term: The process will terminate.
Core: The process will terminate and produce a core dump file.
Ign: The process will ignore the signal.
Stop: The process will stop.
Cont: The process will continue from being stopped.

Default action may be changed using handler function. Some signal’s default action cannot be changed. SIGKILL and SIGABRT signal’s default action cannot be changed or ignored.

Signal Handling

If a process receives a signal, the process has a choice of action for that kind of signal. The process can ignore the signal, can specify a handler function, or accept the default action for that kind of signal.

  • If the specified action for the signal is ignored, then the signal is discarded immediately.
  • The program can register a handler function using function such as signal or sigaction. This is called a handler catches the signal.
  • If the signal has not been neither handled nor ignored, its default action takes place.

We can handle the signal using signal or sigaction function. Here we see how the simplest signal() function is used for handling signals.

int signal () (int signum, void (*func)(int))

The signal() will call the func function if the process receives a signal signum. The signal() returns a pointer to function func if successful or it returns an error to errno and -1 otherwise.

The func pointer can have three values:

  1. SIG_DFL: It is a pointer to system default function SIG_DFL(), declared in h header file. It is used for taking default action of the signal.
  2. SIG_IGN: It is a pointer to system ignore function SIG_IGN(),declared in h header file.
  3. User defined handler function pointer: The user defined handler function type is void(*)(int), means return type is void and one argument of type int.

Basic Signal Handler Example

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_handler(int signum){

  //Return type of the handler function should be void
  printf("\nInside handler function\n");
}

int main(){
  signal(SIGINT,sig_handler); // Register signal handler
  for(int i=1;;i++){    //Infinite loop
    printf("%d : Inside main function\n",i);
    sleep(1);  // Delay for 1 second
  }
  return 0;
}

In the screenshot of the output of Example1.c, we can see that in main function infinite loop is executing. When user typed Ctrl+C, main function execution stop and the handler function of the signal is invoked. After completion of the handler function, main function execution resumed. When user type typed Ctrl+\, the process is quit.

Ignore Signals Example

#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int main(){
  signal(SIGINT,SIG_IGN); // Register signal handler for ignoring the signal

  for(int i=1;;i++){    //Infinite loop
    printf("%d : Inside main function\n",i);
    sleep(1);  // Delay for 1 second
  }
  return 0;
}

Here handler function is register to SIG_IGN() function for ignoring the signal action. So, when user typed Ctrl+C,  SIGINT signal is generating but the action is ignored.

Reregister Signal Handler Example

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

void sig_handler(int signum){
  printf("\nInside handler function\n");
  signal(SIGINT,SIG_DFL);   // Re Register signal handler for default action
}

int main(){
  signal(SIGINT,sig_handler); // Register signal handler
  for(int i=1;;i++){    //Infinite loop
    printf("%d : Inside main function\n",i);
    sleep(1);  // Delay for 1 second
  }
  return 0;
}

In the screenshot of the output of Example3.c, we can see that when user first time typed Ctrl+C, the handler function invoked. In the handler function, the signal handler re register to SIG_DFL for default action of the signal. When user typed Ctrl+C for second time, the process is terminated which is the default action of SIGINT signal.

Sending Signals:

A process also can explicitly send signals to itself or to another process. raise() and kill() function can be used for sending signals. Both functions are declared in signal.h header file.

int raise(int signum)

The raise() function used for sending signal signum to the calling process (itself). It returns zero if successful and a nonzero value if it fails.

int kill(pid_t pid, int signum)

The kill function used for send a signal signum to a process or process group specified by pid.

SIGUSR1 Signal Handler Example

#include<stdio.h>
#include<signal.h>

void sig_handler(int signum){
  printf("Inside handler function\n");
}

int main(){
  signal(SIGUSR1,sig_handler); // Register signal handler
  printf("Inside main function\n");
  raise(SIGUSR1);
  printf("Inside main function\n");
  return 0;
}

Here, the process send SIGUSR1 signal to itself using raise() function.

Raise with Kill Example Program

#include<stdio.h>
#include <unistd.h>
#include<signal.h>
void sig_handler(int signum){
  printf("Inside handler function\n");
}

int main(){
  pid_t pid;
  signal(SIGUSR1,sig_handler); // Register signal handler
  printf("Inside main function\n");
  pid=getpid();      //Process ID of itself
  kill(pid,SIGUSR1);        // Send SIGUSR1 to itself
  printf("Inside main function\n");
  return 0;
}

Here, the process send SIGUSR1 signal to itself using kill() function. getpid() is used to get the process ID of itself.

In the next example we will see how parent and child processes communicates (Inter Process Communication) using kill() and signal function.

Parent Child Communication with Signals

#include<stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<signal.h>
void sig_handler_parent(int signum){
  printf("Parent : Received a response signal from child \n");
}

void sig_handler_child(int signum){
  printf("Child : Received a signal from parent \n");
  sleep(1);
  kill(getppid(),SIGUSR1);
}

int main(){
  pid_t pid;
  if((pid=fork())<0){
    printf("Fork Failed\n");
    exit(1);
  }
  /* Child Process */
  else if(pid==0){
    signal(SIGUSR1,sig_handler_child); // Register signal handler
    printf("Child: waiting for signal\n");
    pause();
  }
  /* Parent Process */
  else{
    signal(SIGUSR1,sig_handler_parent); // Register signal handler
    sleep(1);
    printf("Parent: sending signal to Child\n");
    kill(pid,SIGUSR1);
    printf("Parent: waiting for response\n");
    pause();
  }
  return 0;
}

Here, fork() function creates child process and return zero to child process and child process ID to parent process. So, pid has been checked to decide parent and child process. In parent process, it is slept for 1 second so that child process can register signal handler function and wait for the signal from parent. After 1 second parent process send SIGUSR1 signal to child process and wait for the response signal from child. In child process, first it is waiting for signal from parent and when signal is received, handler function is invoked. From the handler function, the child process sends another SIGUSR1 signal to parent. Here getppid() function is used for getting parent process ID.

Conclusion

Signal in Linux is a big topic. In this article we have seen how to handle signal from the very basic, and also get a knowledge how the signal generate, how a process can send signal to itself and other process, how signal can be used for inter-process communication.

About the author

Bamdeb Ghosh

Bamdeb Ghosh is having hands-on experience in Wireless networking domain.He's an expert in Wireshark capture analysis on Wireless or Wired Networking along with knowledge of Android, Bluetooth, Linux commands and python. Follow his site: wifisharks.com