Reputation: 305
Ive read numerous posts about tty. They all start with the historical reasons for the tty name. Please leave this out and just describe the tty system as it exists today. Then they talk about how a tty is a file and that stdin, stdout and sterr of a process started in a terminal are all mapped to this file. How are three files mapped to a single file?
Some say that tty allows line editing before enter is hit and does other line discipline stuff. There is a blog post which says that each tty has its own stdin and stdout . The blog post by Linus Akesson which i'm still grappling with explains that there is in fact a tty driver in the kernel and a tty device file. then there is the controlling terminal, sessions, terminal emulators, raw and cooked modes, pty and what not.
To better understand what tty is, can someone explain to me a what happens in this simple situation: A terminal is opened and it runs the default shell. From the shell a process is run and it asks for input.
And the output part: When the same process outputs something, does it write to the tty device? But isnt the tty already outputting the current editing buffer line?
IF theres a better way to decribe what a tty does without answering the above questions then please do so If i missed some crucial part please fill in as you think necessary.
Upvotes: 5
Views: 4351
Reputation: 6048
This turned out really long, so brace yourself...
You are looking at a TTY as a screen divided to e.g. 80x24 tiles. But a TTY is a console: it contains an input device (generally connected to a keyboard) and an output device (generally connected to a screen).
While the TTY is connected to (physical or simulated) devices, Unix processes don't see devices, they see an abstraction. This abstraction consists of an input stream, an output stream, and a control interface.
The control interface can turn on/off some "fancy" features like sending the input it receives not only to the process using the terminal, but also to its own output stream (that feature is called "echo" and can be controlled with stty echo
), but the fact that you are seeing something on the terminal output doesn't mean it's connected to any form of stdout.
This abstraction is implemented in the kernel by the TTY driver and the line discipline. You can think of the line discipline as the Strategy design pattern.
This abstraction has to be available to userspace, and the way Unix drivers export anything to userspace is by creating special files such as /dev/tty
.
A TTY file allows you to read()
the input stream, write()
to the output stream, and turn features on/off via ioctl()
Usually, every time you launch a new terminal, a new TTY file is created by the driver.
Any process can open a tty file, regardless of whether that file will be the process' stdin, stdout, stderr, all of them, or none of them.
You can see for yourself: Open a terminal and type tty
. Let's say it printed /dev/pts/3
.
Now open another terminal and run:
exec 10>/dev/pts/3 # open /dev/pts/3 as file descriptor 10
echo hello >&10 # write "hello" through file descriptor 10
This will cause echo
to write hello
to file descriptor 10, which is the first terminal. Accordingly, the first terminal will print hello
.
Unix implements the 3 standard streams, stdin, stdout, and stderr. These receive no special treatment whatsoever from the terminal driver or line discipline, and most of their implementation is in the shell.
When you start your terminal emulator, it opens a tty file, say /dev/pts/3
. It then creates a new process (fork()
), opens /dev/pts/3
as file descriptors 0 (stdin), 1 (stdout), and 2 (stderr), and then executes the shell.
Which means that when the shell starts, it has the terminal file, with its streams and control interfaces. When the shell writes to either stdout or stderr, both writes go to the TTY's output stream.
When the shell executes another process, the process inherits /dev/pts/3
as its file descriptors 0, 1, 2, unless the shell does redirection, or the executed programs changes these file descriptors.
Now we're ready to answer your questions:
What happens when the call scanf is made?
scanf()
calls read(STDIN)
, which calls the TTY driver's implementation of read()
.
In cooked mode, this will block until the input stream has buffered a full line. In raw mode, it will block until at least one character was read.
Then the TTY input buffer is copied to scanf's buffer.
How does the terminal know that scanf is called?
It doesn't. If you type something into the terminal while the program is running and not awaiting your input, it will be buffered in the terminal's input stream.
Then, whenever scanf is called, if at all, it will read that buffer. If scanf isn't called, then the program ends, control returns to the shell. and the shell reads that buffer. You can see it by running sleep 30
, and while it's running, type another command and press enter. The shell will execute it after sleep
is done:
bash-4.3$ sleep 30
echo hello
bash-4.3$ echo hello
hello
bash-4.3$
The editing buffer in the terminal which we see afterwards(the line where we enter text) - where does it come from? Does this buffer exist in the tty device file and is being outputted like an stdout file is printed?
The buffer exists in the kernel and is attached to the TTY file.
If the terminal's echo feature is on, the line discipline will send the input stream not only to the input buffer, but also to the output stream.
If the terminal is in cooked (default) mode, the line discipline will give special treatment characters like backspace (yes, backspace is a character, ASCII 8). In the case of backspace, it will remove the last character from the input buffer, and send to the output stream a control sequence to delete the last character from the screen.
Note that the buffer is managed separately from what you see on the screen.
Which process is controlling this buffer? The tty driver?
The buffer is in the kernel, and not controlled by a process, but rather by the line discipline, which is controlled by the TTY driver.
What happens when we press enter? Does the tty driver 'submit' the line to the stdin part of the tty device?
When you press "enter" a linefeed character (\n
) is added to the buffer, and, if any process is waiting on terminal input, the input buffer is copied to the process' buffer and the process becomes unblocked and continues to run.
The more interesting question is what happens when you press something that is NOT "enter". In raw mode, it doesn't matter if it's "enter" or not, because \n
doesn't get any special treatment. In cooked mode, however, the input buffer is not copied and the reading process is not notified.
How does the process know that input has been submitted.
The process calls e.g. scanf()
which read(STDIN)
, which will block the process until input is available. When input is available, the TTY driver will unblock the blocked process (i.e. wake it up).
Note that this is not special to TTY files or to STDIN, it applies to all files, that's just how read()
works.
Also note that scanf()
doesn't know whether or not STDIN is a TTY file.
When the same process outputs something, does it write to the tty device?
when you call something like printf()
, it calls write(STDOUT)
, which calls the TTY driver's implementation of write()
, which writes to the TTY's output stream.
Again, note that printf()
doesn't know whether or not STDOUT is a TTY file.
But isnt the tty already outputting the current editing buffer line?
In Unix, a file (any file, not just TTY files) can be opened and written to by multiple writers, and no synchronization among them is guaranteed.
As you can see with the echo hello >&10
example above, the process running in the terminal is not the only thing which can write to the TTY's output stream, but even an unrelated process can write to the TTY's output stream.
And when echo is enabled, the line discipline can also write to the TTY's output stream.
All these writes will be interleaved, the driver won't try to synchronize them or make sense of them.
Upvotes: 13