All about Linux signals

Blocking signals


How to block signals


There is sometime a need to block receiving some signals, not handling them. Traditional way is to use the deprecated signal(2) function with SIG_IGN constant as a signal handler. There is also newer, recommended function to do that: sigprocmask(2). It has a bit more complex usage, let's see an example of signal blocking with sigprocmask().

This program will sleep for 10 seconds and will ignore the SIGTERM signal during the sleep. It works this way because we've block the signal with sigprocmask(2). The signal is not ignored, it's blocked, it means that are queued by the kernel and delivered when we unblock the signal. This is different than ignoring the signal with signal(2). First sigprocmask(2) is more complicated, it operates in a set of signals represented by sigset_t, not on one signal. The SIG_BLOCK parameter tells that the the signals in set are to be blocked (in addition to the already blocked signals). The SIG_SETMASK tells that the signals in set are to be blocked, and signals that are not present in the set are to be unblocked. The third parameter, if not NULL, is written with the current signal mask. This allows to restore the mask after modifying the process' signal mask. We do it in this example. The first sleep(3) function is executed with SIGTERM blocked, if the signal arrives at this moment, it's queued. When we restore the original signal mask, we unblock SIGTERM and it's delivered, the signal handler is called.

See the sigprocmask(2) manual on how to use this function and sigsetops(3) on how to manipulate signal sets.

Preventing race conditions.


In the previous example nothing really useful was presented, such use of sigprocmask(2) isn't very interesting. Here is a bit more complex example of code that really needs sigprocmask(2):

Fragment of example: Signal race with select() and accept()

  1. while (!exit_request) {
  2. fd_set fds;
  3. int res;
  4.  
  5. /* BANG! we can get SIGTERM at this point. */
  6.  
  7. FD_ZERO (&fds);
  8. FD_SET (lfd, &fds);
  9.  
  10. res = select (lfd + 1, &fds, NULL, NULL, NULL);
  11.  
  12. /* accept connection if listening socket lfd is ready */
  13. }

Let's say it's an example of a network daemon that accepts connections using select(2) and accept(2). It can use select(2) because it listens on multiple interfaces or waits also for some events other than incoming connections. We want to be able to cleanly shut it down with a signal like SIGTERM (remove the PID file, wait for pending connections to finish etc.). To do this we have a handler for the signal defined which sets global flag and relay on the fact that select(2) will be interrupted when the signal arrives at the moment we are just waiting for some events. If the main loop in the program looks similarly as the above code everything works... almost. There is a specific case in which the signal will not interrupt the program even if it does nothing at all at the moment. When it arrives between checking the while condition and executing select(2). The select(2) function will not be interrupted (because signal was handled) and will sleep until some file descriptor it monitors will be ready.

This is where the sigprocmask(2) and other "new" functions are useful. Let's see an improved version:

Fragment of example: Using pselect() to avoid a signal race

  1. sigemptyset (&mask);
  2. sigaddset (&mask, SIGTERM);
  3.  
  4. if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) {
  5. perror ("sigprocmask");
  6. return 1;
  7. }
  8.  
  9. while (!exit_request) {
  10.  
  11. /* BANG! we can get SIGTERM at this point, but it will be
  12. * delivered while we are in pselect(), because now
  13. * we block SIGTERM.
  14. */
  15.  
  16. FD_ZERO (&fds);
  17. FD_SET (lfd, &fds);
  18.  
  19. res = pselect (lfd + 1, &fds, NULL, NULL, NULL, &orig_mask);
  20.  
  21. /* accept connection if listening socket lfd is ready */
  22. }

What's the difference between select(2) and pselect(2)? The most important one is that the later takes an additional argument of type sigset_t with set of signals that are unblocked during the execution of the system call. The idea is that the signals are blocked, then global variables/flags that are changed in signal handlers are read and then pselect(2) runs. There is no race because pselect(2) unblocks the signals atomically. See the example: the exit_request flag is checked while the signal is blocked, so there is no race here that would lead to executing pselect(2) just after the signal arrives. In fact, in this example we block the signal all the time and the only place where it can be delivered to the program is the pselect(2) execution. In real world you may block the signals only for the part of the program that contains the flag check and the pselect(2) call to allow interruption in other places in the program.
Another difference not related to the signals is that select(2)'s timeout parameter is of type struct timeval * and pselect(2)'s is const struct timespec *. See the pselect(2) manual page for more information.

If you like poll(2) there is analogous ppoll(2) functions, but in contrast of pselect(2) ppoll(2) is not a standard POSIX function.

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.