All about Linux signals

What happens when a process receives a signal?


Default actions


With each signal there is an associated default action which is taken when you don't provide a signal handler and you don't block a signal. The actions are:
  • Termination of a process. This is the most common action. Not only for SIGTERM or SIGQUIT but also for signals like SIGPIPE, SIGUSR1, SIGUSR2 and others.
  • Termination with code dump. This is common for signals that indicate a bug in the program like SIGSEGV, SIGILL, SIGABRT and others.
  • Few signals are ignored by default like SIGCHLD.
  • SIGSTOP (and similar stop signals) cause the program to suspend and SIGCOND to continue. The most common situation is when you use the CTRL-Z command in the shell.

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

Interrupting system calls


If you set a signal handler in your program you must be prepared that some system calls can be interrupted by signals. Even if you don't set any signal handler there could be signals delivered to your program so it's best to be prepared for that. An example situation is compiling your program with the -pg gcc option (enable profiling), so when running it occasionally gets SIGPROF handled without your knowledge, but causing syscalls to be interrupted.

What is interrupted?


Every system or standard library function that uses a system call can be potentially interrupted and you must consult it's manual page to be sure. In general function that return immediately (don't wait for any I/O operation to complete or sleep) are not interruptible like socket(2) which just allocates a socket and doesn't wait for anything. On the other hand functions that wait for something (like for a network transfer, pipe read, explicit sleep etc.) will be interruptible like select(2), read(2), connect(2) and you must be prepared for that. What exactly happens when a signal arrives during waiting for such function to complete is described in it's manual page.

Simple example of signal aware code


The simplest case is sleep(3) which is implemented using nanosleep(2). If it's interrupted by a signal it exits returning number of seconds left to sleep. If you want to sleep 10s regardless of signals that are handled by your application you must do something like:
  1. #include <unistd.h>
  2. #include <signal.h>
  3.  
  4. static void hdl (int sig)
  5. {
  6. }
  7.  
  8. void my_sleep (int seconds)
  9. {
  10. while (seconds > 0)
  11. seconds = sleep (seconds);
  12. }
  13.  
  14. int main (int argc, char *argv[])
  15. {
  16. signal (SIGTERM, hdl);
  17.  
  18. my_sleep (10);
  19.  
  20. return 0;
  21. }

This example works, but if you try it and send few signals during sleep you can see that it may sleep different amount of time. This is because sleep(3) takes the argument and returns the value with 1s resolution so it can't be precise telling you how long it need to sleep after interruption.

Data transferring and signals


Very important thing in daemon programs is proper handling of interruption of system functions. One part of the problem is that common functions that transfer data like recv(2), write(2) and similar like select(2) may be interrupted by a signal which is handled in it's handler, so you need to continue receiving data, restart select(2) etc. We've just seen a simple example how to handle it in case of sleep(3).

See an example of how to handle interruption of system calls.

This program reads from it's standard input and copies the data to the standard output. Additionally, when SIGUSR1 is received it prints to stderr how many bytes has been already read and written. It installs a signal handler which sets a global flag to 1 if called. Whatever the program does at the moment it receives the signal, the numbers are immediately printed. It works because read(2) and write(2) functions are interrupted by signals even during operation. In case of those functions two things might happen:

  • When read(2) waits for data or write(2) waits for stdout to put some data and no data were yet transfered in the call and SIGUSR1 arrives those functions exit with return value of -1. You can distinguish this situation from other errors by reading the value of the errno variable. If it's EINTR it means that the function was interrupted without any data transfered and we can call the function again with the same parameters.
  • Another case is that some data were transfered but the function was interrupted before it finished. In this case the functions don't return an error but a value less that the supplied data size (or buffer size). Neither the return value nor the errno variable tells us that the function was interrupted by a signal, if we want to distinguish this case we need to set some flag in the signal handler (as we do in this example). To continue after interruption we need to call the function again keeping in mind that some data were consumed or read adn we must restart from the right point. In our example only the write(2) must be properly restarted, we use the written variable to track how many bytes were actually written and properly call write(2) again if there are data left in the buffer.

Remember that not all system calls behave exactly the same way, consult their manual page to make sure.

Reading the sigaction(2) manual page you can think that setting the SA_RESTART flag is simpler that handling system call interruption. The documentation says that setting it will make certain system calls automatically restartable across signals. It's not specified which calls are restarted. This flag is mainly used for compatibility with older systems, don't use it.

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.