I'm following the free book Operating Systems: Three Easy Pieces by Arpaci-Dusseau, and despite being very new to C programming (constructive criticism is welcomed), I tried my luck on coding problem 8 from chapter 5:
Write a program that creates two children, and connects the standard output of one to the standard input of the other, using the
pipe()
system call.
Here is my attempt (some error-checking is removed for brevity):
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int pipefd[2];
char buf;
pipe(pipefd);
int rc1 = fork();
if (rc1 == 0) {
// Child 1, reading from argv, writing to pipe
close(pipefd[0]);
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]);
printf("Child 1 done.\n");
} else {
// Parent
wait(NULL);
int rc2 = fork();
if (rc2 == 0) {
// Child 2, reading from pipe, writing to stdout
printf("Entering child 2\n");
close(pipefd[1]);
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
printf("Wrote to stdout!\n");
close(pipefd[0]);
printf("Child 2 done.\n");
} else {
// Still parent
wait(NULL);
printf("Parent finished running.\n");
}
}
}
which generates the following output, and hangs:
$ ./myprogram "Hello world"
Child 1 done.
Entering child 2
Hello world█
where █
is the shell cursor, i.e. the while
loop hasn't exited to reach the writing of the newline character thereafter.
I did, however, get this to work by replacing char buf;
with char buf[1024];
and the lines
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
with
int n = read(pipefd[0], &buf, 1024);
write(STDOUT_FILENO, &buf, n);
So, now it works. But I don't understand why the previous version did not work. That reading loop was practically identical to the one used in the example at the bottom of the pipe(2)
man-pages, which I have verified to be working correctly for me. But why doesn't it work in my own little program?
Possible duplicate questions:
(1) Solution is to close unused ends of pipe. I believe I have done that correctly.
(2) Solution is to break on return codes <= 0
, not on < 0
. I believe I have done that correctly too.
CodePudding user response:
pipefd[1])
never gets closed in the original parent process. So read(pipefd[0], &buf, 1)
will hang in the second child process.
This version
int n = read(pipefd[0], &buf, 1024);
write(STDOUT_FILENO, &buf, n);
doesn't hang because there's no loop. The second child process reads the data written by
write(pipefd[1], argv[1], strlen(argv[1]));
and then continues onward, never checking pipefd[0]
again. So it doesn't matter if read(pipefd[0],...)
would hang.