Open a terminal on your Linux system. You see a dark window with a blinking cursor, waiting for you to type. This seems simple -- you type, the computer responds. But between your keyboard and the program reading your input, there is a chain of abstraction layers with roots going back to the 1800s.
The word "terminal" and the abbreviation "TTY" both come from physical machines that no longer exist. Understanding where they came from explains why the system works the way it does today.
From Teletypes to Terminals
In the 1960s and 1970s, computers did not have monitors. Users interacted with the machine through a teletype (also called a teleprinter or TTY) -- a mechanical device with a keyboard and a printer. You typed a command on the keyboard. The computer's response was printed on paper.
The teletype communicated with the computer over a serial line. Each keystroke was converted to an electrical signal (using a code called ASCII), sent down the wire, and received by the computer. The computer's response traveled back the same way, driving the print mechanism.
By the late 1970s, teletypes were replaced by video terminals -- screens with keyboards. The DEC VT100 (1978) became the standard. Instead of printing on paper, it displayed text on a CRT screen. But it still connected to the computer over a serial line, and the computer still treated it as a TTY.
The physical terminals eventually disappeared too, replaced by terminal emulators -- software programs like GNOME Terminal, Alacritty, or xterm that simulate a VT100 (or its successors) in a window on your screen. But the kernel still uses the same abstraction it used for physical teletypes. That abstraction is the TTY subsystem.
Virtual Consoles
Before looking at terminal emulators, there is a simpler kind of terminal built directly into the kernel: the virtual console. If you press Ctrl+Alt+F1 through Ctrl+Alt+F6 on a Linux system, you switch between virtual consoles. Each one is a full-screen text terminal, managed entirely by the kernel without any graphical environment.
These are the /dev/tty1 through /dev/tty6 devices. They are real TTY devices -- the kernel draws text directly to the video card's framebuffer. When your graphical desktop fails to start, these consoles are your fallback.
Your graphical desktop typically runs on one of the higher-numbered virtual consoles (often tty2 or tty7, depending on the distribution). The terminal emulator you open within your desktop is something different -- it uses a pseudoterminal.
The Pseudoterminal (PTY)
A terminal emulator like GNOME Terminal or Alacritty is a regular graphical application. It draws a window, renders text using a font, and handles keyboard input. But the shell running inside it expects to talk to a TTY device. How does a regular program pretend to be a hardware terminal?
The answer is the pseudoterminal (PTY) -- a pair of virtual devices created by the kernel. One end is the master (also called the PTY master or ptmx). The other end is the slave (also called the PTY slave or pts).
The terminal emulator opens the master side. The shell (and any programs the shell runs) open the slave side. When you type a key in the terminal emulator, the emulator writes the character to the master. The kernel's TTY layer processes it and makes it available for reading on the slave. When the shell prints output, it writes to the slave. The kernel passes it to the master, and the terminal emulator renders it on screen.
You can see your current PTY with:
tty
This will print something like /dev/pts/0. The number increases for each terminal you open. List all active pseudoterminals:
ls /dev/pts/
The Line Discipline
Between the master and slave sides of the PTY sits the line discipline -- a kernel module that processes characters as they flow through the terminal. The line discipline is what makes a terminal feel like a terminal rather than a raw data pipe.
In its default mode (called canonical mode or cooked mode), the line discipline provides:
Line editing. When you type a character and press backspace, the line discipline removes the last character. You are editing a line buffer inside the kernel. The program on the other end does not see any of this -- it only receives a complete line when you press Enter.
Echo. When you type a character, the line discipline sends a copy back to the terminal so you can see what you typed. This is why you see your own keystrokes. Turn off echo (as passwd does), and you type blind.
Signal generation. Certain key combinations are intercepted by the line discipline and converted to signals:
- Ctrl+C generates SIGINT (interrupt the foreground process)
- Ctrl+Z generates SIGTSTP (suspend the foreground process)
- Ctrl+\ generates SIGQUIT (quit with core dump)
- Ctrl+D on an empty line signals end-of-file
These key bindings are not hard-coded -- they are configurable with the stty command. You can see the current settings:
stty -a
Raw Mode
Some programs need to handle every keystroke directly -- text editors like vim, interactive programs like top, and anything that uses arrow keys for navigation. These programs switch the terminal to raw mode (technically, they disable canonical mode and echo). In raw mode, the line discipline does nothing: every keystroke is passed immediately to the program without buffering, editing, or signal translation.
This is why pressing Ctrl+C in vim does not kill vim. Vim has put the terminal in raw mode and handles Ctrl+C itself.
How Keystrokes Reach Your Shell
Let us trace what happens when you press the letter "a" in a terminal emulator:
- Your operating system's window manager detects the key press and sends a key event to the terminal emulator application.
- The terminal emulator translates the key event to the byte
0x61(ASCII for "a"). - The terminal emulator writes
0x61to the PTY master (/dev/ptmx). - The kernel's line discipline receives the byte. In canonical mode, it adds it to the line buffer and echoes it back to the master (so the terminal emulator can display it).
- When you press Enter (
0x0A), the line discipline marks the line as complete. - The shell, reading from the PTY slave (
/dev/pts/0), receives the complete line. - The shell processes the command and writes its output to the PTY slave.
- The output passes through the line discipline (which does minimal processing on output) to the PTY master.
- The terminal emulator reads the output from the master and renders it on screen.
Terminal Escape Codes
A terminal does more than display plain text. It can position the cursor, change text color, clear the screen, and scroll regions. It does all of this through escape sequences -- special byte sequences that the terminal interprets as commands rather than text.
An escape sequence begins with the ESC character (byte 0x1B), followed by [, then parameters and a command letter. These are called ANSI escape codes because they were standardized by ANSI (the American National Standards Institute) in the 1970s.
Some examples:
| Sequence | Effect |
|---|---|
ESC[2J | Clear the entire screen |
ESC[H | Move cursor to top-left |
ESC[31m | Set text color to red |
ESC[0m | Reset all formatting |
ESC[10;20H | Move cursor to row 10, column 20 |
ESC[1A | Move cursor up one line |
When a program like ls --color outputs colored filenames, it is embedding these escape sequences in its output. The terminal emulator intercepts them and changes the rendering instead of displaying the raw bytes.
This is also why piping colored output to a file produces garbled-looking text -- the file contains the raw escape sequences, which only make sense to a terminal.
# This shows escape codes as visible text
ls --color=always | cat -v
The Terminal and Job Control
The terminal layer is deeply connected to process management. Each terminal has a concept of a foreground process group -- the set of processes currently allowed to read from the terminal and receive keyboard signals.
When you press Ctrl+C, the kernel does not send SIGINT to every process. It sends it to the foreground process group of the controlling terminal. When you press Ctrl+Z, SIGTSTP goes to the foreground process group.
This is how job control works in the shell. When you run a command, the shell puts it in the foreground. When you press Ctrl+Z, it gets stopped. When you type bg, the shell moves it to a background process group. Background processes that try to read from the terminal receive SIGTTIN and get stopped -- they are not allowed to compete with the foreground for terminal input.
SSH and Remote Terminals
SSH uses the same PTY mechanism. When you SSH into a remote machine, the sshd daemon on the remote side allocates a PTY pair. sshd holds the master, and the remote shell gets the slave. Your keystrokes travel encrypted over the network to sshd, which writes them to the master. Output travels back the same way.
This is why interactive programs work over SSH -- the remote shell sees a real PTY slave and behaves exactly as if you were sitting at a local terminal. The TERM environment variable (usually set to xterm-256color or similar) tells programs which escape sequences the terminal understands.
Inspecting Your Terminal
A few commands for examining the terminal system:
tty # Print your current terminal device
stty -a # Show all terminal settings
who # Show logged-in users and their terminals
w # Show who is logged in and what they are doing
ls -la /dev/pts/ # List active pseudoterminals
echo $TERM # Show the terminal type
The stty command is particularly useful. It shows the current line discipline settings: which characters trigger signals, whether echo is on, the terminal dimensions (rows and columns), and the baud rate (a historical artifact that is always 38400 for PTYs).
Why This Still Matters
Every time you open a terminal, type a command, press Ctrl+C to interrupt something, or SSH into a server, the TTY subsystem is doing the work. The abstraction is so good that you rarely think about it. But when something goes wrong -- when a program leaves your terminal in a broken state, when Ctrl+C does not work, when characters display as garbage -- understanding the PTY, the line discipline, and escape codes gives you the knowledge to fix it.
If your terminal ever gets into a bad state (no echo, garbled display), the fix is:
reset
This sends the terminal reset escape sequence and restores the line discipline to sane defaults. It works because it operates at the same level as the problem -- the TTY layer.