0
votes

I'm practicing with fork() and pipes and I have a question: why does the second child process get stuck reading the pipe if I don't close the first pipe (first child - second child) in the parent process?

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

void checkArgs(int n){
    if(n!=1){
        perror("Wrong parameters number");
        exit(-1);
    }
}

int isAConsonant(char c){
    char vowels[5]={'a','e','i','o','u'};
    int i=0,toRet=1;
    for(i=0;i<5 && toRet;i++){
        if(c==vowels[i]){
            toRet=0;
        }
    }
    return toRet;
}

int main(int argc,char** argv){
    checkArgs(argc-1);
    int pipe1[2],pipe2[2],sync[2];
    int pid1,pid2;

    if(pipe(pipe1)<0 || pipe(pipe2)<0 || pipe(sync)<0){
        perror("Error opening pipes");
        exit(-1);
    }

    if((pid1=fork())<0){
        perror("error during fork");
        exit(-1);
    }

    if(pid1==0){ //first child
        close(pipe1[0]);
        close(sync[0]);
        char buf[3];
        int fDes=open(argv[1],O_RDONLY);

        if(fDes<0){
            perror("Error opening file");
            exit(-1);
        }

        write(sync[1],"N",1);

        while(read(fDes,buf,3)==3){
            write(sync[1],"N",1);
            write(pipe1[1],buf,3);
        }

        write(sync[1],"S",1);
        close(sync[1]);
        close(fDes);
        close(pipe1[1]);
    }
    else{

        if((pid2=fork())<0){
            perror("error during fork");
            exit(-1);
        }
        if(pid2==0){ //second child
            close(pipe2[0]);
            close(pipe1[1]);
            char buf[3];

            while(read(pipe1[0],buf,3)==3){
                if(isAConsonant(buf[0])){
                    write(pipe2[1],buf,3);
                }
            }
            close(pipe2[1]);
            close(pipe1[0]);
        }
        else{   //parent
            close(pipe2[1]);
            close(sync[1]);

            //it does not work if not executed
            //close(pipe1[1]);
            //close(pipe1[0]);

            char toStart;
            read(sync[0],&toStart,1);
            while(toStart!='S'){
                read(sync[0],&toStart,1);
            }

            int fDes=open(argv[1],O_RDWR|O_APPEND,S_IRUSR|S_IWUSR);
            if(fDes<0){
                perror("Error opening file");
                exit(-1);
            }

            char buf[3];

            while(read(pipe2[0],buf,3)==3){
                write(fDes,buf,3);
                write(fDes," ",1);
            }

            close(pipe2[0]);
            close(sync[0]);
            close(fDes);
        }
    }
}

These calls terminate the program correctly

close(pipe1[1]);
close(pipe1[0]);

inputF file:

abcdefghilmnopqrstuvz

Strace screen for executing code without close(pipe1[0]) and close(pipe1[1]) in the parent process:

strace for the parent process

execve("./pipe", ["./pipe", "inputF"], 0x7ffc458bc820 /* 54 vars */) = 0
brk(NULL)                               = 0x55ed3c675000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (File o directory non esistente)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=71886, ...}) = 0
mmap(NULL, 71886, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7d0b895000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200l\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2000480, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b893000
mmap(NULL, 2008696, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7d0b6a8000
mmap(0x7f7d0b6cd000, 1519616, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f7d0b6cd000
mmap(0x7f7d0b840000, 299008, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x198000) = 0x7f7d0b840000
mmap(0x7f7d0b889000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e0000) = 0x7f7d0b889000
mmap(0x7f7d0b88f000, 13944, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b88f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f7d0b894500) = 0
mprotect(0x7f7d0b889000, 12288, PROT_READ) = 0
mprotect(0x55ed3a7d0000, 4096, PROT_READ) = 0
mprotect(0x7f7d0b8d1000, 4096, PROT_READ) = 0
munmap(0x7f7d0b895000, 71886)           = 0
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
pipe([7, 8])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11194
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11195
close(6)                                = 0
close(8)                                = 0
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "S", 1)                         = 1
openat(AT_FDCWD, "inputF", O_RDWR|O_APPEND) = 6
read(5, "def", 3)                       = 3
write(6, "def", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "ghi", 3)                       = 3
write(6, "ghi", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "lmn", 3)                       = 3
write(6, "lmn", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "rst", 3)                       = 3
write(6, "rst", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=11194, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++

strace for the first child process

execve("./pipe", ["./pipe", "inputF"], 0x7ffc458bc820 /* 54 vars */) = 0
brk(NULL)                               = 0x55ed3c675000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (File o directory non esistente)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=71886, ...}) = 0
mmap(NULL, 71886, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7d0b895000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200l\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2000480, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b893000
mmap(NULL, 2008696, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7d0b6a8000
mmap(0x7f7d0b6cd000, 1519616, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f7d0b6cd000
mmap(0x7f7d0b840000, 299008, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x198000) = 0x7f7d0b840000
mmap(0x7f7d0b889000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e0000) = 0x7f7d0b889000
mmap(0x7f7d0b88f000, 13944, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b88f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f7d0b894500) = 0
mprotect(0x7f7d0b889000, 12288, PROT_READ) = 0
mprotect(0x55ed3a7d0000, 4096, PROT_READ) = 0
mprotect(0x7f7d0b8d1000, 4096, PROT_READ) = 0
munmap(0x7f7d0b895000, 71886)           = 0
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
pipe([7, 8])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11194
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11195
close(6)                                = 0
close(8)                                = 0
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "S", 1)                         = 1
openat(AT_FDCWD, "inputF", O_RDWR|O_APPEND) = 6
read(5, "def", 3)                       = 3
write(6, "def", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "ghi", 3)                       = 3
write(6, "ghi", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "lmn", 3)                       = 3
write(6, "lmn", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "rst", 3)                       = 3
write(6, "rst", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=11194, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++
pepys@pepys:~/Scrivania/lso/C/pipe/prova strana$ ^C
pepys@pepys:~/Scrivania/lso/C/pipe/prova strana$ ls
es  es.c  inputF  pipe  pipe.c  trace.11193  trace.11194  trace.11195
pepys@pepys:~/Scrivania/lso/C/pipe/prova strana$ cat trace.11194
close(3)                                = 0
close(7)                                = 0
openat(AT_FDCWD, "inputF", O_RDONLY)    = 3
write(8, "N", 1)                        = 1
read(3, "abc", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "abc", 3)                      = 3
read(3, "def", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "def", 3)                      = 3
read(3, "ghi", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "ghi", 3)                      = 3
read(3, "lmn", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "lmn", 3)                      = 3
read(3, "opq", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "opq", 3)                      = 3
read(3, "rst", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "rst", 3)                      = 3
read(3, "uvz", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "uvz", 3)                      = 3
read(3, "", 3)                          = 0
write(8, "S", 1)                        = 1
close(8)                                = 0
close(3)                                = 0
close(4)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

strace for the second child process

close(5)                                = 0
close(4)                                = 0
read(3, "abc", 3)                       = 3
read(3, "def", 3)                       = 3
write(6, "def", 3)                      = 3
read(3, "ghi", 3)                       = 3
write(6, "ghi", 3)                      = 3
read(3, "lmn", 3)                       = 3
write(6, "lmn", 3)                      = 3
read(3, "opq", 3)                       = 3
read(3, "rst", 3)                       = 3
write(6, "rst", 3)                      = 3
read(3, "uvz", 3)                       = 3
read(3, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++
2
Please paste console text as text (in a code block), not as images.rici

2 Answers

2
votes

The system won't report EOF on a pipe's read descriptor while there is any process that has the pipe's write descriptor open. That includes the current process. You must make sure that pipes are closed — lots of closes.


Rule of thumb: If you dup2() one end of a pipe to standard input or standard output, close both of the original file descriptors returned by pipe() as soon as possible. In particular, you should close them before using any of the exec*() family of functions.

The rule also applies if you duplicate the descriptors with either dup() or fcntl() with F_DUPFD


If the parent process will not communicate with any of its children via the pipe, it must ensure that it closes both ends of the pipe early enough (before waiting, for example) so that its children can receive EOF indications on read (or get SIGPIPE signals or write errors on write), rather than blocking indefinitely. Even if the parent uses the pipe without using dup2(), it should normally close at least one end of the pipe — it is extremely rare for a program to read and write on both ends of a single pipe.

Note that the O_CLOEXEC option to open(), and the FD_CLOEXEC and F_DUPFD_CLOEXEC options to fcntl() can also factor into this discussion.

If you use posix_spawn() and its extensive family of support functions (21 functions in total), you will need to review how to close file descriptors in the spawned process (posix_spawn_file_actions_addclose(), etc.).

Note that using dup2(a, b) is safer than using close(b); dup(a); for a variety of reasons. One is that if you want to force the file descriptor to a larger than usual number, dup2() is the only sensible way to do that. Another is that if a is the same as b (e.g. both 0), then dup2() handles it correctly (it doesn't close b before duplicating a) whereas the separate close() and dup() fails horribly. This is an unlikely, but not impossible, circumstance.

1
votes

why does the second child process get stuck reading the pipe if I don't close the first pipe (first child - second child) in the parent process?

I don't fully understand at a short glance what your code does exactly, but essentially, you create three pipes first and then you fork twice.

This leaves you with three processes and three pipes. Each of your processes as a reading and a writing end of each of the pipes.

A pipe's reading end only signals EOF when all writers are closed. I supposed this isn't true in your situation.