All about Linux signals

Waiting for a signal


Suppose we want to execute an external command and wait until it exits. We don't want to wait forever, we want to set some timeout after which we will kill the child process. How to do this? To run a command we use fork(2) and execve(2). To wait for a specific process to exit we can use the waitpid(2) function, but it has no timeout parameter. We can also create a loop in which we call sleep(3) with the timeout as an argument and use the fact that sleep(3) will be interrupted by the SIGCHLD signal. This solution will work... almost. It would contain a race condition: if the process exits immediately, before we call sleep(3) we will wait until the timeout expires. It's a race similar to the one described previously.

The proper solution is to use a dedicated function to wait for a signal: see an example of using sigtimedwait().

This program creates a child process that sleeps few seconds (in a real world application this process would do something like execve(2)) and waits for it to finish. We want to implement a timeout after which the process is killed. The waitpid(2) function does not have a timeout parameter, but we use the SIGCHLD signal that is sent when the child process exits. One solution would be to have a handler for this signal and a loop with sleep(3) in it. The sleep(3) will be interrupted by the SIGCHLD signal or will sleep for the whole time which means the timeout occurred. Such a loop would have a race because the signal could arrive not in the sleep(3), but somewhere else like just before the sleep(3). To solve this we use the sigtimedwait(2) function that allows us to wait for a signal without any race. We can do this because we block the SIGCHLD signal before fork(2) and then call sigtimedwait(2) which atomically unblock the signal and wait for it. If the signal arrives it block it again and returns. It can also take a timeout parameter so it will not sleep forever. So without any trick we can wait for the signal safely.

One drawback is that if sigtimedwait(2) is interrupted by another signal it returns with an error and doesn't tell us how much time elapsed, so we don't know how to properly restart it. The proper solution is to wait for all signals we expect at this point in hte program or block other signals. There is another small bug i the program: when we kill the process, SIGCHLD is sent and we don't handle it anywhere. We should unblock the signal before waitpid(2) and have a handler for it.

Other functions to wait for a signal


There are also other functions that can be used to wait for a signal:

  • sigsuspend(2) - waits for any signal. It takes a signal mask of signals that are atomically unblocked, co it doesn't introduce race conditions.
  • sigwaitinfo(2) - like sigtimedwait(2), but without the timeout parameter.
  • pause(2) - simple function taking no argument. Just waits for any signal. Don't use it, you will introduce a race condition similar to the described previously, use sigsuspend(2).

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.