Using the pipe() system call
Asked Answered
A

4

5

I've been trying to use the pipe() system call to create a shell that supports piping (with an arbitrary number of commands).

Unfortunately, I haven't had much luck using pipe(). After spending a few days looking at various online resources, I decided to put together an oversimplified program that has the same effect as executing ls | sort to see if I could even get a pipe to work for two sibling, child processes. Here's the code:

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

void run(char *cmd) {
   char *args[2];
   args[0] = cmd;
   args[1] = NULL;

   execvp(cmd, args);
}

int main(void) {
    int filedes[2];
    pipe(filedes);

    pid_t pid_a, pid_b;

    if (!(pid_a = fork())) {
        dup2(filedes[1], 1);
        run("ls");
    }

    if (!(pid_b = fork())) {
        dup2(filedes[0], 0);
        run("sort");
    }

    waitpid(pid_a, NULL, 0);
    waitpid(pid_b, NULL, 0);

    return 0;
}

The pipe is created in the parent and I know that after the execvp() call, each child process inherits the file descriptors that pipe() creates in the parent. For the ls process, I'm using dup2() to redirect its standard out (1) to the write-end of the pipe and for the sort process, standard in (0) is being redirected to the read-end of the pipe.

Finally, I wait for both processes to finish before exiting.

My intuition tells me this should work, but it doesn't!

Any suggestions?

Arc answered 7/10, 2011 at 20:46 Comment(2)
What does happen? Can you suggest an explanation for why it is happening?Bibber
Yes, the parent process blocks and I suspect it's due to one of more of the child processes never finishing because of not receiving an EOF character on their standard input stream.Arc
B
4

Before calling waitpid in the parent process you have to close all file descriptors from the pipe that you don't need. These are:

  • filedes[0] in pid_a
  • filedes[1] in pid_b
  • both filedes[0] and filedes[1] in the parent process

You should also check that pipe() and fork() didn't return -1, which means an error happened.

Bole answered 7/10, 2011 at 21:10 Comment(3)
In the real thing, I check for errors--I left them out here, so the example could be simple.Arc
Great, this works! I have two other questions: (1) I'm assuming that a file descriptor is truly closed when all processes that refer to it call close(), not just one? (2) We only need to close the read end of pid_a because it's the first process in the "chain" and we close the write-end of pid_b because it's the last process in the chain, but if there were processes in the middle, they would make use of both ends of the pipe, so neither end would have to be closed for those processes, right?Arc
Your statement (1) is correct. Statement (2) is a little more complicated. Whenever you fork(), all file descriptors are duplicated, and then they have two "names" (which are actually small numbers). After you fork, you usually duplicate the file descriptors again (to stdin and stdout). At that point you don't need the original file descriptors anymore and close() them.Bole
A
6

You have to close the pipes you're not using. at least sort will read from its stdin until stdin is closed.

In this case, it's stdin is never closed, as you still have 2 open filedescriptors for it.

  • filedes[0] in the ls child (this likely gets closed when ls finishes)
  • filedes[0] in the parent program (this never gets closed as you waitpid() for sort to end, but it never will because the parent keeps its stdin open)

Change your program to

if (!(pid_a = fork())) {
    dup2(filedes[1], 1);
    closepipes(filedes);
    run("ls");
}

if (!(pid_b = fork())) {
    dup2(filedes[0], 0);
    closepipes(filedes);
    run("sort");
}
closepipe(filedes);
waitpid(pid_a, NULL, 0);
waitpid(pid_b, NULL, 0);

where closepipes is something like

void closepipes(int *fds)
{ 
 close(fds[0]);
 close(fds[1]);
}
Aromatize answered 7/10, 2011 at 21:3 Comment(0)
B
4

Before calling waitpid in the parent process you have to close all file descriptors from the pipe that you don't need. These are:

  • filedes[0] in pid_a
  • filedes[1] in pid_b
  • both filedes[0] and filedes[1] in the parent process

You should also check that pipe() and fork() didn't return -1, which means an error happened.

Bole answered 7/10, 2011 at 21:10 Comment(3)
In the real thing, I check for errors--I left them out here, so the example could be simple.Arc
Great, this works! I have two other questions: (1) I'm assuming that a file descriptor is truly closed when all processes that refer to it call close(), not just one? (2) We only need to close the read end of pid_a because it's the first process in the "chain" and we close the write-end of pid_b because it's the last process in the chain, but if there were processes in the middle, they would make use of both ends of the pipe, so neither end would have to be closed for those processes, right?Arc
Your statement (1) is correct. Statement (2) is a little more complicated. Whenever you fork(), all file descriptors are duplicated, and then they have two "names" (which are actually small numbers). After you fork, you usually duplicate the file descriptors again (to stdin and stdout). At that point you don't need the original file descriptors anymore and close() them.Bole
M
3

You need to close (at least) the writing end of the pipe in the parent process. Otherwise, the reading end of the pipe will never read EOF status, and sort will never finish.

Markowitz answered 7/10, 2011 at 20:59 Comment(2)
I tried closing the writing end before doing any of the forks (close(filedes[1])) and now my program exits, however, it doesn't seem like the second child process is receiving anything on the read-end of the pipe. I tried replacing sort with wc and the results are 0 0 0 which suggests that the second process is not receiving anything on the read-end.Arc
... and it seems like I should have closed the the ends of the pipe in the parent before the waiting, not the forking. And based on the suggestions below, I also closed the unneeded ends in the child processes and it works now!Arc
S
1

This code working properly...

    #include <sys/wait.h>
    #include <unistd.h>
    using namespace std;

    void run(char *cmd) {
       char *args[2];
       args[0] = cmd;
       args[1] = NULL;

       execvp(cmd, args);
    }
    void closepipe(int *fds)
    { 
     close(fds[0]);
     close(fds[1]);
    }

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

        int filedes[2];
        pipe(filedes);
        char lss[]="ls";
        char sorts[]="sort";
        pid_t pid_a, pid_b;
     chdir(argv[1]);
    if (!(pid_a = fork())) {
        dup2(filedes[1], 1);
        closepipe(filedes);
        run(lss);
    }

    if (!(pid_b = fork())) {
        dup2(filedes[0], 0);
        closepipe(filedes);
        run(sorts);
    }
    closepipe(filedes);
    waitpid(pid_a, NULL, 0);
    waitpid(pid_b, NULL, 0);

        return 0;
    }
Strickle answered 6/9, 2012 at 13:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.