Sunday, April 3, 2011

Signals Tutorial : In Linux



Signals are a way of sending simple messages to a process or a group of processes and important thing to remember is signal is Not an Exception but Signals could trigger because of an exception. Signals are one of the oldest inter-process communication methods used. They are used to signal asynchronous events to one or more processes. A signal could be generated by a keyboard interrupt or an error condition such as the process attempting to access a non-existent location in its virtual memory. Signals are also used by the shells to signal job control commands to their child processes.
Signals are used by the kernel to notify processes of system events. However, signals can only be processed when the process is in user mode (process context). If a signal has been sent to a process that is in kernel mode (kernel context), it is dealt with immediately on returning to user mode.


Every signal has a unique signal name, an abbreviation that begins with SIG (SIGINT for interrupt signal, for example) and a corresponding signal number. Additionally, for all possible signals, the system defines a default action to take when a signal occurs. There are four possible default actions:
·         Exit: forces the process to exit.
·         Core: forces the process to exit and create a core file.
·         Stop: stops the process.
·         Ignore: ignores the signal; no action taken.

Signals are not presented to the process immediately after they are generated. Instead, they must wait until the process is running again. Every time a process exits from a system call, its signal and blocked fields are checked and, if there are any unblocked signals, they can now be delivered. This might seem a very unreliable method since it is dependant on the processes checking the signals, but every process in the system is making system calls, for example to write a character to the terminal, all of the time. Processes can elect to wait for signals if they wish, they are suspended in state Interruptible until a signal is presented. The Linux signal processing code looks at the sigaction structure for each of the current unblocked signals.

A signal's handler is set to the default action, then the kernel will handle it, alternatively, a process may have specified its own signal handler. Signal handler is a routine that is called whenever the signal is generated and the sigaction structure holds the routine's address. It is the kernel's job to call the process' signal handling routine. SIGKILL and SIGSTOP are the only signals whose functionality cannot be changed.

Each one of signals can be in one of three states:
  • We may have our own signal handler for the signal.
  • Signal may be handled by the default handler. Every signal has its default handler function. For instance, SIGINT default handler will terminate your application.
  • Signal may be ignored. Ignoring signal sometimes referred to as blocking signal.
Linux implements signals using information stored in the task_struct for the process. The number of supported signals is limited to the word size of the processor. Processes with a word size of 32 bits can have 32 signals whereas 64 bit processors may have up to 64 signals. The currently pending signals are kept in the signal field with a mask of blocked signals held in blocked. With the exception of SIGSTOP and SIGKILL, all signals can be blocked. If a blocked signal is generated, it remains pending until it is unblocked. Linux also holds information about how each process handles every possible signal and this is held in an array of sigaction data structures pointed at by the task_struct for each process. Amongst other things it contains either the address of a routine that will handle the signal or a flag which tells Linux that the process either wishes to ignore this signal or let the kernel handle the signal for it. The process modifies the default signal handling by making system calls and these calls alter the sigaction for the appropriate signal as well as the blocked mask.



It's possible that the process to which you want to send the signal is sleeping. If that process is sleeping at an interruptible priority, then the process will awaken to handle the signal.


The kernel keeps track of pending signals in each process' process structure. This is a 32-bit value in which each bit represents a single signal. Because it is only one bit per signal, there can only be one signal pending of each type. If there are different kinds of signals pending, the kernel has no way of determining which came in when. It will therefore process the signals starting at the lowest numbered signal and moving up.Signals can be blocked i.e., prevented from being received.

SIGKILL’s value is 9. This is why kill -9 <pid> shell command is so effective – it sends SIGKILL signal to the process.

Here I'am explaining a simple code that would make you understand these concepts, and the flow goes starting with signal registration.



Registering Signal Handler :

There are several interfaces that allow you to register your own signal handler.
signal()


This is the oldest one. It accepts two arguments, first signal number (one of those SIGsomething) and second pointer to a signal handler function. Signal handler function returns void and accepts single integer argument that represents a signal number that has been sent. This way you can use the same signal handler function for several different signals. Here is a short code snippet demonstrating how to use it.

Code: signal_test.c

/*
 * This is to test signals 
*/
#include <stdlib.h>
#include <stdio.h>
#include <signal.h> 

void mySig_handler(int signum)
{
      printf("Received Signal No: %d\n", signum);
}

int main()
{
     signal(SIGINT, mySig_handler);
     sleep(10); /*This is the time you press Ctrl+c to send signal*/
     return 0;
}

Note: Copying the code directly into your source.c file will also copies the invisible characters and finally you will left out with stray errors, i recommend you to type the code and that even becomes a practice.

This is a simple and small application registers its own SIGINT signal. Compile this program and see what is happening when you run it and press CTRL-C.

$ gcc signal_test.c -o signal
$ ./signal

Ignoring signals and restoring original signal handler routine :


Using signal() you can set default signal handler for certain signal to be used. You can also tell the system that you would like to ignore certain signal. To ignore the signal, specify SIG_IGN as a signal handler. To restore default signal handler, specify SIG_DFL as signal handler.
Although this seems to be everything you may need, it is better to avoid using signal(). There’s a portability problem with this system call. I.e. it behaves differently on different operating systems. There’s a newer system call that does everything signal() does and also gives slightly more information about the actual signal, its origin, etc. and here it is,

sigaction()

sigaction() is another system call that manipulates signal handler. It is much more advanced comparing to good old signal(), and its declaration goes like this 


int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

arg1    : specifies a signal number. 
arg2,3 : pointers to structure called sigaction. 

This structure specifies how process should handle given signal.

struct sigaction
{
     void (*sa_handler)(int signum);
     void (*sa_sigaction)(int signum, siginfo_t *siginfo, void *uctx);
     sigset_t sa_mask;
     int sa_flags;
     void (*sa_restorer)(void);
};



sa_handler is a pointer to the signal handler routine. The routine accepts single integer number containing signal number that it handles and returns void – same as signal handler registered by signal(). In addition, sigaction() let you have more advanced signal handler routine. If needed sa_sigaction pointer should point to the advanced signal handler routine. This one receives much more information about the origin of the signal.

To use sa_sigaction routine, make sure to set SA_SIGINFO flag in sa_flags member of struct sigaction. Similarily to sa_handlersa_sigaction receives an integer telling it what signal has been triggered. In addition it receives a pointer to structure called siginfo_t. It describes the origin of the signal. For instance, si_pid member of siginfo_t holds the process ID of the process that has sent the signal. There are several other fields that tell you lots of useful information about the signal. You can find all the details on sigaction‘s manual page (man sigaction).

Last argument received by sa_sigaction handler is a pointer to ucontext_t. This type different from architecture to architecture. My advice to you is to ignore this pointer, unless you are writing a new debugger.

One additional advantage of sigaction() compared to signal() is that it allows you to tell operating system what signals can handle signal you are registering. I.e. it gives you full control over what signals can arrive, while your program handling another signal.
To tell this, you should manipulate sa_mask member of the struct sigaction. Note that is asigset_t field. sigset_t type represents signal masks. To manipulate signal masks, use one of the following functions:
  • int sigemptyset(sigset_t *) – to clear the mask.
  • int sigfillset(sigset_t *) – to set all bits in the mask.
  • int sigaddset(sigset_t *, int signum) – to set bit that represents certain signal.
  • int sigdelset(sigset_t *, int signum) – to clear bit that represents certain signal.
  • int sigismember(sigset_t *, int signum) – to check status of certain signal in a mask.

Here is a short code snippet demonstrating how to use sigaction().

Code: sigaction_test.c

/*
 * This is to test sigaction 
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h> 

struct sigaction act;

void sighandler(int signum, siginfo_t *info, void *ptr)
{
    printf("Received signal %d\n", signum);
    printf("Signal originates from process %lu\n",(unsigned long)info->si_pid);
}

int main()
{
    printf("I am %lu\n", (unsigned long)getpid());

    memset(&amp;act, 0, sizeof(act));

    act.sa_sigaction = sighandler;
    act.sa_flags = SA_SIGINFO;

    sigaction(SIGTERM, &amp;act, NULL);

    // Waiting for CTRL+C...
    sleep(100);

    return 0;

}
Note: Copying the code directly into your source.c file will also copies the invisible characters and finally you will left out with stray errors, i recommend you to type the code and that even becomes a practice.

2 comments:

  1. Hi. Thank you for the article.

    You've mentiod: "Signal may be ignored. Ignoring signal sometimes referred to as blocking signal"

    This is wrong. Blocking a signal is different from ignoring it. A signal is not delivered as
    long as it is blocked; it is delivered only after it has been unblocked. An ignored sig-
    nal is always delivered, and there is no further action.

    ReplyDelete