What is a Pseudoterminal?
A pseudoterminal (PTY) is a pair of virtual devices โ a master and a slave โ that together behave like a real terminal (like a serial port or keyboard+screen). However, they are purely software-based and live entirely in the kernel. No physical hardware is involved.
The slave side looks exactly like a real terminal to any process that opens it. The master side is held by a controlling application (like an SSH server or a terminal emulator) that reads from and writes to the slave on behalf of the actual user.
Key Terms in This Part
The master and slave are two ends of a bidirectional channel. The slave end has a line discipline just like a real terminal โ it handles things like echo, newline processing, and Ctrl+C signal generation. The master end is what the controlling program (e.g., ssh, xterm) talks to.
Many programs behave differently depending on whether their standard input/output is a real terminal or a pipe. For example:
lsshows colored output on a terminal but plain text through a pipe.- Programs like
vim,top, andbashrequire a terminal โ they useisatty()to check. - SSH, telnet, and terminal emulators (xterm, gnome-terminal) all need to create a virtual terminal for the remote/child shell to run in.
Pseudoterminals solve this: the slave side passes the isatty() check because it truly is a terminal device (just a virtual one).
There are two historical styles of pseudoterminals in Linux:
- Single multiplexed master device:
/dev/ptmx - Slaves appear as
/dev/pts/0,/dev/pts/1โฆ - Uses
posix_openpt(),grantpt(),unlockpt(),ptsname() - Modern, preferred approach on Linux
- Defined by UNIX 98 / POSIX standard
- Pre-allocated master devices:
/dev/ptyXY - Slaves:
/dev/ttyXY - Must search for a free pair manually
- Limited number of pairs available
- Still used on older BSDs; legacy on Linux
TLPI Chapter 64 focuses on UNIX 98 style. The ptyMasterOpen() function (Section 64.3) is designed to hide these differences so the same calling code works with both styles.
Every terminal device (real or virtual) has a line discipline sitting between the hardware (or PTY master) and the user process. It is a kernel module that performs processing on the raw byte stream.
- Echo: echoes typed characters back
- Canonical mode: buffers input until newline
- Signal generation: Ctrl+C โ SIGINT, Ctrl+Z โ SIGTSTP
- Erase/kill: Backspace erases previous char
- Flow control: Ctrl+S / Ctrl+Q (XON/XOFF)
The line discipline can be switched to raw mode (no processing) using tcsetattr() โ this is what programs like vim and ssh do when they want full control of every keystroke.
Run these commands to see pseudoterminals in action on a Linux system:
/* List all current PTY slave devices */
$ ls -la /dev/pts/
total 0
crw--w---- 1 ravi tty 136, 0 Jun 18 10:00 0
crw--w---- 1 ravi tty 136, 1 Jun 18 10:05 1
c--------- 1 root root 5, 2 Jun 18 10:00 ptmx
/* Each open terminal window = one /dev/pts/N entry */
/* /dev/pts/ptmx is equivalent to /dev/ptmx */
/* Check what PTY your current shell is using */
$ tty
/dev/pts/0
/* Check if stdin is a terminal */
$ ls -la /proc/self/fd/0
lrwx------ 1 ravi ravi 64 Jun 18 10:00 /proc/self/fd/0 -> /dev/pts/0
/* Minimal C program: check if fd 0 is a terminal */
#include <stdio.h>
#include <unistd.h>
int main(void)
{
if (isatty(STDIN_FILENO))
printf("stdin is a terminal (PTY or real)\n");
else
printf("stdin is NOT a terminal (pipe or file)\n");
return 0;
}
/*
* Compile: gcc -o check_tty check_tty.c
* Run directly: ./check_tty -> "stdin is a terminal"
* Run via pipe: echo "" | ./check_tty -> "stdin is NOT a terminal"
*/
A real terminal is a physical device (serial port, keyboard+screen). A pseudoterminal is a kernel-emulated pair of devices (master + slave) that behaves exactly like a terminal but requires no hardware. The slave passes isatty() checks; the master is a file descriptor held by a controlling application.
The line discipline (typically N_TTY) sits between the PTY master and slave in the kernel. It performs input processing โ character echo, canonical buffering, signal generation (Ctrl+C โ SIGINT), erase/kill handling, and flow control. It can be disabled by setting the terminal to raw mode via tcsetattr().
The shell (bash, zsh) running inside the emulator must have a real terminal as its controlling terminal. A PTY slave serves this role. The terminal emulator holds the master side and renders characters received from the slave, while forwarding user keystrokes to the slave.
UNIX 98 PTY uses a single multiplexed master /dev/ptmx and dynamically creates slaves under /dev/pts/. BSD PTY uses pre-allocated static device pairs (/dev/ptyXY + /dev/ttyXY) and requires searching for a free pair. UNIX 98 is the modern standard on Linux.
(1) sshd โ allocates a PTY so the remote shell has a controlling terminal for interactive use. (2) script(1) โ uses a PTY to capture all terminal I/O (including control sequences) to a file. (3) expect โ drives interactive programs automatically by writing to and reading from the PTY master.
isatty(fd) check, and how does a PTY slave pass this check?
isatty() calls tcgetattr() internally. If the call succeeds (returns 0), the fd is a terminal; if it fails with ENOTTY, it is not. A PTY slave passes this check because it is a genuine character device with a line discipline, registered as a terminal in the kernel โ tcgetattr() succeeds on it.
