Before stating my question, I have read several related questions on stack overflow, such as pipe & dup functions in UNIX, and several others,but didn't clarify my confusion.
First, the code, which is an example code from 'Beginning Linux Programming', 4th edition, Chapter 13:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
pid_t fork_result;
if (pipe(file_pipes) == 0)
{
fork_result = fork();
if (fork_result == (pid_t)-1)
{
fprintf(stderr, "Fork failure");
exit(EXIT_FAILURE);
}
if (fork_result == (pid_t)0) // Child process
{
close(0);
dup(file_pipes[0]);
close(file_pipes[0]); // LINE A
close(file_pipes[1]); // LINE B
execlp("od", "od", "-c", (char *)0);
exit(EXIT_FAILURE);
}
else // parent process
{
close(file_pipes[0]); // LINE C
data_processed = write(file_pipes[1], some_data,
strlen(some_data));
close(file_pipes[1]); // LINE D
printf("%d - wrote %d bytes\n", (int)getpid(), data_processed);
}
}
exit(EXIT_SUCCESS);
}
The execution result is:
momo@xue5:~/TestCode/IPC_Pipe$ ./a.out
10187 - wrote 3 bytes
momo@xue5:~/TestCode/IPC_Pipe$ 0000000 1 2 3
0000003
momo@xue5:~/TestCode/IPC_Pipe$
If you commented LINE A, LINE C, and LINE D, the result is the same as above.
I understand the result, the child get the data from its parent through its own stdin which is connected to pipe, and send 'od -c' result to its stdout.
However, if you commented LINE B, the result would be:
momo@xue5:~/TestCode/IPC_Pipe$ ./a.out
10436 - wrote 3 bytes
momo@xue5:~/TestCode/IPC_Pipe$
No 'od -c' result!
Is 'od -c' started by execlp() not excuted, or its output not directed to stdout? One possibility is the read() of 'od' is blocked, because the write file descriptor file_pipes[1] of child is open if you commented LINE B. But commenting LINE D, which let write file descriptor file_pipes[1] of parent open, can still have the 'od -c' output.
And, why we need to close pipe before execlp()? execlp() will replace the process image, including stack, .data, .heap, .text with new image from 'od'. Does that mean, even if you don't close file_pipes[0] and file_pipes[1] in child as LINE A and B, file_pipes[0] and file_pipes[1] will still be 'destroyed' by execlp()? From the result by code, it is not. But where am I wrong?
Thanks so much for your time and efforts here~~
Is closing a pipe necessary when followed by execlp()?
It's not strictly necessary because it depends on how the pipe is used. But in general, yes it should be closed if the pipe
end is not needed by the process.
why we need to close pipe before execlp()? execlp() will replace the process image
Because file descriptors (by default) remain open across exec
calls. From the man page: "By default, file descriptors remain open across an execve(). File descriptors that are marked close-on-exec are closed; see the description of FD_CLOEXEC in fcntl(2)."
However, if you commented LINE B,...No 'od -c' result!
This is because the od
process reads from stdin
until it gets an EOF
. If the process itself does not close file_pipes[1]
then it will not see an EOF
as the write end of the pipe would not be fully closed by all processes that had it opened.
If you commented LINE A, LINE C, and LINE D, he result is the same as above
This is because the file descriptors at A and C are read ends of the pipe and no one will be blocked waiting for it to be closed (as described above). The file descriptor at D is a write end and not closing it would indeed cause problems. However, even though the code does not explicitly call close
on that file descriptor, it will still be closed because the process exits.
And, why we need to close pipe before execlp()? execlp() will replace the process image, including stack, .data, .heap, .text with new image from 'od'.
Yes, the exec-family functions, including execlp()
, replace the process image of the calling process with a copy of the specified program. But the process's table of open file descriptors is not part of the process image -- it is maintained by the kernel, and it survives the exec.
Does that mean, even if you don't close file_pipes[0] and file_pipes[1] in child as LINE A and B, file_pipes[0] and file_pipes[1] will still be 'destroyed' by execlp()?
The variable file_pipes
is destroyed by execlp()
, but that's just the program's internal storage for the file descriptors. The descriptors are just integer indexes into a table maintained for the process by the kernel. Losing track of the file descriptor values does not cause the associated files to be closed. In fact, that's a form of resource leakage.
From the result by code, it is not. But where am I wrong?
As described above.
Additionally, when a process exits, all its open file descriptors are closed, but the underlying open file description in the kernel, to which the file descriptors refer, is closed only when no open file descriptors referring to it remain. Additional open file descriptors may be held by other processes, as a result of inheriting them across a fork()
.
Now as to the specific question of what happens when the child process does not close file_pipes[1]
before execing od
, you might get a clue by checking the process list via the ps
command. You will see the child od
process still running (maybe several, if you have tested several times). Why?
Well, how does od
know when to exit? It processes its entire input, so it must exit when it reaches the end of its input(s). But the end of input on a pipe doesn't mean that no more data is available right now, because more data might later be written to the write end of the pipe. End of input on a pipe happens when the write end is closed. And if the child does not close file_pipes[1]
before it execs, then it likely will remain open indefinitely, because after the exec the child doesn't any longer know that it owns it.