You are on page 1of 10

HANDLING SIGNALS IN A

MULTITHREADED APPLICATION
BY LIRAN B.H | NOVEMBER 30, 2017 | 3 COMMENTS | LINUX
FacebookTwitterMore
Signals are very useful feature in linux to send notification from one process to another
and from the kernel to the process. Signals are sent in some error cases (accessing
wrong memory address, bus error, floating point error, …) and also to inform the user
application ( timer expired, child process finished, IO is ready, ….)

The signal context


While a signal arrives on a single threaded process, the thread complete the current
instruction, jump to the signal handler and return when it finish.

Signal handlers are per process, signal masks are per thread

On a multithreaded application –

 the signal handler execute in one of the thread contexts.


 We can’t predict the thread that will be chosen to run the signal handler.

Consider the following example:


#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
 
void *threadfn1(void *p)
{
while(1)
{
printf("thread1\n");
sleep(2);
}
return 0;
}
 
void *threadfn2(void *p)
{
while(1){
printf("thread2\n");
sleep(2);
}
return 0;
}
 
void *threadfn3(void *p)
{
while(1){
printf("thread3\n");
sleep(2);
}
return 0;
}
 
 
void handler(int signo, siginfo_t *info, void *extra)
{
int i;
for(i=0;i<10;i++)
{
puts("signal");
sleep(2);
}
}
 
void set_sig_handler(void)
{
        struct sigaction action;
 
 
        action.sa_flags = SA_SIGINFO;

        action.sa_sigaction = handler;
 
        if ( sigaction (SIGRTMIN + 3, &action, NULL) == -1)
{
            perror("sigusr: sigaction");
            _exit(1);
        }
 
}
 
int main()
{

pthread_t t1 , t2, t3 ;

set_sig_handler();

pthread_create( &t1, NULL, threadfn1, NULL);

pthread_create( &t2, NULL, threadfn2, NULL);

pthread_create( &t3, NULL, threadfn3, NULL);

pthread_exit(NULL);

return 0;
}
Compile and run the app, you will see periodic output for each thread:
thread1
thread2
thread3
thread1
thread2
thread3
...

Now send a signal to the process using the kill command:


# kill -37 [pid]

The kernel choose one thread and run the signal handler in its context. In my case
thread 1 selected so the output for 10 times is:
signal
thread2
thread3
signal
thread2
thread3
...

This behaviour can be problematic in case the selected thread is an important task

(Note that if the signal is an exception (SIGSEGV, SIGFPE, SIGBUS, SIGILL, …) the
signal will be caught by the thread doing the exception)

We can’t choose the selected thread but we can do a little trick to hack the system to
choose the thread we want.
The trick is to block the signal on all threads except one thread – the one we want  to
run the signal in:

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
 
void mask_sig(void)
{
sigset_t mask;

sigemptyset(&mask);

         sigaddset(&mask, SIGRTMIN+3);


                
         pthread_sigmask(SIG_BLOCK, &mask, NULL);        
}
 
void *threadfn1(void *p)
{
mask_sig();
while(1){
printf("thread1\n");
sleep(2);
}
return 0;
}
 
void *threadfn2(void *p)
{
mask_sig();
while(1){
printf("thread2\n");
sleep(2);
}
return 0;
}
 
void *threadfn3(void *p)
{
while(1){
printf("thread3\n");
sleep(2);
}
return 0;
}
 
 
void handler(int signo, siginfo_t *info, void *extra)
{
int i;
for(i=0;i<10;i++)
{
puts("signal");
sleep(2);
}
}
 
void set_sig_handler(void)
{
        struct sigaction action;
 
        action.sa_flags = SA_SIGINFO;

        action.sa_sigaction = handler;
 
        if (sigaction(SIGRTMIN + 3, &action, NULL) == -1)
{
            perror("sigusr: sigaction");
            _exit(1);
        }
 
}
 
int main()
{
pthread_t t1,t2,t3;

set_sig_handler();

pthread_create(&t1,NULL,threadfn1,NULL);

pthread_create(&t2,NULL,threadfn2,NULL);

pthread_create(&t3,NULL,threadfn3,NULL);

pthread_exit(NULL);

return 0;
}

We block the signal on threads 1,2 so the system will deliver the signal to thread 3

Run the app, send the signal with kill command. The output:
signal
thread1
thread2
signal
thread1
thread2
...

Another trick is to create a thread for signal handling that will be blocked using sigwait ,
waiting for signal

Behind the scenes


Inside the kernel, each thread has a task_struct object defines in sched.h:

All the signals fields are stored per thread. Actually, there is no structure for the process
all the threads on the same process points to the same memory and files tables so the
kernel need to choose a thread to deliver the signal to:

struct task_struct
{
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;
...
...
...
/* Signal handlers: */
struct signal_struct *signal;

struct sighand_struct *sighand;

sigset_t blocked;

sigset_t real_blocked;

/* Restored if set_restore_sigmask() was used: */


sigset_t saved_sigmask;

struct sigpending pending;

unsigned long sas_ss_sp;

size_t sas_ss_size;

unsigned int sas_ss_flags;


...
...

}
 

Sending signals to a thread


Another option is to use pthread_kill(3) to send a signal directly to a thread.
This can be done only in the same process. For example:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
 
 
void *threadfn1(void *p)
{
while(1){
printf("thread1\n");
sleep(2);
}
return 0;
}
 
void *threadfn2(void *p)
{
while(1){
printf("thread2\n");
sleep(2);
}
return 0;
}
 
void *threadfn3(void *p)
{
while(1){
printf("thread3\n");
sleep(2);
}
return 0;
}
 
 
void handler( int signo, siginfo_t *info, void *extra )
{
int i;

for(i=0;i<5;i++)
{
puts("signal");
sleep(2);
}
}
 
void set_sig_handler(void)
{
        struct sigaction action;

        action.sa_flags = SA_SIGINFO;

        action.sa_sigaction = handler;
 
        if (sigaction( SIGRTMIN + 3, &action, NULL) == -1)
{
            perror("sigusr: sigaction");
            _exit(1);
        }
 }
 

int main()
{
pthread_t t1, t2, t3;

set_sig_handler();
pthread_create( &t1, NULL , threadfn1, NULL);

pthread_create( &t2, NULL , threadfn2, NULL);

pthread_create( &t3, NULL , threadfn3, NULL);

sleep(3);

pthread_kill(t1,SIGRTMIN+3);

sleep(15);

pthread_kill(t2,SIGRTMIN+3);

pthread_kill(t3,SIGRTMIN+3);

pthread_exit(NULL);

return 0;
}

We start with creating 3 threads, then we send a signal to thread 1, wait for the signal
handler to finish then send signals both to threads 2 and 3 , they will run the signal
handler at the same time so in this case we will see :
signal
signal
thread1

siginfo_t ( Data structure containing signal information  )


SYNOPSIS
#include <sys/siginfo.h>
union sigval
{
int sival_int;
void *sival_ptr;
};

typedef struct
{
int si_signo;
int si_code;
union sigval si_value;
int si_errno;
pid_t si_pid;
uid_t si_uid;
void * si_addr;
int si_status;
int si_band;
} siginfo_t;

DESCRIPTION

The siginfo_t structure is passed as the second parameter to a user signal handler function, if


the SA_SIGINFO flag was specified when the handler was installed with sigaction().

Fields of the Structure

The siginfo_t structure contains the following fields:

si_signo  

Signal number being delivered. This field is always set.

si_code  

Signal code. This field is always set. Refer to Signal Codes for information on valid settings, and for
which of the remaining fields are valid for each code.

si_value  

Signal value.

si_errno  

If non-zero, an errno value associated with this signal.

si_pid  

Process ID of sending process.

si_uid  

Real user ID of sending process.


si_addr  

Address at which fault occurred.

si_status  

Exit value or signal for process termination.

si_band  

Band event for SIGPOLL/SIGIO.

st_mtime  

Time of last data modification.

You might also like