I'm learning to use pipes and following along with this code on pipes. The program makes two child processes using fork. The first child runs 'ls' command and outputs to pipe1. The second reads from pipe1 runs 'wc' and outputs to stdout.
I'm just trying to add a third process in the middle that reads from pipe1 and outputs to pipe2. Basically what I'm trying to do
ls | cat | wc -l
What I'm trying to do:
(ls)stdout -> pipe1 -> stdin(cat)stdout-> stdin(wc -l) -> stdout
Nothing ever prints to stdout and the program never exits.
Here's my code with the changes for process #3
int
main(int argc, char *argv[])
{
int pfd[2]; /* Pipe file descriptors */
int pfd2[2];
if (pipe(pfd) == -1) /* Create pipe */
perror("pipe");
if (pipe(pfd2) == -1) /* Create pipe */
perror("pipe");
/*
Fork process 1 and exec ls command
write to pfd[1], close pfd[0]
*/
switch (fork()) {
case -1:
perror("fork");
case 0:
if (close(pfd[0]) == -1)
perror("close 1");
// dup stdout on pfd[1]
if (pfd[1] != STDOUT_FILENO) {
if (dup2(pfd[1], STDOUT_FILENO) == -1)
perror("dup2 2");
if (close(pfd[1]) == -1)
perror("close 4");
}
execlp("ls", "ls", (char *) NULL);
perror("execlp ls");
default:
break;
}
/*
* Fork process 2 and exec wc command
read from pfd[0], close pfd[1]
write to pfd[1], close pfd2[0]
*/
switch (fork()) {
case -1:
perror("fork");
case 0:
// read from pfd[0]
if (close(pfd[1]) == -1)
perror("close 3");
if (pfd[0] != STDIN_FILENO) {
if (dup2(pfd[0], STDIN_FILENO) == -1)
perror("dup2 2");
if (close(pfd[0]) == -1)
perror("close 4");
}
if (pfd2[1] != STDOUT_FILENO) {
if (dup2(pfd2[1], STDOUT_FILENO) == -1)
perror("dup2 2");
if (close(pfd2[1]) == -1)
perror("close 4");
}
execlp("cat", "cat", (char *) NULL);
perror("execlp cat");
default:
break;
}
/*
* Fork process 3
*/
switch (fork()) {
case -1:
perror("fork");
case 0:
if (close(pfd2[1]) == -1)
perror("close 3");
if (pfd2[0] != STDIN_FILENO) {
if (dup2(pfd2[0], STDIN_FILENO) == -1)
perror("dup2 2");
if (close(pfd2[0]) == -1)
perror("close 4");
}
execlp("wc", "wc", "-l", (char *) NULL);
perror("execlp wc");
default:
break;
}
/* Parent closes unused file descriptors for pipe, and waits for children */
if (close(pfd[0]) == -1)
perror("close 5");
if (close(pfd[1]) == -1)
perror("close 6");
if (close(pfd2[0]) == -1)
perror("close 5");
if (close(pfd2[1]) == -1)
perror("close 6");
if (wait(NULL) == -1)
perror("wait 1");
if (wait(NULL) == -1)
perror("wait 2");
if (wait(NULL) == -1)
perror("wait 3");
exit(EXIT_SUCCESS);
}
The problem is that you did not close
pfd[1]
in process 3, addclose(pfd[1]);
after case 0 in that process 3 will fix it.In process 3, that
cat
will read frompfd[0]
, however there are fourpfd[1]
in those processes:process 0
this is the main process,
pfd[1]
in this process will be closed by that close beforewait()
.process 1
after
ls
finished,pfd[1]
in this process will be closed automatically by the operating system.process 2
pfd[1]
has been closed before executingcat
.process 3
pfd[1]
is open in this process whilewc
is running, and this is what happened at that moment:cat
tries to readpfd[0]
for data frompfd[1]
wc
tries to readpfd2[0]
for data frompfd2[1]
pdf[1]
still open in process 3, and nothing will be written to it, reading frompfd[0]
in process 2 (cat) will wait forevercat
in process 3 still alive, reading frompfd2[0]
in process 3 (wc) will wait (forever)As you can see, you have a deadlock between process 2 (cat) and process 3 (wc) because of file descriptor leak. To break this deadlock, you just need to close
pfd[1]
in process 3 before you runwc
, after that:cat
in process 2 will exit afterls
in process 1 exits, because there is nothing left for it (cat) to readcat
in process 2 exits,wc
in process 3 will also exit, because there is nothing left for it (wc) to readIt is possible that there are more than one write ends for the read end of a pipe, unless all these write ends are closed, end-of-file will not be delivered to the read end, and the reader will just wait for more data to come. If there is nothing to come, that reader will wait forever.