I am writing a simple shell that handles piping. I have working code, but I don't quite understand how it all works under the hood. Here is a modified code snippet I need help understanding (I removed error checking to shorten it):
int fd[2];
pipe(fd);
if (fork()) { /* parent code */
close(fd[1]);
dup2(fd[0], 0);
/* call to execve() here */
} else { /* child code */
close(fd[0]);
dup2(fd[1], 1);
}
I have guesses for my questions, but that's all they are - guesses. Here are the questions I have:
- Where is the blocking performed? In all the example code I've seen,
read()
andwrite()
provide the blocking, but I didn't need to use them here. I just copy STDIN to point at the at the read end of the pipe and STDOUT to point to the write end of the pipe. What I'm guessing is happening is that STDIN is doing the blocking afterdup2(fd[0], 0)
is executed. Is this correct? - From what I understand, there is a descriptor table for each running process that points to the open files in the file table. What happens when a process redirects STDIN, STDOUT, or STDERR? Are these file descriptors shared across all processes' descriptor tables? Or are there copies for each process? Does redirecting one cause changes to be reflected among all of them?
After a call to
pipe()
and then a subsequent call tofork()
there are 4 "ends" of the pipe open: A read and a write end accessed by the parent and a read and a write end accessed by the child. In my code, I close the parent's write end and the child's read end. However, I don't close the remaining two ends after I'm done with the pipe. The code works fine, so I assume that some sort of implicit closing is done, but that's all guess work. Should I be adding explicit calls to close the remaining two ends, like this?int fd[2]; pipe(fd); if (fork()) { /* parent code */ close(fd[1]); dup2(fd[0], 0); /* call to execve() here */ close(fd[0]); } else { /* child code */ close(fd[0]); dup2(fd[1], 1); close(fd[1]); }
This is more of a conceptual question about how the piping process works. There is the read end of the pipe, referred to by the file handle
fd[0]
, and the write end of the pipe, referred to by the file handlefd[1]
. The pipe itself is just an abstraction represented by a byte stream. The file handles represent open files, correct? So does that mean that somewhere in the system, there is a file (pointed at byfd[1]
) that has all the information we want to send down the pipe written to it? And that after pushing that information through the byte stream, there is a file (pointed at byfd[0]
) that has all that information written to it as well, thus creating the abstraction of a pipe?