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 +++
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.