Linux Signals

Overview

Sending and receiving signals are key features in Linux. A Linux signal is a type of interrupt,
used to announce asynchronous events to a process. It’s easier to illustrate it with codes.

Let’s start with a simple server program:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    while(1)
    {
        printf("Operation 1\n");
        sleep(1);
    }
}

If we run the program above, it will create an infinite loop

Sending Signals

We can interrupt this loop by press Ctl+c or use killall command. This is an example of
sending signals. There are 64 Linux signals and most of them are used to terminate programs.
We can update our codes to capture Linux signals.

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

void handler(int sig)
{
  printf("Received signal: %d\n", sig);
}

int main()
{
  for(int ii=1; ii <= 64; ii++)
  {
    signal(ii, handler);
  }
  while(1)
  {
    printf("operate once\n");
    sleep(1);
  }
}

In the above program, I added a function handler to capture all the received signals. If I
press Ctl-C the console will show capturing signal 2, but the loop will continue

If I open another terminal and use command killall book, the console will show capturing
signal 15 and the loop continues too.

The loop will terminate only if I enter killall -9 book. Signal 9 will actually kill the
process.

The signal might also get ignored. I’ll add a line of code signal(15, SIG_ING) to ignore
signal 15. Here SIG_ING is a macro to ignore signals.

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

void handler(int sig)
{
  printf("Received signal: %d\n", sig);
}

int main()
{
  for(int ii=1; ii <= 64; ii++)
  {
    signal(ii, handler);
    signal(15, SIG_IGN);
  }
  while(1)
  {
    printf("operate once\n");
    sleep(1);
  }
}

After firing up ./book program, if I use killall book or killall -15 book, the signal
will not be captured but the other signal will be captured.

Additionally, signal can also be set as system default with SIG_DFL. Of all 64 signals,
signal 9 is special, it cannot be ignored nor can be captured. So even if we set signal(9, SIG_IGN), it won’t work and signal 9 can’t be ignored.

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

void handler(int sig)
{
  printf("Received signal: %d\n", sig);
}

int main()
{
  for(int ii=1; ii <= 64; ii++)
  {
    signal(ii, handler);
    signal(9, SIG_IGN);
  }
  while(1)
  {
    printf("operate once\n");
    sleep(1);
  }
}

To recap, signals are used to pass messages among different processes about different types of
events. Signals cannot pass any data to processes. In Linux, we could use kill and killall
to send signals. To use kill we need to find the process id first.

Here the process id is 8619.

We can send signal 15 to this process

kill -15 8619

Similarly, use kill -9 8619 to kill the process.

Types of Signals

To list all 64 signals, use kill -l command.

The most commonly used signals are 2 SIGINT, 15 SIGTERM and 9 SIGKILL. Note that signal
19 SIGSTOP cannot be ignored nor captured, just like 9, but 9 is more commonly used.

Signal 17 SIGHLD kills the child-process which is also widely used in the multi-processing
context, which I will discuss in another post.

Another interesting signal is 14 SIGALRM which sends alarm to signals. To illustrate alarm
signal, I’ll add a function to handle the alarm.

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

void handler(int sig)
{
  printf("Received signal: %d\n", sig);
}

void alarmfunc(int sig)
{
  printf("Received alarm signal: %d\n", sig);
  alarm(3);
}

int main()
{
  for(int ii=1; ii <= 64; ii++)
  {
    signal(ii, handler);
  }
  signal(SIGALRM, alarmfunc);

  while(1)
  {
    printf("operate once\n");
    sleep(1);
  }
}

Why do we need signals?

When we have some processes running in the background, maybe killing some is not a good idea.
Because if the processes exit abruptly without proper post-handling, it may cause issues. The
more appropriate approach would be sending a signal to the target process, once the process
receives the signal, it will invoke a post-signal-handling function, which will handle the
event accordingly, e.g. releasing memory, garbage collecting, closing web socket, remove temp
files, rolling back database etc. If we sent signal 0 to a process, we can check if this
process is still alive.


   Reprint policy


《Linux Signals》 by Isaac Zhou is licensed under a Creative Commons Attribution 4.0 International License
  TOC