2

When running a command on a tty in a terminal it returns /dev/pts/10.

Besides that there are the files /dev/stdout /dev/stdin and /dev/stderr. Interacting with them directly shows results in the terminal.

user@laptop:build$ tty
/dev/pts/10
user@laptop:build$ echo "Test" > /dev/stdout
Test
user@laptop:build$ echo "Test" > /dev/stdin
Test
user@laptop:build$ echo "Test" > /dev/stderr
Test

Also any cli app, started from the shell, will have the file descriptors open for stdout/stderr/stdin. i.e. if you run a script that prints something, printing is an equivalent of writing to stdout.

So far stdout/stderr/stdin were the only interfaces the shell works with. That is also true for the apps.

Some OS component is eventually moving the data that is written to stdout to the terminal otherwise I wouldn't see anything printed.

When and where does a connection between stdout/stdin/stderr and the terminal happen so that interacting with std* actually result with something on the terminal?

My rough assumption that I'd like to have challenged is:

/dev/stdout, /dev/stdin and /dev/stderr are created by the running shell, they don't exist without a shell.

The shell sets up the communication channel with the actual device file representing the terminal (/dev/pts/10) and exposes the terminal functionalities through /dev/stdout, /dev/stdin and /dev/stderr. This way the shell provides the apps with a simple file interface instead of having every app worrying how to work the device file for simple printing.

Update

Even though /dev/pts/10 is a pseudoterminal, I'll value more the answers which manage to give the answer without introducing the pseudoterminal concept. I'm coming from a perspective that it will only distract from the answer to the question:

When and where does a connection between stdout/stdin/stderr and the terminal happen so that interacting with /dev/std* actually result with something on the terminal?

  • Or, rather, https://unix.stackexchange.com/q/513615/5132 . – JdeBP Jan 25 '20 at 11:43
  • 1
    @JdeBP The question you posted doesn't address my question. Following the links from that question leads to this answer: https://unix.stackexchange.com/a/18534/29436 which doesn't address my question but concludes with a Somewhat similarly, /dev/tty designates the terminal to which the process is connected. What were you hoping to communicate out with the link? – TheMeaningfulEngineer Jan 25 '20 at 11:51
  • The entire premise of this question is wrong: /dev/pts/10 is not "connected to" stdin, /dev/pts/10 IS stdin. This question looks purposely obtuse and a bit of text analysis "connects it" (LOL) to other similar questions on different matters. –  Jan 29 '20 at 13:20
  • @UncleBilly Thanks for the IS part. The /dev/std* are indeed just links to /dev/pts/10. Something must create those links and make it point to the actual terminal. When does that happen and what is responsible for it? – TheMeaningfulEngineer Jan 29 '20 at 13:36
  • "When running a command on a tty in a terminal it returns /dev/pts/1" what command? – ctrl-alt-delor Feb 02 '20 at 12:26

3 Answers3

5

/dev/pts/10 is the slave end of a pseudoterminal device pair. At the other end is the program that opened the master clone device /dev/ptmx and received /dev/pts/10 as pair (every time you open /dev/ptmx, you obtain a different slave). The connection between /dev/ptmx and /dev/pts/10 is basically a bidirectional pipe with a twist.

When you open a terminal emulator application:

  • it opens /dev/ptmx and obtains the name of the other end. It configures the other end and opens it up,
  • it forks,
  • the new process opens the other end of the pseudoterminal device and connects its stdin, stdout and stderr to it,
  • the new process executes the shell.

The shell does nothing to setup these three file descriptors, it inherits them from its parent process. The same way its children will inherit the file descriptors from the shell.

Remark: On a Linux system /dev/stdin, /dev/stdout and /dev/stderr are real files, which by a series of symbolic links point to /proc/<pid>/0, /proc/<pid>/1 and /proc/<pid>/2, which in turn point to the real input/output device: in your case /dev/pts/10.

The existence of these three standard streams is guaranteed by the C library.

Edit: to address your updated question, let's clarify some points of the answer:

  • everything written to /proc/pts/* is read by the terminal that created it and displayed, everything read from /proc/pts/* comes from an input device connected to the terminal,
  • on Linux /dev/stdout is often a symbolic link to /proc/self/fd/1, while /dev/stdin a symbolic link to /proc/self/fd/0. The virtual /proc filesystem takes care to show to every application /proc/self as a symbolic link to /proc/<pid>, where <pid> is the applications process id.
  • the symbolic links in /proc/<pid>/fd point to the files, pipes and other stuff opened by an application or inherited from their parent. Every application is guaranteed to have three file descriptors open: 0 to read input, 1 to write output, 2 to write errors. In your case it is /dev/pts/10.

If you don't redirect output to another file, every command run by the shell write to the terminal. The exception to this rule is if your command's process group is different from the foreground process group of the terminal, than the write will fail and SIGTTOU will be sent to the command. This behaviour can be controlled with stty tostop and stty -tostop:

stty tostop
echo "/dev/stdout points to the terminal, but I won't print anything" &
stty -tostop
echo "You can see me" &
  • You have definitely addressed my assumption on the shell being responsible for creating the /dev/st* files. Thank you for that :). I've updated the question to encourage answers which don't start clarifying pseudoterminals. The gist of the question is expanding on the part: which in turn point to the real input/output device. – TheMeaningfulEngineer Jan 25 '20 at 20:50
  • Thanks for the expansion of the answer. If you could just expand it just by being more explicit that /proc/<pid>/fd are actually a link to /dev/pts/21. And that /dev/pts/21 can be directly read/written to, I'd accept your answer and grant you the bounty. – TheMeaningfulEngineer Jan 29 '20 at 13:32
  • I modified my answer, but feel free to write your own answer or leave the bounty on until you find one that you like. – Piotr P. Karwasz Jan 29 '20 at 14:18
2

A POSIX-compliant program can expect to inherit file descriptors #0, #1 and #2 (also known by programming constants stdin, stdout and stderr, respectively) from its parent process, in an already-opened, ready-to-use state.

In the simplest case, of a command-line program in a session logged in on the text console, with no redirections applied, this chain of inheritance goes all the way back to the getty process that initialized the TTY device for the login session.

When logging in using a GUI, the display manager process (gdm/kdm/sddm/lightdm/xdm/<whatever>dm) will usually set the standard input and output to /dev/null and standard error to $HOME/.xsession-errors or something similar for the first process of the session, and these file descriptors are likewise inherited by all the GUI programs started in the session, either as parts of the desktop environment, or started using desktop menus or icons.

For e.g. SSH sessions, the sshd process that forked to initialize the session would have allocated a pseudo-TTY device pair, pointed the stdin/out/err file descriptors to one half of it, and then exec()ed the user's shell. The other side of that fork will keep hold of the other half of the pseudo-TTY device pair, and will be handling the en/decryption of the outgoing/incoming traffic between the network and the pseudo-TTY device, until the session ends.

When a terminal emulator is started within a GUI session, it behaves essentially the same as the sshd process when initializing a new session: it allocates a pseudo-TTY, fork()s itself, and one copy sets up the session, including pointing file descriptors #0, #1 and #2 to the pseudo-TTY and finally exec()s the user's shell, and the other side of the fork will remain handling the task of actually maintaining the visual representation of the terminal window.

So, in a nutshell, the (pseudo?)TTY device was connected to stdin/stdout/stderr by the thing that initialized your terminal session, and all the processes that may be between that and your application simply passed them down in a chain of inheritance, by doing nothing at all to those file descriptors, just letting them pass to their child process as-is.

When redirection operators are used in shell command line, as the shell fork()s a temporary copy of itself in preparation to actually exec()ing the command, just after the fork() the temporary copy will close the respective file descriptor, open the thing specified by the redirection operator in its place, and then exec() the command so that it will inherit the modified stdin/out/err file descriptor(s).


In some Unix-style systems /dev/std* devices might be handled by the shell. But Linux makes them a bit more "real".

In Linux, /dev/stdin, /dev/stdout and /dev/stderr are just plain old symbolic links pointing to the /proc filesystem:

$ ls -l /dev/std*
lrwxrwxrwx 1 root root 15 Feb  4 08:22 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Feb  4 08:22 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Feb  4 08:22 /dev/stdout -> /proc/self/fd/1

These links are created as the udev RAM-based /dev filesystem is initialized at system boot-up. They are just regular plain symbolic links, nothing magical there.

But /proc is an entirely virtual filesystem that reflects the state of the processes in the system in real-time, and so it has several "magical" properties:

  • /proc/self is a symbolic link that points to the /proc/<PID> directory of the process that looks at it:
$ ls -l /proc/self   # the PID of this ls command will be 10839
lrwxrwxrwx 1 root root 0 Feb  4 08:22 /proc/self -> 10839/

$ ls -l /proc/self   # the PID of this ls command will be 10843
lrwxrwxrwx 1 root root 0 Feb  4 08:22 /proc/self -> 10843/
  • /proc/<PID>/fd is a directory that contains symbolic links with their names corresponding to file descriptors opened by the process with <PID>, and pointing to whatever that file descriptor is associated with.

So, when a process on /dev/pts/10 tries to access /dev/stdin, the symbolic link points it to /proc/self/fd/0 instead... and when /proc/self/fd/0 is accessed, the /proc filesystem driver looks at the kernel's process table, uses it to find the file descriptor table of the process that is accessing it, and presents /proc/self/fd/0 as a symbolic link to /dev/pts/10... precisely because that process currently has /dev/pts/10 associated with its file descriptor #0.

On Solaris 11, the /dev/std* devices are symbolic links to /dev/fd/ directory, which is similarly "magical":

$ uname -sr
SunOS 5.11
$ ls -l /dev/std*
lrwxrwxrwx   1 root     root           0 Jun 17  2019 /dev/stderr -> ./fd/2
lrwxrwxrwx   1 root     root           0 Jun 17  2019 /dev/stdin -> ./fd/0
lrwxrwxrwx   1 root     root           0 Jun 17  2019 /dev/stdout -> ./fd/1

Here, the Solaris /dev filesystem driver implements the magic using device nodes in the /dev/fd directory instead of redirecting to /proc filesystem, as Linux does for historical reasons.

telcoM
  • 96,466
  • So much magic misses the fact that /dev/fd on Solaris (and OpenBSD, etc) are completely different than on Linux; on Solaris, open("/dev/fd/1", whatever_mode) is exactly equivalent to dup(1): su other_user -c 'echo yes >/dev/fd/1' will fail on Linux but succeed on Solaris. –  Feb 04 '20 at 12:15
  • And Solaris also has /proc/<pid>/fd/<fd>, but they're not exactly the same as those from Linux, either: they're "magical" hardlinks, not symlinks. (the /dev/fd/<fd> are "magical" character devices on Solaris). –  Feb 04 '20 at 12:24
0

Running a command in the shell

When a command is run in the shell, this is what happens.

  • shell calls fork: shell in how running in both processes.
  • new shell sets up std in/out/err: but if there is no redirection, it does nothing. The new process inherited these from the original shell, and the shell already had the correct values.
  • new shell calls exec to run a new program: this new program will inherit values for std in/out/err, and replaces the new shell.

This new shell is very transient (now mentioned in the docs, as it is just an implementation detail). It is not the same as a sub-shell.

The new command opens /dev/stdin

When the new program opens /dev/stdin, the filesystem code in the kernel sees that this is a symbolic-link to /proc/self/fd/0 it then sees that /dev/self is a symbolic-link to /proc/nnnn where nnnn is the pid of the process, so this points to /proc/nnnn/fd/0 which points to the file e.g. /dev/pts/10. Opening /dev/stdin will create a new file descriptor. Opening dev/stdin in not normal necessary, because file descriptor 0 already points to the file.

(You will only need to do it if the program is not written to read stdin, but can read from a file.) (all this is also true of stdout and stderr.)

The files in /proc are not real-file (not stored anywhere); These are dynamically created when accessed (never written to disk), by a file-system, that looks up the data from data structures with in the kernel (not from a disk).

  • shell in how running ... now mentioned (not?) looks like you have spell check except for the non-nonsense dvorak slips –  Feb 02 '20 at 13:37