10
votes

I am trying to understand the behavior of the three streams - stdout, stdin and stderr. I couldn't get the answer from any textbook, so I came here.

I know that these three are stored in file descriptor table with file descriptors 0 (stdin), 1 (stdout) and 2 (stderr). I am also aware that these are not merely file descriptors but I/O streams which can be redirected. Ok, so how about sharing?

Consider the three cases:

  1. When a fork() is called : The child process and parent process shares file descriptors, but do they have the same stdin, stdout and stderr ?
  2. When a thread is created : Threads share file descriptors, but I/O streams?
  3. When execl() is called : In this case the present process image is overwritten with new process image. If I do execl("./a.out", "a.out", NULL); , then will this new executable get a freshcopy of stdin, stderr and stdout?

All wise answers are welcome.

2

2 Answers

5
votes

In order to understand what's going on, consider that these are communication channels across the process boundaries. I'll avoid calling them streams, because those are used in different contexts, but they are related.

Now, firstly, the filedescriptor is just an index into a process-specific table that represents these channels, they are basically a kind of opaque handle. However, here is the first answer: Since threads are part of a process, they also share these descriptors, so if you write from two threads into the same channel, it leaves through the same channel, so outside of the process, the two threads are indistinguishable.

Then, when fork() is called, the process is effectively copied. This is done with copy-on-write optimizations, but still, it means that they also have different tables representing these communication channels. The entry with index 2 in one process is not the same as the one with index 2 in the fork. This is the same for any structure that is inside the process, if you created a C FILE* or a C++ std::stream on top of one, it gets copied, too, along with the other data.

When execl() is called, the process still "owns" certain channels to the outside. These are assigned to it from the OS which manages processes. This means that the index 2 can still be used to communicate with the outside world. On startup, the runtime library will then create e.g. the FILE* for use in C for the three well-known channels stdin, stdout and stderr.

The question that remains is what happens when a process is forked to the channels to the outside. Here, the answer is simple, either the channel is closed or inherited, which can be configured on a per-channel base. If it is inherited, it remains usable in the child process. Anything written to an inherited channel will end up wherever the output from the parent process would have ended up, too.

Concerning the stdin of a forked process, I'm actually not sure, I think that the input is by default one of those that are closed, because sending input to multiple targets doesn't make sense. Also, I never found the need to actually process input from stdin in a child process, unless that input was specifically provided by the parent process (similar to pipes in the shell, although those are siblings rather than parent and child).

Note: I'm not sure if this description is clear, please don't hesitate to ask and I will try to improve things for your understanding.

-1
votes

Lets assume the converse.

If they did not share the same locations (that is essentially what a file descriptor is) then these scenarios will have to conjure up something? Is that possible - one would conclude from a deterministic machine it is not.

There lies the answer. Yes they share the same location.