I was learning pipe
and communication between processes and I saw the following code to ping-pong a byte between parent and child processes:
int
main(int argc, char **argv)
{
int p[2];
char buf[2];
char *send = "a", *rec = "b";
pipe(p);
int pid = fork();
if(pid == 0){
if(read(p[0], buf, 1) != 1){
fprintf(2, "child failed to read byte from parent.\n");
exit(1);
};
close(p[0]);
printf("%d: received ping\n",getpid());
if(write(p[1], rec, 1) != 1){
fprintf(2, "child failed to send byte to parent.\n");
exit(1);
};
close(p[1]);
exit(0);
} else if(pid > 0){
if(write(p[1], send, 1) !=1){
fprintf(2, "parent failed to send byte to child.\n");
exit(1);
};
close(p[1]);
wait(0);
if(read(p[0], buf, 1) != 1) {
fprintf(2, "parent failed to read byte back from child.\n");
exit(1);
};
close(p[0]);
printf("%d: received pong\n", getpid());
exit(0);
}
else exit(1);
}
This code works. However, after learning that
If no data is available, a read on a pipe waits for either data to be written or for all file descriptors referring to the write end to be closed; in the latter case, read will return 0, just as if the end of a data file had been reached.
I began to have some doubts. The wait(0)
in the parent process seems now redundant, for the read
in the parent process would not run before the child process writes, which in some way acted as a wait()
.
So, I removed the wait(0)
line, and the code would still be able to print "received pong" but no more "received ping", which means the child did not perform the read
as expected. This confuses me a lot.
To clarify: wait
is used by a parent process to wait for any child process to terminate, and to release the system information associated with it (commonly known as reaping a zombie process). Its functionality is only tangentially related to writing/reading to/from pipes, in that it vaguely falls under the umbrella of inter-process communication.
Without wait
in between the write
and read
of the parent process, it is very likely that the parent process will simply read the byte it has written to the pipe before the child process even gets CPU time. Both processes are reading and writing to the same pipe, after all. In this case, the parent process fails to reap the child process properly by exiting without waiting. The child becomes an orphan process, since it will block forever, trying to read a byte from the pipe, as it still has the write end of the pipe open.
With wait
present, the parent process writes a byte to the pipe, and then blocks waiting for the child process to terminate. At approximately the same time the child process reads a byte (possibly blocking), then writes a byte, then terminates. After the child terminates, the parent process reaps the child process, and then reads a byte.
Leave the wait
where it is, or use two pipes for bidirectional communication and close
the unused ends of each more aggressively (i.e., as early as possible) where appropriate. Be wary of deadlocks.