Home > other >  pthread_kill(), how to send signals and catch them in a thread
pthread_kill(), how to send signals and catch them in a thread

Time:11-24

I am writing a simple program that generates signals (SIGUSR1 or SIGUSR2, but currently I am only worried about SIGUSR1) until the user terminates the program, these signals are then sent to a thread to catch and log them.

The child process is in charge of creating the thread to receive the signals.

I believe I install the handler correctly, as well as the signal mask.

Originally, the signals would generate but the thread wouldn't receive and catch them. I was using kill() to send the signal. I switched to using the pthread library version of pthread_kill(), which now results in a segmentation fault.

Just a little lost on where I am going wrong and would appreciate any input!

Here's what I got:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>

pthread_t reporter_thread;
struct sigaction sa;
sigset_t new_sig_set; // set up mask

void handle_SIGUSR1(int sig)
{
    write(STDOUT_FILENO, "SIGUSR1 received", 20);
}

void * reporter(void *arg)
{
    printf("Entered reporter thread\n");
    //empty mask
    pthread_sigmask(SIG_UNBLOCK, &new_sig_set, NULL);
    while(1){
        sigaction(SIGUSR1, &sa, NULL);
    }
}

int main(int argc, char **argv)
{
    /* set up the signal masks */
    sigemptyset(&new_sig_set); // empty the mask
    sigaddset(&new_sig_set, SIGUSR1); // add SIGUSR1 to the mask
    pthread_sigmask(SIG_BLOCK, &new_sig_set, NULL); // block SIGUSR1

    /* set up the signal handler */
    sa.sa_handler = handle_SIGUSR1; 
    sa.sa_flags = 0;

    // randomly generate signals every 5 seconds.
    int pid = fork();
    if (pid == 0)
    {
        //will need to exec() sigcatcher.c here
        pthread_create(&reporter_thread, NULL, reporter, NULL);
        printf("Created reporter thread\n");
    }
    else
    {
        wait(NULL);
        pthread_sigmask(SIG_BLOCK, &new_sig_set, NULL); // block SIGUSR1

        while (1)
        {
            srand((unsigned)time(NULL));
            printf("Generating signal\n");
            //generate a random number between 1 and 2
            int random = rand()%((2 1)-1)   1;
            if (random == 1)
            {
                printf("Sending signal: SIGUSR1\n");
                pthread_kill(reporter_thread, SIGUSR1);
            }
            else
            {
                printf("Something else\n");
            }
            sleep(2);
        }
        return 0;
    }
}

CodePudding user response:

So, several bugs here.

  1. As pilcrow says, pthread_kill is for sending a signal to a thread in the same process. For signaling another process, you did want kill() all along. (And moreover, the reporter_thread object was initialized only in the child process, which has its own memory; it never got initialized at all in the parent.) So why didn't kill() work before? Read on.

  2. The main thread of your child process exits immediately after starting the reporter thread: it falls out of the if (pid == 0) block and does the next thing in the function, which is nothing: it merely returns from main. This causes all the threads of that process to terminate, including the reporter thread. Threads do not get to survive the death of the process. See man pthread_create:

    The new thread terminates in one of the following ways: [...]

    • Any of the threads in the process calls exit(3), or the main thread performs a return from main(). This causes the termination of all threads in the process.

    Moreover, the parent process then calls wait() to reap the child, so by the time you get around to kill(), the child process isn't even a zombie anymore. If you had checked the return value and errno of kill() (hint hint), you should have seen ESRCH, the target process does not exist.

    So you need to make the child process's main thread keep running after starting the reporter thread. For instance you could pthread_join the reporter thread (which may or may not ever return). Likewise, the parent needs to not call wait(); that would never complete since the child process never exits.

  3. The reporter thread has a race. It needs to install the signal handler before unblocking the signal; otherwise the signal is likely to arrive before the handler is installed, which would then be fatal.

    The infinite loop of sigaction also serves no purpose except to waste CPU. The default behavior of sigaction, unless you specify the SA_RESETHAND flag, is that the handler remains installed after the signal is delivered. Have the reporter thread call sigaction only once, then pthread_sigmask once, then enter a while(1) pause(); loop or similar.

  4. The write() in the signal handler writes 20 bytes of a string that is only 17 bytes long. gcc issues a warning about this. Enable all compiler warnings and heed them.

  5. It's unnecessary to call pthread_sigmask a second time in the parent. The signal is already blocked, and nobody is going to be sending signals to the parent anyway. The only reason to call it in the parent at all is to avoid a race by ensuring the child process starts with the signal already blocked.

  6. Unrelated, but never call srand() in a loop; it is pointless and usually counterproductive. Just call it once before the first call to rand().

With these fixed, the program works for me. Try on godbolt.

  • Related