Skip to content
HN On Hacker News ↗

The TTY demystified

▲ 96 points 11 comments by 20after4 5d ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully human-written

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 5 of 5
SEGMENTS · AI 0 of 5
WORD COUNT 1,726
PEAK AI % 0% · §4
Analyzed
May 19
backend: pangram/v3.3
Segments scanned
5 windows
avg 345 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,726 words · 5 segments analyzed

Human AI-generated
§1 Human · 0%

Real teletypes in the 1940s.The TTY subsystem is central to the design of Linux, and UNIX in general. Unfortunately, its importance is often overlooked, and it is difficult to find good introductory articles about it. I believe that a basic understanding of TTYs in Linux is essential for the developer and the advanced user.Beware, though: What you are about to see is not particularly elegant. In fact, the TTY subsystem — while quite functional from a user's point of view — is a twisty little mess of special cases. To understand how this came to be, we have to go back in time.HistoryIn 1869, the stock ticker was invented. It was an electro-mechanical machine consisting of a typewriter, a long pair of wires and a ticker tape printer, and its purpose was to distribute stock prices over long distances in realtime. This concept gradually evolved into the faster, ASCII-based teletype. Teletypes were once connected across the world in a large network, called Telex, which was used for transferring commercial telegrams, but the teletypes weren't connected to any computers yet.Meanwhile, however, the computers — still quite large and primitive, but able to multitask — were becoming powerful enough to be able to interact with users in realtime. When the command line eventually replaced the old batch processing model, teletypes were used as input and output devices, because they were readily available on the market.There was a plethora of teletype models around, all slightly different, so some kind of software compatibility layer was called for. In the UNIX world, the approach was to let the operating system kernel handle all the low-level details, such as word length, baud rate, flow control, parity, control codes for rudimentary line editing and so on. Fancy cursor movements, colour output and other advanced features made possible in the late 1970s by solid state video terminals such as the VT-100, were left to the applications.In present time, we find ourselves in a world where physical teletypes and video terminals are practically extinct. Unless you visit a museum or a hardware enthusiast, all the TTYs you're likely to see will be emulated video terminals — software simulations of the real thing.

§2 Human · 0%

But as we shall see, the legacy from the old cast-iron beasts is still lurking beneath the surface.The use casesA user types at a terminal (a physical teletype). This terminal is connected through a pair of wires to a UART (Universal Asynchronous Receiver and Transmitter) on the computer. The operating system contains a UART driver which manages the physical transmission of bytes, including parity checks and flow control. In a naïve system, the UART driver would then deliver the incoming bytes directly to some application process. But such an approach would lack the following essential features:Line editing. Most users make mistakes while typing, so a backspace key is often useful. This could of course be implemented by the applications themselves, but in accordance with the UNIX design philosophy, applications should be kept as simple as possible. So as a convenience, the operating system provides an editing buffer and some rudimentary editing commands (backspace, erase word, clear line, reprint), which are enabled by default inside the line discipline. Advanced applications may disable these features by putting the line discipline in raw mode instead of the default cooked (or canonical) mode. Most interactive applications (editors, mail user agents, shells, all programs relying on curses or readline) run in raw mode, and handle all the line editing commands themselves. The line discipline also contains options for character echoing and automatic conversion between carriage returns and linefeeds. Think of it as a primitive kernel-level sed(1), if you like.Incidentally, the kernel provides several different line disciplines. Only one of them is attached to a given serial device at a time. The default discipline, which provides line editing, is called N_TTY (drivers/char/n_tty.c, if you're feeling adventurous). Other disciplines are used for other purposes, such as managing packet switched data (ppp, IrDA, serial mice), but that is outside the scope of this article.Session management. The user probably wants to run several programs simultaneously, and interact with them one at a time. If a program goes into an endless loop, the user may want to kill it or suspend it. Programs that are started in the background should be able to execute until they try to write to the terminal, at which point they should be suspended. Likewise, user input should be directed to the foreground program only.

§3 Human · 0%

The operating system implements these features in the TTY driver (drivers/char/tty_io.c).An operating system process is "alive" (has an execution context), which means that it can perform actions. The TTY driver is not alive; in object oriented terminology, the TTY driver is a passive object. It has some data fields and some methods, but the only way it can actually do something is when one of its methods gets called from the context of a process or a kernel interrupt handler. The line discipline is likewise a passive entity.Together, a particular triplet of UART driver, line discipline instance and TTY driver may be referred to as a TTY device, or sometimes just TTY. A user process can affect the behaviour of any TTY device by manipulating the corresponding device file under /dev. Write permissions to the device file are required, so when a user logs in on a particular TTY, that user must become the owner of the device file. This is traditionally done by the login(1) program, which runs with root privileges.The physical line in the previous diagram could of course be a long-distance phone line:This does not change much, except that the system now has to handle a modem hangup situation as well.Let's move on to a typical desktop system. This is how the Linux console works:The TTY driver and line discipline behave just like in the previous examples, but there is no UART or physical terminal involved anymore. Instead, a video terminal (a complex state machine including a frame buffer of characters and graphical character attributes) is emulated in software, and rendered to a VGA display.The console subsystem is somewhat rigid. Things get more flexible (and abstract) if we move the terminal emulation into userland. This is how xterm(1) and its clones work:To facilitate moving the terminal emulation into userland, while still keeping the TTY subsystem (session management and line discipline) intact, the pseudo terminal or pty was invented. And as you may have guessed, things get even more complicated when you start running pseudo terminals inside pseudo terminals, à la screen(1) or ssh(1).Now let's take a step back and see how all of this fits into the process model.

§4 Human · 0%

ProcessesA Linux process can be in one of the following states: RRunning or runnable (on run queue) DUninterruptible sleep (waiting for some event) SInterruptible sleep (waiting for some event or signal) TStopped, either by a job control signal or because it is being traced by a debugger. ZZombie process, terminated but not yet reaped by its parent. By running ps l, you can see which processes are running, and which are sleeping. If a process is sleeping, the WCHAN column ("wait channel", the name of the wait queue) will tell you what kernel event the process is waiting for.$ ps l F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 0 500 5942 5928 15 0 12916 1460 wait Ss pts/14 0:00 -/bin/bash 0 500 12235 5942 15 0 21004 3572 wait S+ pts/14 0:01 vim index.php 0 500 12580 12235 15 0 8080 1440 wait S+ pts/14 0:00 /bin/bash -c (ps l) >/tmp/v727757/1 2>&1 0 500 12581 12580 15 0 4412 824 - R+ pts/14 0:00 ps l The "wait" wait queue corresponds to the wait(2) syscall, so these processes will be moved to the running state whenever there's a state change in one of their child processes. There are two sleeping states: Interruptible sleep and uninterruptible sleep. Interruptible sleep (the most common case) means that while the process is part of a wait queue, it may actually also be moved to the running state when a signal is sent to it.

§5 Human · 0%

If you look inside the kernel source code, you will find that any kernel code which is waiting for an event must check if a signal is pending after schedule() returns, and abort the syscall in that case. In the ps listing above, the STAT column displays the current state of each process. The same column may also contain one or more attributes, or flags: sThis process is a session leader. +This process is part of a foreground process group. These attributes are used for job control.Jobs and sessionsJob control is what happens when you press ^Z to suspend a program, or when you start a program in the background using &. A job is the same as a process group. Internal shell commands like jobs, fg and bg can be used to manipulate the existing jobs within a session. Each session is managed by a session leader, the shell, which is cooperating tightly with the kernel using a complex protocol of signals and system calls.The following example illustrates the relationship between processes, jobs and sessions:The following shell interactions......correspond to these processes......and these kernel structures. TTY Driver (/dev/pts/0). Size: 45x13 Controlling process group: (101) Foreground process group: (103) UART configuration (ignored, since this is an xterm): Baud rate, parity, word length and much more. Line discipline configuration: cooked/raw mode, linefeed correction, meaning of interrupt characters etc. Line discipline state: edit buffer (currently empty), cursor position within buffer etc. pipe0 Readable end (connected to PID 104 as file descriptor 0) Writable end (connected to PID 103 as file descriptor 1) Buffer The basic idea is that every pipeline is a job, because every process in a pipeline should be manipulated (stopped, resumed, killed) simultaneously. That's why kill(2) allows you to send signals to entire process groups. By default, fork(2) places a newly created child process in the same process group as its parent, so that e.g. a ^C from the keyboard will affect both parent and child. But the shell, as part of its session leader duties, creates a new process group every time it launches a pipeline.The TTY driver keeps track of the foreground process group id, but only in a passive way. The session leader has to update this information explicitly when necessary.