Search code examples
clinuxsignalsipcsigprocmask

Sequential signals between two processes


I have a parent process and two children. The parent process only creates two children, a reader and a counter, and waits for its death. The children do the following things.

The first child (reader):

  1. opens file,
  2. reads a line,
  3. sends a signal (SIGUSR1) to the second child,
  4. waits for a signal from the second child,
  5. go to 2 if we can read another line, else kill the second child.

The second child (counter):

  1. waits for a signal (SIGUSR1) from reader,
  2. counts line length,
  3. sends a signal to reader and go to 1.

I have trouble waiting for a signal from another process. The signal can be received before I call pause() function, i.e., process can be blocked forever. I also tried to use sigprocmask(), sigsuspend() and sigwaitinfo(), but it doesn't work correctly.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>

enum { MAX_LEN = 16 };

void handler(int signo)
{
    // do nothing
}

int main(int argc, const char * argv[])
{
    sigset_t ss;
    sigemptyset(&ss);
    sigaddset(&ss, SIGUSR1);

    // handle SIGUSR1
    signal(SIGUSR1, handler);

    // shared memory for file line
    char *data = mmap(NULL, MAX_LEN, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);

    pid_t *pid_counter = mmap(NULL, sizeof(*pid_counter), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);

    pid_t pid_reader;
    if (!(pid_reader = fork())) {
        sigprocmask(SIG_BLOCK, &ss, NULL);
        printf("READER: waiting signal from COUNTER\n"); fflush(stdout);
        sigwaitinfo(&ss, NULL);
        sigprocmask(SIG_UNBLOCK, &ss, NULL);
        printf("READER: got signal\n"); fflush(stdout);

        printf("READER: opening file\n"); fflush(stdout);
        FILE *f = fopen(argv[1], "r");
        while (fgets(data, MAX_LEN, f) > 0) {
            printf("READER: reading line and waiting signal from COUNTER\n"); fflush(stdout);

            sigprocmask(SIG_BLOCK, &ss, NULL);
            kill(*pid_counter, SIGUSR1);
            sigwaitinfo(&ss, NULL);
            sigprocmask(SIG_UNBLOCK, &ss, NULL);

            printf("READER: got signal\n"); fflush(stdout);
        }
        printf("READER: closing file and killing COUNTER\n"); fflush(stdout);
        fclose(f);
        kill(*pid_counter, SIGTERM);
        _exit(0);
    }

    if (!(*pid_counter = fork())) {
        sleep(1);
        printf("COUNTER: send signal to READER that COUNTER is ready\n"); fflush(stdout);
        while (1) {
            sigprocmask(SIG_BLOCK, &ss, NULL);
            kill(pid_reader, SIGUSR1);
            printf("COUNTER: waiting signal from READER\n"); fflush(stdout);
            sigwaitinfo(&ss, NULL);
            sigprocmask(SIG_UNBLOCK, &ss, NULL);
            printf("COUNTER: got signal\n"); fflush(stdout);

            printf("%d\n", strlen(data));
            fflush(stdout);
        }
    }

    wait(NULL);
    wait(NULL);

    return 0;
}

For this code I can get the following sequence:

$ gcc -o prog prog.c && ./prog input.dat
READER: waiting signal from COUNTER
COUNTER: send signal to READER that COUNTER is ready
COUNTER: waiting signal from READER
READER: got signal
READER: opening file
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: reading line and waiting signal from COUNTER
READER: got signal
READER: closing file and killing COUNTER

Why counter doesn't write "got signal"? How I can send and receive signals synchronously? (I know about other IPC methods, but I need synchronization through signals.)


Solution

  • There are a few obvious problems here.

    The one that is actually causing your issue is this:

    if (!(*pid_counter = fork())) {
    

    Remember that (1) pid_counter points to shared memory; and (2) fork() returns twice every time it is called. When it returns in the child, it will set *pid_counter to 0, but when it returns in the parent, it'll set *pid_counter to the PID of the child. You cannot predict which will happen first. What's actually going on in your case is that it ends up set to 0, and so all your kill calls in the reader process are sending signals to every process in your process group, so both reader and counter are getting them at the same time. This is causing your synchronization to fail, because both processes are returning from sigwaitinfo() at the same time. In the reader, you should send SIGUSR1 to the counter process only.

    What you need to do is change it to this:

    pid_t temp_pid
    if ( !(temp_pid = fork()) ) {
        *pid_counter = getpid();
    

    Other points:

    1. sigprocmask() is preserved across fork() calls, so you should just set it once before fork()ing. You never need to unblock it in your case, and should not.

    2. There is no need to (and arguably better not to) establish a handler for SIGUSR1 if you're calling sigwaitinfo() on it.

    3. strlen() returns type size_t, so your printf() format specifier should be %zu, not %d.

    4. All your calls to fflush(stdout), while harmless, are here superfluous.

    5. You hardly ever check any of the returns from your system calls. This is not just for production code - if your program is not working, the very first thing to do it verify that you're checking all your system calls for success, because the reason it may not be working is because one of those calls is failing, perhaps because you passed it bad information. It's more important to check for success when you are testing your program, not less important.

    Anyway, here's a working version of your program:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/ipc.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <signal.h>
    #include <string.h>
    
    enum { MAX_LEN = 16 };
    
    int main(int argc, const char * argv[])
    {
        if ( argc != 2 ) {
            fprintf(stderr, "Enter one argument, and one argument only.\n");
            return EXIT_FAILURE;
        }
    
        // shared memory for file line
        char *data = mmap(NULL, MAX_LEN, PROT_READ | PROT_WRITE, MAP_SHARED |
                          MAP_ANON, -1, 0);
        if ( data == MAP_FAILED ) {
            perror("mmap() failed for data");
            exit(EXIT_FAILURE);
        }
    
        pid_t *pid_counter = mmap(NULL, sizeof *pid_counter, PROT_READ |
                                  PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
        if ( pid_counter == MAP_FAILED ) {
            perror("mmap() failed for pid_counter");
            exit(EXIT_FAILURE);
        }
    
        pid_t pid_reader, temp_pid;
    
        sigset_t ss;
        sigemptyset(&ss);
        sigaddset(&ss, SIGUSR1);
        if ( sigprocmask(SIG_BLOCK, &ss, NULL) == -1 ) {
            perror("sigprocmask() failed");
            exit(EXIT_FAILURE);
        }
    
        if ( (pid_reader = fork()) == -1 ) {
            perror("fork() failed for reader");
            exit(EXIT_FAILURE);
        }
        else if ( !pid_reader ) {
            printf("READER: waiting signal from COUNTER\n");
    
            if ( sigwaitinfo(&ss, NULL) == -1 ) {
                perror("sigwaitinfo() failed in reader");
                _exit(EXIT_FAILURE);
            }
    
            printf("READER: got signal\n");
    
            printf("READER: opening file\n");
    
            FILE *f = fopen(argv[1], "r");
            if ( !f ) {
                fprintf(stderr, "Couldn't open input file\n");
                _exit(EXIT_FAILURE);
            }
    
            while ( fgets(data, MAX_LEN, f) ) {
                printf("READER: reading line and waiting signal from COUNTER\n");
    
                if ( kill(*pid_counter, SIGUSR1) == -1 ) {
                    perror("kill() for SIGUSR1 failed in reader");
                    _exit(EXIT_FAILURE);
                }
    
                if ( sigwaitinfo(&ss, NULL) == -1 ) {
                    perror("sigwaitinfo() failed in reader");
                    _exit(EXIT_FAILURE);
                }
    
                printf("READER: got signal\n");
            }
            printf("READER: closing file and killing COUNTER\n");
            fclose(f);
            if ( kill(*pid_counter, SIGTERM) == -1 ) {
                perror("kill() for SIGTERM failed in reader");
                _exit(EXIT_FAILURE);
            }
    
            _exit(EXIT_SUCCESS);
        }
    
        if ( (temp_pid = fork()) == -1 ) {
            perror("fork() failed for counter");
            exit(EXIT_FAILURE);
        }
        else if ( temp_pid == 0 ) {
            *pid_counter = getpid();
    
            sleep(1);
            printf("COUNTER: send signal to READER that COUNTER is ready\n");
    
            while (1) {
                if ( kill(pid_reader, SIGUSR1) == -1 ) {
                    perror("kill() failed for SIGUSR1 in counter");
                    _exit(EXIT_FAILURE);
                }
    
                printf("COUNTER: waiting signal from READER\n");
    
                if ( sigwaitinfo(&ss, NULL) == -1 ) {
                    perror("sigwaitinfo() failed in counter");
                    _exit(EXIT_FAILURE);
                }
    
                printf("COUNTER: got signal\n");
    
                printf("%zu\n", strlen(data));
            }
    
            _exit(EXIT_SUCCESS);
        }
    
        if ( wait(NULL) == -1 ) {
            perror("first wait() failed");
            exit(EXIT_FAILURE);
        }
    
        if ( wait(NULL) == -1 ) {
            perror("second wait() failed");
            exit(EXIT_FAILURE);
        }
    
        return 0;
    }
    

    with the following output for the shown file:

    paul@thoth:~/src$ cat file.txt
    line one
    line two
    line three
    
    paul@thoth:~/src$ ./sig file.txt
    READER: waiting signal from COUNTER
    COUNTER: send signal to READER that COUNTER is ready
    COUNTER: waiting signal from READER
    READER: got signal
    READER: opening file
    READER: reading line and waiting signal from COUNTER
    COUNTER: got signal
    9
    COUNTER: waiting signal from READER
    READER: got signal
    READER: reading line and waiting signal from COUNTER
    COUNTER: got signal
    9
    COUNTER: waiting signal from READER
    READER: got signal
    READER: reading line and waiting signal from COUNTER
    COUNTER: got signal
    11
    COUNTER: waiting signal from READER
    READER: got signal
    READER: reading line and waiting signal from COUNTER
    COUNTER: got signal
    1
    COUNTER: waiting signal from READER
    READER: got signal
    READER: closing file and killing COUNTER
    paul@thoth:~/src$