Search code examples
csignalsforkparent-childipc

How to keep sending signals to children from parent process repeatedly?


I'm trying to get a program to create n child processes to read a file, and the parent keeps looping over them and trying to send a signal to keep reading the file if that process is free. Else, if that process is occupied, it moves to the next one. This is my approach using signals, but the output is unexpected. And if i use printf to debug the code in the while loop of the parent, I only get the printf statements itself. Why is this happening?

From what I understand, the expected output is supposed to be all n child processes reading from the file almost at once (The file is small), and then an interval of 1 second before this happens again. Is this correct, or is this not certain due to race conditions?

I know that there may be a better way using pipes, but can this be possible using Signals? Thanks in advance.

  • I know that signal() is not used as much as sigaction(), as mentioned in the man page, but can this be solved nevertheless?

  • Could the unexpected output due to printf be because of the interference of printf itself with the IPC?

EDIT : Pastebin Link for testing : https://pastebin.com/ZWFnkJDA

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

int counter = 0;
int fd;
const char* filename;
int j = 0;
int no_of_children;
int* cpid;

//User define signal handler
static void sig_usr1(int);
static void sig_usr2(int signo, siginfo_t* info, void* context);

static void sig_usr1(int signo)
{
    //Now the child process waits for reading the Filename

    //Block SIGUSR1 until it's complete
    signal(SIGUSR1, SIG_IGN);
    printf("Blocked now.\n");
    printf("Child no %d is reading now.\n\n",getpid());
    fd = open(filename, O_RDONLY | O_CREAT);

    char buf = 'a';
    int k=0;
    char* op = (char*) malloc (255*sizeof(char));

    while(read (fd, &buf, 1))
    {
        if (buf == '\n')
        {
            op[k] = '\0';
            break;
        }

        else
        {
            op[k++] = buf;
        }
    }

    //Now wait for a second and then send a signal
    sleep(1);
    //Print the contents of the buffer via op
    printf("Output: %s\n\n", op);


    //Now unblock the signal
    kill(getppid(), SIGUSR2);
    signal(SIGUSR1, sig_usr1);
    printf("Unblocked now\n");
}

static void sig_usr2(int signo, siginfo_t* info, void* context)
{
    if (signo == SIGUSR2)
    {
        child_pid = info->si_pid;
        printf("Parent Received SIGUSR2. Child Process with PID %d is now free\n\n", child_pid);
    }
}

int main(int argc, char* argv[])
{
    //Filename is the first argument
    filename = argv[1];

    //Number of Child Processes to be spawned
    no_of_children = atoi(argv[2]);

    cpid = (int*) malloc (no_of_children*sizeof(int));

    //Create a sigaction() handler for SIGUSR2
    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = sig_usr2;
    sigaction(SIGUSR2, &sa, NULL);

    //Create no_of_children children
    for(int i=0; i<no_of_children; i++)
    {
        cpid[i] = fork();
        if (cpid[i] == 0)
        {
            //Inside a child
            printf("Created %dth child process", i);
            printf(" with Process ID = %d\n", getpid());            

            signal(SIGUSR1, sig_usr1);

            while(1)
            {
                pause();
            }

            //Every child process must exit so control goes back to the parent
            exit(0);
        }
    }

    //Returns to the parent process
    while(1)
    {
        int fpid = cpid[j];
        //Send the signal to the free child process
        //printf("Sending to PID %d\n", fpid); //----> Uncommenting this line only prints this statement. Why does this happen?
        kill(fpid, SIGUSR1);
        j = (j < no_of_children - 1) ? j + 1 : 0; //----->Does not work as expected if i uncomment this line. I want to switch between PIDs and send signals to all of them
    }

    return 0;
}
  • Sample Output:
$ ./a.out sample.txt 4
Created 0th child process with Process ID = 15734
Created 1th child process with Process ID = 15735
PID: 15737 -> PID: 15736 -> PID: 15735 -> PID: 15734
Created 2th child process with Process ID = 15736
Blocked now.
Child no 15734 is reading now.

Created 3th child process with Process ID = 15737
Output: This is a sample file

Unblocked now
Parent Received SIGUSR2. Child Process with PID 15734 is now free
Blocked now.
Child no 15734 is reading now.

Blocked now.
Child no 15735 is reading now.

Output: This is a sample file

Unblocked now
Output: This is a sample file

Parent Received SIGUSR2. Child Process with PID 15735 is now free
Blocked now.
Child no 15734 is reading now.

Unblocked now
Parent Received SIGUSR2. Child Process with PID 15736 is now free
Blocked now.
Child no 15735 is reading now.

Blocked now.
Blocked now.
Child no 15736 is reading now.

Child no 15737 is reading now.

^\% 

Solution

  • Note: This is not ment as an answer, just some edited code and conclusions that might lead to a solution.

    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<unistd.h>
    #include<signal.h>
    #include<fcntl.h>
    #include<sys/wait.h>
    
    int counter = 0;
    int fd;
    const char* filename;
    int j = 0;
    int no_of_children;
    int* cpid;
    int my_pid;
    
    //User define signal handler
    static void sig_usr1(int);
    static void sig_usr2(int);
    
    static void sig_usr1(int signo)
    {
        //Now the child process waits for reading the Filename
    
        //Block SIGUSR1 until it's complete
        signal(SIGUSR1, SIG_IGN);
        printf("%d\tBlocked now.\n", my_pid);
        printf("%d\tChild no %d is reading now.\n",my_pid, getpid());
        fd = open(filename, O_RDONLY | O_CREAT);
    
        char buf = 'a';
        int k=0;
        char* op = (char*) malloc (255*sizeof(char));
    
        while(read (fd, &buf, 1))
        {
            if (buf == '\n')
            {
                op[k] = '\0';
                break;
            }
    
            else
            {
                op[k++] = buf;
            }
        }
    
        //Now wait for a second and then send a signal
        sleep(1);
        //Print the contents of the buffer via op
        printf("%d\tOutput: %s\n", my_pid, op);
    
    
        //Now unblock the signal
        kill(getppid(), SIGUSR2);
        signal(SIGUSR1, sig_usr1);
        printf("%d\tUnblocked now\n", my_pid);
    }
    
    static void sig_usr2(int signo)
    {
        if (signo == SIGUSR2)
        {
            printf("%d\tParent Received SIGUSR2. Child Process with PID %d is now free\n", my_pid, cpid[j]);
            kill (cpid[j++], SIGUSR1);
            if (j == no_of_children)
            {
                j = 0;
            }
    
        }
    }
    
    int main(int argc, char* argv[])
    {
        my_pid = getpid();
        //Filename is the first argument
        filename = argv[1];
    
        //Number of Child Processes to be spawned
        no_of_children = atoi(argv[2]);
    
        cpid = (int*) malloc (no_of_children*sizeof(int));
    
        signal(SIGUSR2, sig_usr2);
        //Create no_of_children children
        for(int i=0; i<no_of_children; i++)
        {
            cpid[i] = fork();
            if (cpid[i] == 0)
            {
                //Inside a child
                my_pid = getpid();
                printf("%d\tCreated %dth child process", my_pid, i); 
                printf(" with Process ID = %d\n", getpid());            
    
                signal(SIGUSR1, sig_usr1);
    
                while(1)
                {
                    pause();
                }
    
                //Every child process must exit so control goes back to the parent
                exit(0);
            }
            printf("%d\tforked %dth child -> %d\n", my_pid, i, cpid[i]);
        }
    
        //Returns to the parent process
        while(1)
        {
            int fpid = cpid[j];
            //Send the signal to the free child process
            printf("%d\tSending to PID %d\n", my_pid, fpid);
            kill(fpid, SIGUSR1);
            //j = (j < no_of_children - 1) ? j + 1 : 0;
            pause();
        }
    
        return 0;
    }
    

    OK, what did I do?

    1. Add the PID of the current process at the beginning of each printf(). For this the variable my_pid was added and set at convenient places. Yes, I'm aware that some outputs gave this information already. I didn't want to change too much of the OP's source.
    2. Removed double \n in the course. They don't help.
    3. To see the success of fork() print its result.
    4. Add a printf in the endless loop of the main process in the end. I wanted to see which process got signaled how many times at which rate. Wow, that was a lot!

    Hm, could it be that the main process floods the child with so many signals that it can't even get to its call of signal() to block it temporarily?

    1. Add pause() in the endless loop of the main process in the end.

    Now things start to work, but most probably not in a way the OP wanted. Anyway, all child processes are created and receive their signals, reading the file, sending back their signals, and so on.

    It seems there are too many signals in the system. The algorithm as such has to be thought over. So this is my advice:

    • Make it slow. You could speed it up later.
    • Put as many watch points (here: printf()) in the code to follow all events.
    • Especially for multi-processing think about a way to visualize the results. I copied the shell output in an editor and indented the lines like this:
    $ ./signal_repetition signal_repetition.c 4
    1901                    forked 0th child -> 1902
        1902                Created 0th child process with Process ID = 1902
    1901                    forked 1th child -> 1903
            1903            Created 1th child process with Process ID = 1903
    1901                    forked 2th child -> 1904
                1904        Created 2th child process with Process ID = 1904
    1901                    forked 3th child -> 1905
    1901                    Sending to PID 1902
        1902                Blocked now.
        1902                Child no 1902 is reading now.
                    1905    Created 3th child process with Process ID = 1905
        1902                Output: #include<stdio.h>
        1902                Unblocked now
    1901                    Parent Received SIGUSR2. Child Process with PID 1902 is now free
    1901                    Sending to PID 1903
        1902                Blocked now.
        1902                Child no 1902 is reading now.
    1903                    Blocked now.
    1903                    Child no 1903 is reading now.
        1902                Output: #include<stdio.h>
        1902                Unblocked now
    1901                    Parent Received SIGUSR2. Child Process with PID 1903 is now free
    1901                    Sending to PID 1904
                1904        Blocked now.
            1903            Output: #include<stdio.h>
                1904        Child no 1904 is reading now.
            1903            Unblocked now
            1903            Blocked now.
            1903            Child no 1903 is reading now.
    1901                    Parent Received SIGUSR2. Child Process with PID 1904 is now free
    1901                    Sending to PID 1905
                    1905    Blocked now.
                    1905    Child no 1905 is reading now.
            1903            Output: #include<stdio.h>
                1904        Output: #include<stdio.h>
            1903            Unblocked now
                1904        Unblocked now
                1904        Blocked now.
                1904        Child no 1904 is reading now.
    1901                    Parent Received SIGUSR2. Child Process with PID 1905 is now free
    
    • You could create a log for each process in its own log file. Add a timestamp with at least milliseconds resolution to synchronize all logs later. This way you avoid problems with mixed up output on stdout.

    There are still enough other things to sort out. Have a lot of fun! And good luck!