Does one end of a pipe have both read and write fd?
Solution 1
Yes, a pipe made with pipe() has two file descriptors. fd[0]
for reading and fd[1]
for writing.
No, you do not have to close either end of the pipe, it can be used for bidirectional communication.
Edit: in the comments you want to know how this relates to ls | less
, so I'll explain that too:
Your shell has three open file descriptors: 0 (stdin), 1 (stdout) and 2 (stderr). When a shell executes a command it does something like this (I've simplified it a bit:
pid = fork();
if(pid == 0) {
/* I am the child */
execve(...); /* Whatever the user asked for */
}
else {
waitpid(pid); /* Wait for child to complete */
}
File descriptors 0, 1 and 2 are inherited by the child, so input/output works as expected. If you do ls | less
, something slightly different happens to do the redirection:
int pipe[2];
pipe(pipe, 0);
pid1 = fork();
if(pid1 == 0) {
/* This is ls, we need to remap stdout to the pipe. We don't care about reading from the pipe */
close(pipe[0]);
close(1);
dup2(pipe[1], 1);
execve(...);
}
else {
pid2 = fork();
if(pid2 == 0) {
/* This is less, it reads from the pipe */
close(pipe[1]);
close(0);
dup2(pipe[0], 0);
execve(...);
}
else {
waitpid(pid1);
waitpid(pid2);
}
}
So the shell creates the pipe, forks, and just before executing it remaps the pipe to the stdin or stdout of the child processes, making data flow from process one to process 2. As shell pipes are not bidirectional, they only use one end of the pipe and close the other end (it actually closes the other end too, after duplicating the filedescriptor to stdin or stdout).
Solution 2
This is a summary of the chat conversation Dennis and I had, simplified so even other newbies can use it. When executing ls | less
:
- The bash shell is the parent process that has
fd[0]
,fd[1]
, andfd[2]
. -
pipe()
is called by the parent, and it creates a pipe which is nothing but a buffer to accommodate data, and it has a file descriptor at each end, one for reading and one for writing. - The child process inherits all the open fds; 0, 1, and 2, as well as the two for the pipe. Thus
ls
has its own copy of the buffer withfd[0]
andfd[1]
, but both of them point to the same pipe fds frombash
. Inside the first child process,ls
is executed and now we need to remap the output to the write end of the pipe (fd[1]
) and then to the console. Soclose(1)
anddup(fd[1])
will do the remapping.close(1)
only affects thels
child process, not thebash
parent. Now the output ofls
will be directed to thefd[1]
write end of the pipe to the child.fd[0]
is still the read end, and we don't want the data read byls
itself, so we still need to close that end withclose(fd[0])
. Again, this only closes the file descriptor inls
, notbash
. - We again fork to make the child process
less
, which inherits the pipe file descriptors. Input toless
needs to read from the buffer, so we make use offd[0]
. Sincefd[0]
isn't stdin anymore, we need to remap it, so weclose(0)
anddup(fd[0])
. Nowfd[0]
will point to stdin, so the input here is the output ofls
.less
usesfd[0]
for reading and closes the write endfd[1]
asless
wants to prevent the data read by its write from being written out again
Related videos on Youtube
John
Updated on September 18, 2022Comments
-
John over 1 year
As far as I understood one end of a pipe has both read and write fd's and the other end also has read and write fd's. Thats why when we are writing using
fd[1]
, we are closing the read end e.g.fd[0]
of the same side of the pipe and when we are reading from the 2nd end usingfd[0]
we close thefd[1]
of that end. Am I correct? -
Michael Mrozek about 11 yearsNice summary. I cleaned up the comments on the original answer
-
qdii about 11 yearsShouldn’t
less
be forked beforels
? writing to a pipe causes an error when the other end is closed.