Skip to content
Advertisement

How to exit while loop when read() blocks

In a problem given by university we have to pipe from a parent process(P1) to its child P2, and afterwards P2 must pipe to another child of P1, the other child is P3. Both P2 and P3 are to be written in c and made into executable files. They will then by execed by child processes in P1.

P1 writes the numbers 1 to 10000 to stdout, P2 reads them through its stdin, removes the numbers divisible by 2, and writes the result to its stdout. P3 reads those numbers through its stdin, filters out the results that are divisible by 3, and writes everything to a file.

I have managed to implement absolutely everything, but my child processes do not end. The reason for this, I believe, is that I have used the following method to read the input in each child:

while(n=read(0, &i, sizeof(i))>0)

The problem here, as I understand it, is that read blocks when it doesn’t get any bytes. As P1 writes the 10000 numbers using:

for(i=1; i<=10000; i++){
        write(1, &i, sizeof(i));
    }

Neither child process ever has any reason to believe that no more data is coming its way. Therefore, each read simply blocks waiting for a byte that will never come.

Can anyone suggest a way to overcome this roadblock?

The code of each process is as follows:

Parent:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
#include<sys/wait.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
        // pipe to send input string from parent
        // Child prints to file 
        int fd1[2];  // Used to store two ends of first pipe
        int fd2[2];  // Used to store two ends of second pipe

    //variables
    int n, i, status1, status2;
    char *args1[]={"./Div2",NULL};
    char *args2[]={"./Div3",NULL};


    //child process ids
        pid_t pid1, pid2;

    //open pipe 1
        if (pipe(fd1)==-1)
        {
            fprintf(stderr, "Pipe 1 Failed" );
            return 1;
        }
    //open pipe 2
    if (pipe(fd2)==-1)
        {
            fprintf(stderr, "Pipe 2 Failed" );
            return 1;
        }

    //create child 1    
    pid1 = fork();  
    if(pid1<0){
        printf("Error creating child1n");
        return(1);
    }
    if(pid1==0){ //child1
        if(close(fd1[1])<0){ //child does not write to pipe 1
            error();
        }
        if(close(fd2[0])<0){ //child does not read from pipe 2
            error();
        }
        dup2(fd1[0], 0); //redirect stdin
        dup2(fd2[1], 1); //redirect stdout
            execvp(args1[0],args1);
        if(close(fd1[0])<0){ //close used pipe
            error();
        }
        if(close(fd2[0])<0){ //close used pipe
            error();
        }
        exit(0);
    }

    pid2=fork();
    if(pid2<0){
        printf("Error creating child2n");
        return(1);
    }
    if(pid2==0){ //child2
        if(close(fd1[0])<0){ //child does not use pipe 1
            error();
        }
        if(close(fd1[1])<0){ 
            error();
        }
        if(close(fd2[1])<0){ //child does not write to pipe 2
            error();
        }
        dup2(fd2[0], 0); //redirect stdin
        execvp(args2[0], args2);
        if(close(fd2[0])<0){ //close pipe after use
            error();
        }
        exit(0);
    }


    //parent
    //parent doesn't read from the pipe
    if(close(fd1[0])<0){
    error();
    }
    if(close(fd2[0])<0){
    error();
    }
    if(close(fd2[1])<0){
    error();
    }
    dup2(fd1[1], 1); //redirect stdout
    for(i=1; i<=10000; i++){
        write(1, &i, sizeof(i));
    }
    if(close(fd1[1])<0){
    error();
    }
    int returnedPID1=waitpid(pid1, &status1, 0);
    if(returnedPID1==pid1){
    printf("Parent waited for child as predictedn");
    }
    int returnedPID2=waitpid(pid2, &status2, 0);
    if(returnedPID2==pid2){
    printf("Parent waited for child as predictedn");
    }
    _exit(0);

}

P2 (includes excluded)

int main()
{
    int n;
    int i;
    while((n=read(0, &i, 4))>0){
        if((i%2)!=0){
            write(1, &i, sizeof(i));
        }   
    }
    return;
}

P3

int main()
{
    int n;
    int i;
    char tmp[12] = {0x0};
    char *arg[]= {"/home/eric/Documents/pr3/test.txt"};
    int fp = open(arg[0], O_WRONLY|O_CREAT|O_TRUNC, 0666);
        if(fp<0){
            printf("Error opening filen");
            _exit(1);
        }

    while((n=read(0, &i, 4))>0){
        if((i%3)!=0){
            sprintf(tmp,"%11d", i);
            write(fp, tmp, strlen(tmp));
        }   
    }
    return;
}

Thanks guys.

Advertisement

Answer

Except in case of error, exec never returns. So when you write:

    execvp(args1[0],args1);
    if(close(fd1[0])<0){ //close used pipe
        error();
    }

you are wrong to expect the file descriptor to be closed. Close them before you exec. They are getting left open. Although in your particular case the problem is that the parent never closes fd 1. The parent has two file descriptors that are writing into the pipe ( fd[1] and 1), and you need to close them both before the child reading the pipe will finish.

Advertisement