Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pipe, Fork, and Exec - Two Way Communication Between Parent and Child Process

Tags:

c

fork

pipe

exec

An assignment in my Operating Systems class requires me to build a binary process tree by recursively calling exec on the same program. The goal is to split some arbitrary task into separate processes. The parent should communicate with the children, and the children with the parent only via unnamed pipes. The idea is that the parent sends each child half of the work and this continues recursively until a base case is met where the length of the string being passed to each child is <= 2. The child then processes this data and sends the results back to the parent via pipes.

To get a better understanding of how two way communication works with pipes in c I created the following simple program before moving on to the actual assignment. The parent never reads the data from the child process though. I'm expecting the output...

in parent | message received: test

Instead, when I print I get...

in parent | message received:

It seems that buff is empty and not reading from the child process. Can someone please explain what I'm doing wrong and/or the standard way of

  1. writing to exec'd child from parent
  2. reading from parent in exec'd child
  3. writing back to parent from exec'd child
  4. reading from exec'd child in parent

I am required to use exec(), pipe(), fork(). Thank you.

/**
 * *********************************
 * two_way_pipes.c
 * *********************************
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <sys/time.h> 
#include <sys/types.h> 
#include <unistd.h>

#define PARENT_READ read_pipe[0]
#define PARENT_WRITE write_pipe[1]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ  write_pipe[0]

#define DEBUGGING 1

int main(int argc, char **argv) {
    char buff[5];

    // in the child process that was exec'd on the orginal call to two_way_pipes
    if(argc == 2) {
        read(STDIN_FILENO, buff, 4); // this should read "test" from stdin
        buff[4] = '\0';
        fprintf(stdout, "%s\n", buff); // this should right "test" to stdout and be read by the parent process
    // int the root process, the original call to two_way_pipes with no args
    } else {
        int pid;
        int read_pipe[2];
        int write_pipe[2];

        pipe(read_pipe);
        pipe(write_pipe);

        pid = fork();

        // parent process
        if(pid > 0) {
            close(CHILD_READ);
            close(CHILD_WRITE);

            write(PARENT_WRITE, "test", 4); // attempting to write this to the child

            struct timeval tv;
            fd_set readfds;
            tv.tv_sec = 10;
            tv.tv_usec = 0;
            FD_ZERO(&readfds);
            FD_SET(PARENT_READ, &readfds);
            select(PARENT_READ + 1, &readfds, NULL, NULL, &tv);

            if(FD_ISSET(PARENT_READ, &readfds)) {
                read(PARENT_READ, buff, 4); // should read "test" which was written by the child to stdout
                buff[4] = '\0';
                close(PARENT_READ);
                close(PARENT_WRITE);
                fprintf(stderr, "in parent | message received: %s\n", buff);  // "test" is not in buff
            }

        // child process
        } else if(pid == 0) {
            close(PARENT_READ);
            close(PARENT_WRITE);

            dup2(CHILD_READ, STDIN_FILENO);
            dup2(CHILD_WRITE, STDOUT_FILENO);
            close(CHILD_READ);
            close(CHILD_WRITE);

            char *argv2[] = {"some random arg to make sure that argc == 2 in the child", NULL};
            execvp("two_way_pipes", argv2);
            _exit(0);
        // error forking child process
        } else {
            fprintf(stderr, "error forking the child\n");
        }
    }
}

Update

Based on Jonathon's answer I modified the arg2 array being passed into execvp to...

char *argv2[] = {"two_way_pipes", "1", NULL};
execvp("two_way_pipes", argv2);

This didn't fix the issue. The parent still wasn't able to read "test" back from the client. However, in response to Jonathon's answer and William's comment I started tweaking my exec call and for some reason changing it to the line show below worked.

execl("two_way_pipes", "two_way_pipes", "1", NULL);

I'll gladly accept any answers explaining why the execvp call wouldn't work but the execl call did.

like image 648
Anthony Jack Avatar asked Oct 04 '13 21:10

Anthony Jack


1 Answers

Besides the issue mentioned by Jonathon Reinhart, most probably the call to execv() fails.

To test this modify these lines

execvp("two_way_pipes", argv2);
_exit(0);

to be

...
#include <errno.h>
...


execvp("two_way_pipes", argv2); /* On sucess exec*() functions never return. */
perror("execvp() failed); /* Getting here means execvp() failed. */
_exit(errno);

Expect to receive

execvp() failed: No such file or directory

To fix this change

execvp("two_way_pipes", argv2);

to be

execvp("./two_way_pipes", argv2);

Also if the child was not exec*()ed then this line

read(PARENT_READ, buff, 4); // should read "test" which was written by the child to stdout

fails and in turn buff is not initialised and therefore this line

fprintf(stderr, "in parent | message received: %s\n", buff);  

provokes undefined behaviour.

To fix this at least properly initialise buff by changing

char buff[5];

to be

char buff[5] = "";
like image 73
alk Avatar answered Oct 06 '22 19:10

alk