I am trying to implement a code that does ls| grep "pipes"| wc -l. For this I have created 3 child processes and used 2 pipes. Please find the code used:
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//pid_t p;
int pfds1[2],pfds2[2],s;
pipe(pfds1);
pipe(pfds2);
if(!fork()) //first child ls
{
//printf("ls ppid is:%d\n", getppid());
dup2(pfds1[1],1);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("ls", "ls", NULL)==-1)
{
perror("Error in exec line 24\n");
exit(1);
}
}
else{
if(!fork())
{
//printf("grep ppid is:%d\n", getppid());
dup2(pfds1[0],0);
dup2(pfds2[1],1);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("grep","grep","pipes",NULL)==-1)
{
perror("Error in exec line 41\n");
exit(1);
}
}
else{
if(!fork())
{ //printf("wc ppid is:%d\n", getppid());
dup2(pfds2[0],0);
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
if(execlp("wc","wc","-l",NULL)==-1)
{
perror("Error in exec line 56\n");
exit(1);
}
}
else{
close(pfds1[0]);
close(pfds2[0]);
close(pfds1[1]);
close(pfds1[1]);
wait(&s);
wait(&s);
wait(&s);
//printf("parent pid is:%d\n", getpid());
//printf("grandparent pid is:%d\n", getppid());
exit(0);
}
}
}
}
The code works as intended when only 2 wait(&s) are used instead of 3, even though there are three children processes. The current code gets stuck and doesn't finish executing. Could someone pls elaborate on why this is happening?
Thanks
CodePudding user response:
Before all, there's a typo in your code that makes one end of the pipe in the last process to remain open and this makes the block to happen. See below.
The problem is that when you fork your two file descriptors (from which you are closing one, the unused one, in the child, before calling exec) convert in four (two in the child, and also two in the parent, and you don't close one of the file descriptors in the parent process)
if you don't close the descriptors you don't use (either in the parent or in the child process) there will be cases in which both descriptors of the pipe will still be open, and while that happens, the reading process is blocked, waiting for some input (or EOF) to come. when you create a pipe (by using the pipe(2) system call, as soon as you fork(), close the file descriptor of the pipe you are not going to use, because you can block because everything is finished, but you still wait for input to come (being the parent or the child process, depending on how you organized the information to flow)
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
//pid_t p;
int pfds1[2],pfds2[2], s;
pipe(pfds1);
pipe(pfds2);
if(!fork()) { //first child ls
//printf("ls ppid is:%d\n", getppid());
dup2(pfds1[1],1);
here, you have installed the writing end of the pipe as the standard output for ls, but you don't read that.
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
This is ok. All file descriptors will be closed on exit, so no need to close them explicitly.
if(execlp("ls", "ls", NULL)==-1) {
perror("Error in exec line 24\n");
exit(1);
}
} else { // parent
if(!fork()) {
//printf("grep ppid is:%d\n", getppid());
dup2(pfds1[0],0);
dup2(pfds2[1],1);
Here you connect the standard input to the reading side of pipe1 and the standard output of pipe2 to the standard input. grep will wait for input on that pipe.
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
as said before, all pipes will be closed when grep finishes... so no need to explicitly close them here.
if(execlp("grep","grep","pipes",NULL)==-1) {
perror("Error in exec line 41\n");
exit(1);
}
} else { // parent
if(!fork()) {
//printf("wc ppid is:%d\n", getppid());
dup2(pfds2[0],0);
you connect also the input edge of pipe2 to standard input, so wc -l will wait for your input on pipe2.
close(pfds1[0]);
close(pfds2[0]);
close(pfds2[1]);
close(pfds1[1]);
as always, no problem if you don't close because this process will close all of them when it finishes.
if(execlp("wc","wc","-l",NULL)==-1) {
perror("Error in exec line 56\n");
exit(1);
}
} else{ // parent
close(pfds1[0]);
close(pfds2[0]);
close(pfds1[1]);
close(pfds1[1]); /* mistake!!!! */
You have a typo above, you close twice the descriptor pdfs1[1] and pdfs2[1] remains open, so the last process remains waiting for more input on the pipe, and never ends.
wait(&s);
wait(&s);
wait(&s);
//printf("parent pid is:%d\n", getpid());
//printf("grandparent pid is:%d\n", getppid());
exit(0);
}
}
}
}
Edit
The pipe requires that all the descriptors belonging to the write side to be closed, in order to notify the readers that end of file has occured and so, unlock the processes read()ing on them. In the fork, the descriptors of the pipe are implicitly dup()ed, and you again dup() them, when you redirect the input or output, by doing an explicit loop. The children processes just need to close the descriptors they are not going to use (you do well, when you do the dup() to redirect, and then close all the pipe descriptors)
In your case you use the pipes just to connect the children, and no communication is made from the parent... but its pipe descriptors also count to consider is EOF will be signalled to a reader. For a reader to be unlocked from read with EOF, you need to close all the dupped writing descriptors. This includes the ones of the parent, and the ones of the other children.
