Search code examples
cunixpipeforkwaitpid

C in Unix: fork, waitpid and pipes


My question is about how to control the process execution with regards to pipes, and specifically implementation of wait / waitpid function.

When I create a pipe for the following command ls | head -3, I do the following:

  • I create the pipe, fork the process creating a child
  • For the child, I call dup2 for stdin, I execute the head -3 command, closing output side of pipe in the child
  • For the parent, I call dup2 for stdout, I execute the ls command, closing the input side of pipe in the parent

My question: based on this discussion, I do need to wait for the child to complete the execution, i.e. the execution of head -3. But how/where can I implement the waitpid function in such a way that it won't conflict with the close[] command?

Based on this great text, the description reads:

If the parent wants to receive data from the child, it should close fd1, and the child should close fd0. If the parent wants to send data to the child, it should close fd0, and the child should close fd1. Since descriptors are shared between the parent and child, we should always be sure to close the end of pipe we aren't concerned with. On a technical note, the EOF will never be returned if the unnecessary ends of the pipe are not explicitly closed.

So the child will have to wait for the parent to complete the pipe, before execution.

I have also seen examples where two forks are made for one process with pipe. Can it be the purpose of avoiding zombie processes, just like the description in my copy of APUE ch.8?

Consider the following code implementation:

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

    int main()
    {
    int pid, status;

    int fd[2];

    char *com1[2];
    char *com2[3];

    com1[0] = "ls";
    com1[1] = NULL;

    com2[0] = "head";
    com2[1] = "-3";
    com2[2] = NULL;

    pipe(fd);

    if((pid = fork()) == -1)
    {
        printf("fork error");
        exit(1);
    }

    if(pid == 0)
    {
        /* Child process closes up output side of pipe */
        dup2(fd[0], 0);
        close(fd[1]);
        execvp(com2[0], com2);
    }

    else
    {

        /* if(waitpid(0, WIFEXITED(&status), 0) != pid)
        {
            printf("wait error");
        }
        is this even needed here? */

        /* Parent process closes up input side of pipe */
        dup2(fd[1], 1);
        close(fd[0]);
        execvp(com1[0], com1);

    }

    exit(0);

}

I must admit that I have looked through many similar questions here on StackExchange. Almost all of them are of specific content however. What I am looking for are explanations of principles and combinations of the functions. Thank you for your time!


Solution

  • You can't both want to wait for a child and exec. So you need to have two child. There is two common way to do this, either have a parent process that creates two direct children and can/must then wait for both; or have a child that itself creates the second one, so that the parent only have to wait for one process termination.

    Option 1 (inherited pipe)

    pipe(pi);
    pid1 = fork();
    if (pid1==0) { // child1
      // make redirections
      exec("head"...);
    }
    pid2 = fork();
    if (pid2==0) { // child2
      // make redirections
      exec("ls"...);
    }
    // parent process
    // close unuseful pipe
    waitpid(pid1...);
    waitpid(pid2...);
    

    Option 2 (pipe is only visible to the concerned processes)

    pid1 = fork();
    if (pid1==0) { // child
      pipe(pi);
      pid2 = fork();
      if (pid2==0) { // gran child
        // make redirections
        exec("ls"...);
      }
      // make redirections
      exec("head"...);
    }
    // parent process
    waitpid(pid1...);