All about Linux signals

What is signaled in Linux


Your process may receive a signal when:
  • From user space from some other process when someone calls a function like kill(2).
  • When you send the signal from the process itself using a function like abort(3).
  • When a child process exits the operating system sends the SIGCHLD signal.
  • When the parent process dies or hangup is detected on the controlling terminal SIGHUP is sent.
  • When user interrupts program from the keyboard SIGINT is sent.
  • When the program behaves incorrectly one of SIGILL, SIGFPE, SIGSEGV is delivered.
  • When a program accesses memory that is mapped using mmap(2) but is not available (for example when the file was truncated by another process) - really nasty situation when using mmap() to access files. There is no good way to handle this case.
  • When a profiler like gprof is used the program occasionally receives SIGPROF. This is sometimes problematic when you forgot to handle interrupting system functions like read(2) properly (errno == EINTR).
  • When you use the write(2) or similar data sending functions and there is nobody to receive your data SIGPIPE is delivered. This is a very common case and you must remember that those functions may not only exit with error and setting the errno variable but also cause the SIGPIPE to be delivered to the program. An example is the case when you write to the standard output and the user uses the pipeline sequence to redirect your output to another program. If the program exits while you are trying to send data SIGPIPE is sent to your process. A signal is used in addition to the normal function return with error because this event is asynchronous and you can't actually tell how much data has been successfully sent. This can also happen when you are sending data to a socket. This is because data are buffered and/or send over a wire so are not delivered to the target immediately and the OS can realize that can't be delivered after the sending function exits.

For a complete list of signals see the signal(7) manual page.

Signal handlers

Traditional signal() is deprecated


The signal(2) function is the oldest and simplest way to install a signal handler but it's deprecated. There are few reasons and most important is that the original Unix implementation would reset the signal handler to it's default value after signal is received. If you need to handle every signal delivered to your program separately like handling SIGCHLD to catch a dying process there is a race here. To do so you would need to set to signal handler again in the signal handler itself and another signal may arrive before you cal the signal(2) function. This behavior varies across different systems. Moreover, it lacks features present in sigaction(2) you will sometimes need.

The recommended way of setting signal actions: sigaction


The sigaction(2) function is a better way to set the signal action. It has the prototype:
  1. int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);

As you can see you don't pass the pointer to the signal handler directly, but instead a struct sigaction object. It's defined as:

  1. struct sigaction {
  2. void (*sa_handler)(int);
  3. void (*sa_sigaction)(int, siginfo_t *, void *);
  4. sigset_t sa_mask;
  5. int sa_flags;
  6. void (*sa_restorer)(void);
  7. };

For a detailed description of this structure's fields see the sigaction(2) manual page. Most important fields are:

  • sa_handler - This is the pointer to your handler function that has the same prototype as a handler for signal(2).
  • sa_sigaction - This is an alternative way to run the signal handler. It has two additional arguments beside the signal number where the siginfo_t * is the more interesting. It provides more information about the received signal, I will describe it later.
  • sa_mask allows you to explicitly set signals that are blocked during the execution of the handler. In addition if you don't use the SA_NODEFER flag the signal which triggered will be also blocked.
  • sa_flags allow to modify the behavior of the signal handling process. For the detailed description of this field, see the manual page. To use the sa_sigaction handler you must use SA_SIGINFO flag here.

What is the difference between signal(2) and sigaction(2) if you don't use any additional feature the later one provides? The answer is: portability and no race conditions. The issue with resetting the signal handler after it's called doesn't affect sigaction(2), because the default behavior is not to reset the handler and blocking the signal during it's execution. So there is no race and this behavior is documented in the POSIX specification. Another difference is that with signal(2) some system calls are automatically restarted and with sigaction(2) they're not by default.

Example use of sigaction()


See example of using sigaction() to set a signal handler with additional parameters.

In this example we use the three arguments version of signal handler for SIGTERM. Without setting the SA_SIGINFO flag we would use a traditional one argument version of the handler and pass the pointer to it by the sa_handler field. It would be a replacement for signal(2). You can try to run it and do kill PID to see what happens.

In the signal handler we read two fields from the siginfo_t *siginfo parameter to read the sender's PID and UID. This structure has more fields, I'll describe them later.

The sleep(3) function is used in a loop because it's interrupted when the signal arrives and must be called again.

SA_SIGINFO handler


In the previous example SA_SIGINFO is used to pass more information to the signal handler as arguments. We've seen that the siginfo_t structure contains si_pid and si_uid fields (PID and UID of the process that sends the signal), but there are many more. They are all described in sigaction(2) manual page. On Linux only si_signo (signal number) and si_code (signal code) are available for all signals. Presence of other fields depends on the signal type. Some other fields are:
  • si_code - Reason why the signal was sent. It may be SI_USER if it was delivered due to kill(2) or raise(3), SI_KERNEL if kernel sent it and few more. For some signals there are special values like ILL_ILLADR telling you that SIGILL was sent due to illegal addressing mode.
  • For SIGCHLD fields si_status, si_utime, si_stime are filled and contain information about the exit status or the signal of the dying process, user and system time consumed.
  • In case of SIGILL, SIGFPE, SIGSEGV, SIGBUS si_addr contains the memory address that caused the fault.

We'll see more examples of use of siginfo_t later.

Compiler optimization and data in signal handler


Let's see the following example:
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <signal.h>
  4. #include <string.h>
  5.  
  6. static int exit_flag = 0;
  7.  
  8. static void hdl (int sig)
  9. {
  10. exit_flag = 1;
  11. }
  12.  
  13. int main (int argc, char *argv[])
  14. {
  15. struct sigaction act;
  16.  
  17. memset (&act, '\0', sizeof(act));
  18. act.sa_handler = &hdl;
  19. if (sigaction(SIGTERM, &act, NULL) < 0) {
  20. perror ("sigaction");
  21. return 1;
  22. }
  23.  
  24. while (!exit_flag)
  25. ;
  26.  
  27. return 0;
  28. }

What it does? It depends on compiler optimization settings. Without optimization it executes a loop that ends when the process receives SIGTERM or other sgnal that terminates the process and was not handler. When you compile it with the -O3 gcc flag it will not exit after receiving SIGTERM. Why? because whe while loop is optimized in such way that the exit_flag variable is loaded into a processor register once and not read from the memory in the loop. The compiler isn't aware that the loop is not the only place where the program accesses this variable while running the loop. In such cases - modifying a variable in a signal handler that is also accessed in some other parts of the program you must remember to instruct the compiler to always access this variable in memory when reading or writing them. You should use the volatile keyword in the variable declaration:

  1. static volatile int exit_flag = 0;

After this change everything works as expected.

Atomic Type


There is one data type defined that is guaranteed to be atomically read and written both in signal handlers and code that uses it: sig_atomic_t. The size of this type is undefined, but it's an integer type. In theory this is the only type you can safely assign and read if it's also accessed in signal handlers. Keep in mind that:

  • It doesn't work like a mutex: it's guaranteed that read or write of this type translates into an uninterruptible operation but code such as:

  1. sig_atomic_t i = 0;
  2.  
  3. void sig_handler (int sig)
  4. {
  5. if (i++ == 5) {
  6. // ...
  7. }
  8. }

Isn't safe: there is read and update in the if operation but only single reads and single writes are atomic.

  • Don't try to use this type in a multi-threaded program as a type that can be used without a mutex. It's only intended for signal handlers and has nothing to do with mutexes!
  • You don't need to worry if data are modified or read in a signal handler are also modified or read in the program if it happens only in parts where the signal is blocked. Later I'll show how to block signals. But you will still need the volatile keyword.

Signal-safe functions


You can't just do anything in a signal handler. Remember that your program is interrupted, you don't know at which point, which data objects are in the middle of being modified. It may be not only your code, but a library you are using or the standard C library. In fact there is a quite short list of function you can safely call from a signal handler in signal(7). You can for example open a file with open(2), remove a file with unlink(2), call _exit(2) (but not exit(3)!) and more. In practice this list is so limited that the best you can do is to just set some global flag to notify the process to do something like exiting. On the other hand the wait(2) and waitpid(2) functions can be used, so you can cleanup dead processes in SIGCHLD, unlink(2) is available, so you can delete a pid file etc.

Alternative method of handling signals: signalfd()


signalfd(2) is a quite new Linux specific call available from the 2.6.22 kernel that allows to receive signals using a file descriptor. This allows to handle signals in a synchronous way, without providing handler functions. Let's see an example of signalfd() use

First we must block the signals we want to handle with signalfd(2) using sigprocmask(2). This function will be described later. Then we call signalfd(2) to create a file descriptor that will be used to read incoming signals. At this point in case of SIGTERM or SIGINT delivered to your program it will not be interrupted, no handler will be called. It will be queued and you can read information about it from the sfd descriptor. You must supply a buffer large enough to read the struct signalfd_siginfo object that will be filled with information similar to the previously described siginfo_t. The difference is that the fields are named a bit different (like ssi_signo instead of si_signo). What is interesting is that the sfd descriptor behaves and can be used just like any other file descriptor, in particular you can:

  • Use it in select(2), poll(2) and similar functions.
  • Make it non-blocking.
  • Create many of them, each handling different signals to return different descriptors as ready by select(2) for every different signal.
  • After fork() the file descriptor is not closed, so the child process can read signals that were send to the parent.

This is perfect to be used in a single-process server with the main loop executes a function like poll(2) to handle many connections. It simplifies signal handling because the signal descriptor can be added to the poll's array of descriptors and handled like any other of them, without asynchronous actions. You handle the signal when you are ready for that because your program is not interrupted.

Comments

Hello, Thank you for good

Hello, Thank you for good article. Is it possible to give permission of other user program to send signal to my process ? If yes, then how to do this ?

I don't think it's possible.

I don't think it's possible.

Restarting system calls

"It's not specified which calls are restarted" - says the last paragraph. It is very much specified, on the signal(7) man page.

very useful and readable blog about Unix signals

Unix signals is a deep and interesting topic. For example, the signal() function is either referring to the version in the C library or the version in the operating system. The topic of signals has a rich history as part of Unix.

Thanks

I just want to say thank you , I was googling about what happens to signals handlers when I fork() and you blog showed up. thanks

Ctrl+Z Signal

CTRL-Z - sends SIGTSTP

Hi, typo jerk here

Hi, typo jerk here again: page3: Moreover, it lack's features -> lacks why the signal was send -> sent page5: signals like SIGPIPE, SIGUSR1, SIGUSR1 -> SIGUSR2 signal i exits -> it This program read from it's -> reads, its Additionally when SIGUSR1 -> Additionally, when [missing comma] I hope you really, truly don't consider this as some kind of personal attack.

i love type jerk!

typos suck! combat them!

Thanks. I don't consider it

Thanks. I don't consider it as an attack :) Some of them are just caused by the fact that English is not my native language.

I was looking for linux

I was looking for linux programming tutorial and i found this blog . keep good work.

Great write-up

Great write-up; thanks for the information.

Signals - That's not everything

I wouls suggest the reading of "Advanced Programming in the Unix Environment". It does not address signals with threads, but it is the most extensive explanation of signal handling. It treats also long jumps to remove races in signal handling.

Problem regarding signals.

Hi I have written a sample program to understand signal handling. signal1.c
#include 
#include 
/* for random() stuff */
#include 
#include 
#include 
#include 

void
termination_handler (int signum)
{
  struct temp_file *p;
 int err;
  printf("\nTerminated\n");
		sleep(10); 
}

int
main (void)
{

	int shmfd,*shared_msg, pid;
	struct sigaction new_action, old_action;
	int shared_seg_size = (1 * sizeof(int));
	//shm operations
	shmfd = shm_open("/shm_sumit",O_CREAT|O_RDWR|O_EXCL,S_IRWXU | S_IRWXG);
	if (shmfd < 0) {
	        perror("In shm_open()");
        	exit(1);
    	}

	ftruncate(shmfd, shared_seg_size);
	shared_msg = (int *)mmap(NULL, shared_seg_size, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0);
	if (shared_msg == NULL) {
	        perror("In mmap()");
        	exit(1);
    	}

        pid = getpid();
        *shared_msg = pid; 

  /* Set up the structure to specify the new action. */
  new_action.sa_handler = termination_handler;
  sigemptyset (&new_action.sa_mask);
  new_action.sa_flags = SA_NODEFER;

    sigaction (SIGUSR1, &new_action, NULL);
     //while(1)
{
   sleep(10);
}
	/*if (shm_unlink("/shm_sumit") != 0) {
	        perror("In shm_unlink()");
        	exit(1);
    	}*/

}



signal2.c


#include 
#include 
#include 
void
termination_handler (int signum)
{
  struct temp_file *p;
  printf("\nTerminated");
}

int
main (void)
{
	int shmfd,*shared_msg, pid;
    struct sigaction new_action, old_action;
    int shared_seg_size = (1 * sizeof(int));
    //shm operations
    shmfd = shm_open("/shm_sumit",O_RDWR,S_IRWXU | S_IRWXG);
    shared_msg = (int *)mmap(NULL, shared_seg_size, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0);
    printf(" Process id of sigtest1 = %d\n",*shared_msg);
 kill(*shared_msg,SIGUSR1);
 kill(*shared_msg,SIGUSR1);
 kill(*shared_msg,SIGUSR1);
 kill(*shared_msg,SIGUSR1);
 kill(*shared_msg,SIGUSR1);
	
	sleep(1);
	if (shm_unlink("/shm_sumit") != 0) {
        perror("In shm_unlink()");
        exit(1);
    }

}
here sigtest2.c send SIGUSR1 to sigtest1.c 5 times, but the string "Terminated " gets printed sometimes 2 times and sometimes 5 times. why is this printing behavior inconsistent??

Hi, because while handling

Hi, because while handling the signal it wont be handle the signal of same type . you can check this by remove the sleep(10) in sigtest1.c . It will work fine.