coperating-systemforkexecdup

intercommunicating using stdin and stdout between forked C processes


What I am trying to achieve is, creating 2 child processes say "left" and "right", left child executes a process called ./left that gets 2 inputs from stdin and sums it, then forward it to its parent process by stdout. Then right child again executes a process that again sums the 2 stdin inputs and forwards it to its parent. In code below, left result successfully handled and printed via stderr but right result is a garbage value, I couldnt figure out why.

main.c

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


int main() {

    //###### 0 is read end, 1 is write end

    int p2c_l[2]; // Parent to child left pipe, p2c[0] is read end, p2c[1] is write end from parent perspective
    int c2p_l[2]; // Child to parent left pipe

    // Create pipes
    pipe(p2c_l);
    pipe(c2p_l);
    
    int pid = fork();
    if (pid == 0) { //Left child
        fprintf(stderr, "Left child process\n");
        // close right child pipes

        close(c2p_l[0]);
        close(p2c_l[1]);
        
        dup2(p2c_l[0], 0);
        dup2(c2p_l[1], 1);

        char* args[] = {"./left", NULL};
        execvp(args[0], args);
        fprintf(stderr, "Left child process failed\n");
        exit(EXIT_FAILURE);
    } else { // Parent process
        close(c2p_l[1]);
        close(p2c_l[0]);

        // dup2(p2c_l[1], 1);
        // dup2(c2p_l[0], 0);

        fprintf(stderr, "Parent process after first fork\n");

        int n1, n2;
        n1 = 5;
        n2 = 10;
        write(p2c_l[1], &n1, sizeof(n1));
        write(p2c_l[1], &n2, sizeof(n2));

        int resultFromLeftChild;
        read(c2p_l[0], &resultFromLeftChild, sizeof(resultFromLeftChild));

        fprintf(stderr, "Left child result: %d\n", resultFromLeftChild);

        int p2c_r[2]; // Parent to child right pipe
        int c2p_r[2]; // Child to parent right pipe

        int pid2 = fork();
        if (pid2 == 0) { //Right child
            fprintf(stderr, "Right child process\n");
            // close left child pipes

            close(p2c_r[0]);
            close(c2p_r[1]);
            
            //make pc2_r[0] and c2p_r[1] as stdin and stdout
            dup2(p2c_r[0], 0);
            dup2(c2p_r[1], 1);

            char* args[] = {"./right", NULL};
            execvp(args[0], args);

            fprintf(stderr, "Right child process failed\n");
            exit(EXIT_FAILURE);
        
        } else { // Parent process
            close(p2c_r[1]);
            close(c2p_r[0]);

            // dup2(p2c_r[1], 1);
            // dup2(c2p_r[0], 0);

            fprintf(stderr, "Parent process after second fork\n");

            int n1, n2;
            n1 = resultFromLeftChild;
            n2 = 10;
            // printf("%d\n", n1);
            // printf("%d\n", n2);
            write(p2c_r[1], &n1, sizeof(n1));
            write(p2c_r[1], &n2, sizeof(n2));

            int resultFromRightChild;
            read(c2p_r[0], &resultFromRightChild, sizeof(resultFromRightChild));

            fprintf(stderr, "Right child result: %d\n", resultFromRightChild);

            int result = resultFromLeftChild + resultFromRightChild;
            fprintf(stderr, "Result: %d\n", result);

        }
    }

    return 0;
}

left.c and right.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    int num1, num2;
     if (argc != 1) {
        printf("Usage: %s \n", argv[0]);
        return 1; // Error code for incorrect usage
    }
    scanf("%d", &num1);
    scanf("%d", &num2);
    // Calculate the addition
    int result = num1 + num2;
    //printf("Inputs: %d %d \n", num1, num2);
    // Print the result
    printf("%d\n", result);

    return 0; // Successful execution
}

I expected to get Right child result: 25 as output but i get Right child result: {garbage value} as output.


Solution

  • One part of the problem is that you're not closing enough pipe descriptors.

    Rule of thumb: If you dup2() one end of a pipe to standard input or standard output, close both of the original file descriptors from pipe() as soon as possible. In particular, that means before using any of the exec*() family of functions. The rule also applies with either dup() or fcntl() with F_DUPFD.

    Another problem in the current revision of the code (revision 4) is that the children expect to read ASCII numbers using scanf() but the parent sends binary numbers.

    The biggest problem is that you don't create the pipes for the child process. A secondary problem is that you close the wrong ends of the right-child pipes in the parent process.

    Fixing these issues leads to code like this:

    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    int main(void)
    {
        // ###### 0 is read end, 1 is write end
    
        int p2c_l[2]; // Parent to child left pipe, p2c[0] is read end, p2c[1] is write end from parent perspective
        int c2p_l[2]; // Child to parent left pipe
    
        // Create pipes
        if (pipe(p2c_l) < 0 || pipe(c2p_l) < 0)
        {
            fprintf(stderr, "Failed to create pipes for left child: %d %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        int resultFromLeftChild = -1;
        int pid1 = fork();
        if (pid1 < 0)
        {
            fprintf(stderr, "Fork 1 failed: %d %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        if (pid1 == 0)   // Left child
        {
            fprintf(stderr, "Left child process\n");
            dup2(p2c_l[0], 0);  // STDIN_FILENO?
            dup2(c2p_l[1], 1);  // STDOUT_FILENO?
    
            close(c2p_l[0]);
            close(c2p_l[1]);
            close(p2c_l[0]);
            close(p2c_l[1]);
    
            char *args[] = {"./left", NULL};
            execvp(args[0], args);
            fprintf(stderr, "Left child process failed: %d %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        else     // Parent process
        {
            close(c2p_l[1]);
            close(p2c_l[0]);
    
            fprintf(stderr, "Parent process after first fork\n");
    
            int n1, n2;
            n1 = 5;
            n2 = 10;
            char buffer[32];
            snprintf(buffer, sizeof(buffer), "%d\n", n1);
            write(p2c_l[1], buffer, strlen(buffer));
            snprintf(buffer, sizeof(buffer), "%d\n", n2);
            write(p2c_l[1], buffer, strlen(buffer));
    
            int nbytes = read(c2p_l[0], buffer, sizeof(buffer));
            if (nbytes <= 0)
                fprintf(stderr, "Failed to read from child 1\n");
            else
            {
                fprintf(stderr, "Left child result: %s\n", buffer);
                resultFromLeftChild = atoi(buffer);
            }
            close(p2c_l[1]);
            close(c2p_l[0]);
    
            int corpse;
            int status;
            while ((corpse = wait(&status)) > 0)
                printf("PID %d exited with status 0x%.4X\n", corpse, status);
        }
    
        /* Now repeat the code for the right child - this is where we use functions! */
        int p2c_r[2]; // Parent to child right pipe
        int c2p_r[2]; // Child to parent right pipe
    
        // Create pipes
        if (pipe(p2c_r) < 0 || pipe(c2p_r) < 0)
        {
            fprintf(stderr, "Failed to create pipes for right child: %d %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        int pid2 = fork();
        if (pid2 < 0)
        {
            fprintf(stderr, "Fork 1 failed: %d %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        if (pid2 == 0)   // Right child
        {
            fprintf(stderr, "Right child process\n");
            dup2(p2c_r[0], 0);
            dup2(c2p_r[1], 1);
            close(p2c_r[0]);
            close(p2c_r[1]);
            close(c2p_r[0]);
            close(c2p_r[1]);
            char *args[] = {"./right", NULL};
            execvp(args[0], args);
            fprintf(stderr, "Right child process failed: %d %s\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        else     // Parent process
        {
            close(p2c_r[0]);
            close(c2p_r[1]);
            fprintf(stderr, "Parent process after second fork\n");
    
            int n1, n2;
            n1 = resultFromLeftChild;
            n2 = 10;
            char buffer[32];
            snprintf(buffer, sizeof(buffer), "%d\n", n1);
            write(p2c_r[1], buffer, strlen(buffer));
            snprintf(buffer, sizeof(buffer), "%d\n", n2);
            write(p2c_r[1], buffer, strlen(buffer));
    
            int nbytes = read(c2p_r[0], buffer, sizeof(buffer));
            if (nbytes <= 0)
                fprintf(stderr, "Failed to read from child 2\n");
            else
                fprintf(stderr, "Right child result: %s\n", buffer);
            close(p2c_r[1]);
            close(c2p_r[0]);
    
            int corpse;
            int status;
            while ((corpse = wait(&status)) > 0)
                printf("PID %d exited with status 0x%.4X\n", corpse, status);
        }
    
        return 0;
    }
    

    Sample output:

    Parent process after first fork
    Left child process
    Left child result: 15
    
    PID 57771 exited with status 0x0000
    Parent process after second fork
    Right child process
    Right child result: 25
    
    PID 57772 exited with status 0x0000
    

    Note that this code is still a bit sloppy about checking that read and write operations succeeded. It's a good idea, especially when things aren't working yet, to systematically check every operation that can fail and report failures. That means checking calls that open files or create/modify file descriptors, the read and write operations, and so on. The data read off the pipe is not a null-terminated string; the code should fix things up so that it does null-terminate the data off the pipe.

    A more thorough reworking of the code would put the code for each child into a function (a common function with arguments to identify what's to happen) that is called twice with appropriate arguments. That code could look like this (83 lines vs 139 in prior version):

    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    static int invoke_child(char *name, int v1, int v2)
    {
        int p2c[2];
        int c2p[2];
    
        if (pipe(p2c) < 0 || pipe(c2p) < 0)
        {
            fprintf(stderr, "Failed to create pipes for %s child: %d %s\n", name, errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        int resultFromChild = -1;
        int pid1 = fork();
        if (pid1 < 0)
        {
            fprintf(stderr, "Fork for %s child failed: %d %s\n", name, errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        if (pid1 == 0)
        {
            fprintf(stderr, "The %s child process (PID = %d)\n", name, getpid());
            dup2(p2c[0], STDIN_FILENO);
            dup2(c2p[1], STDOUT_FILENO);
    
            close(c2p[0]);
            close(c2p[1]);
            close(p2c[0]);
            close(p2c[1]);
    
            char *args[] = { name, NULL };
            execvp(args[0], args);
            fprintf(stderr, "The %s child process failed: %d %s\n", name, errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        else
        {
            close(c2p[1]);
            close(p2c[0]);
    
            fprintf(stderr, "Parent process after first fork (%s child PID = %d)\n", name, pid1);
    
            char buffer[32];
            snprintf(buffer, sizeof(buffer), "%d\n", v1);
            write(p2c[1], buffer, strlen(buffer));
            snprintf(buffer, sizeof(buffer), "%d\n", v2);
            write(p2c[1], buffer, strlen(buffer));
    
            int nbytes = read(c2p[0], buffer, sizeof(buffer));
            if (nbytes <= 0)
                fprintf(stderr, "Failed to read from child 1\n");
            else
            {
                /* The buffer should include a newline */
                fprintf(stderr, "The %s child result: %s", name, buffer);
                resultFromChild = atoi(buffer);
            }
            close(p2c[1]);
            close(c2p[0]);
    
            int corpse;
            int status;
            while ((corpse = wait(&status)) > 0)
                printf("PID %d (%s child) exited with status 0x%.4X\n", corpse, name, status);
        }
        return resultFromChild;
    }
    
    int main(void)
    {
        char name_1[] = "left";
        char name_2[] = "right";
        int result_1 = invoke_child(name_1, 5, 10);
        int result_2 = invoke_child(name_2, result_1, 10);
        printf("Results: %s = %d, %s = %d\n", name_1, result_1, name_2, result_2);
        return 0;
    }
    

    Sample output:

    Parent process after first fork (left child PID = 58514)
    The left child process (PID = 58514)
    The left child result: 15
    PID 58514 (left child) exited with status 0x0000
    Parent process after first fork (right child PID = 58515)
    The right child process (PID = 58515)
    The right child result: 25
    PID 58515 (right child) exited with status 0x0000
    Results: left = 15, right = 25
    

    The error checking still needs fixing.