Pseudoterminals (PTY)Master/Slave Concept

 

Chapter 64: Pseudoterminals (PTY)
Part 1 — Introduction, Overview, and Master/Slave Concept
64.1
Section Covered
PTY
Virtual Terminal
IPC
Bidirectional

What Will You Learn?

In this tutorial we will understand what a pseudoterminal (PTY) is, why it was invented, and how the master and slave devices work together. This is fundamental knowledge for anyone working on terminal emulators, SSH, serial consoles over network, or any Linux systems programming involving interactive programs.

Think of a PTY as a “fake terminal” — it looks and behaves exactly like a real hardware terminal from the program’s point of view, but it is entirely virtual and implemented in the Linux kernel.

Key Terms in This Tutorial

Pseudoterminal (PTY) PTY Master PTY Slave Terminal-Oriented Program IPC Channel Controlling Terminal Driver Program Canonical Mode /dev/tty SIGINT / SIGTSTP

1. The Problem: Why Do We Need Pseudoterminals?

Before understanding what a PTY is, let us understand the problem it solves.

Imagine you want to run a text editor like vi on a remote server from your local laptop over a network. You have sockets and TCP/IP to transfer data. But there is a big problem: vi is a terminal-oriented program. It does not just read and write plain text. It expects to be connected to a real terminal device.

The Problem: Terminal-Oriented Program Over Network

Local Host
User at Terminal
Client Program
TCP/IP Protocols

← Network →
Socket works here

Remote Host
Terminal-Oriented
Program (vi, bash)
❌ Cannot connect
socket directly!
Problem: Socket ≠ Terminal. Terminal programs need terminal operations that fail on a socket.

A terminal-oriented program needs three things from its environment:

1. Terminal Operations
It must be able to switch the terminal to noncanonical mode, turn echoing on/off, set foreground process groups, etc. If it tries these on a socket, the system calls fail with ENOTTY.
2. Terminal Driver Processing
The terminal driver processes special characters. For example, in canonical mode, pressing Ctrl+D at line start signals end-of-file and makes the next read() return 0 bytes.
3. Controlling Terminal
A terminal-oriented program must have a controlling terminal so it can open /dev/tty and receive job-control signals like SIGINT, SIGTSTP, SIGTTIN.

None of these things work when stdin/stdout/stderr are connected to a raw socket. The pseudoterminal is the solution to all three problems at once.

2. What is a Pseudoterminal (PTY)?

A pseudoterminal (PTY) is a pair of virtual kernel devices that together act as a communication channel:

  • PTY Master — the controlling side, opened by the driver/relay program
  • PTY Slave — looks exactly like a real terminal, used by the terminal-oriented program

Think of it like a bidirectional pipe with terminal features:

PTY: Master and Slave Working Together

Driver Program
(ssh, terminal emulator, script)
Opens PTY Master
/dev/ptmx

write to master
read from master
Kernel PTY driver
handles terminal
line discipline

Terminal Program
(bash, vi, top, python)
Sees PTY Slave
/dev/pts/N
as if it’s a real terminal
Key Insight: The PTY slave device supports all terminal ioctls (tcsetattr, ioctl TIOCGWINSZ, etc.) just like a real terminal.

The key rule to remember: whatever you write to the master appears as input on the slave, and whatever the slave program writes appears as output readable from the master. The kernel PTY driver in the middle applies the full terminal line discipline — all the special character processing (Ctrl+C sends SIGINT, Ctrl+D is EOF, etc.).

The PTY slave silently ignores operations that don’t make sense for a virtual device (like setting baud rate or parity). This allows real terminal programs to work without any modifications.

3. What is a Terminal-Oriented Program?

The term terminal-oriented program is broader than you might think. It covers almost every interactive program you use in a shell session:

bash / sh / zsh
vi / vim / nano
top / htop
python interpreter
gdb debugger
passwd / sudo
less / more
mysql client

These programs share common behaviors that require a real terminal:

Why these programs need a real terminal (or PTY slave):
  • They call tcsetattr() to change terminal mode (raw, noncanonical, etc.)
  • They call tcgetattr() to save and restore terminal settings
  • They use ioctl(TIOCGWINSZ) to get window size for screen layout
  • They open /dev/tty to directly read passwords or control the terminal
  • They rely on the terminal driver to deliver signals on special key presses

If you try to run such a program with its stdin/stdout connected to a plain socket or pipe, these operations return errors like ENOTTY (“Not a typewriter”) and the program either crashes or behaves incorrectly.

C Code: What happens when a terminal program runs without a real terminal
#include <stdio.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    struct termios t;

    /* Try to get terminal attributes on stdin */
    if (tcgetattr(STDIN_FILENO, &t) == -1) {
        /* If stdin is a pipe or socket, this fails with ENOTTY */
        fprintf(stderr, "tcgetattr failed: %s\n", strerror(errno));
        /* errno will be ENOTTY = 25 = "Inappropriate ioctl for device" */
    } else {
        printf("stdin is a terminal - terminal programs work fine\n");
    }

    /* Check using isatty() before calling terminal functions */
    if (isatty(STDIN_FILENO)) {
        printf("stdin is a TTY\n");
    } else {
        printf("stdin is NOT a TTY - PTY slave would fix this\n");
    }

    return 0;
}
Try it yourself:
gcc -o check_tty check_tty.c && ./check_tty — runs with a real terminal, prints “is a TTY”
echo hello | ./check_tty — stdin is a pipe, prints “NOT a TTY”
./check_tty < /dev/null — stdin is /dev/null, prints “NOT a TTY”

4. The PTY Pair — Master and Slave in Detail

A pseudoterminal always comes as a matched pair: one master device and one slave device. You cannot use one without the other.

Property PTY Master PTY Slave
Device file /dev/ptmx (POSIX multiplexer) /dev/pts/0, /dev/pts/1, … (auto assigned)
Who opens it Driver program (ssh server, terminal emulator) Terminal-oriented program (shell, vi, etc.)
Looks like A regular file descriptor (no special terminal features) A full terminal device — supports all terminal ioctls
Data flow Write to master → appears as input on slave Write to slave → appears as output readable from master
Terminal processing Passes through kernel PTY line discipline Full line discipline applied (echo, Ctrl+C → SIGINT, etc.)
Controlling terminal Cannot be a controlling terminal Becomes the controlling terminal for the child process

The kernel PTY line discipline sits between master and slave. It does all the same work as a real serial terminal driver: echoing characters, translating newlines, processing Ctrl+C into SIGINT, Ctrl+Z into SIGTSTP, and so on. This is why terminal-oriented programs work transparently on the PTY slave.

C Code: Opening a PTY master (POSIX method)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
    int master_fd;
    char *slave_name;

    /*
     * posix_openpt() opens /dev/ptmx and returns a file descriptor
     * for a new PTY master device. This is the POSIX standard way.
     * Equivalent to: open("/dev/ptmx", O_RDWR)
     */
    master_fd = posix_openpt(O_RDWR | O_NOCTTY);
    if (master_fd == -1) {
        perror("posix_openpt");
        exit(EXIT_FAILURE);
    }

    /* grantpt(): sets ownership and permissions of the slave device */
    if (grantpt(master_fd) == -1) {
        perror("grantpt");
        exit(EXIT_FAILURE);
    }

    /* unlockpt(): unlocks the slave so it can be opened */
    if (unlockpt(master_fd) == -1) {
        perror("unlockpt");
        exit(EXIT_FAILURE);
    }

    /* ptsname(): returns the pathname of the slave device */
    slave_name = ptsname(master_fd);
    if (slave_name == NULL) {
        perror("ptsname");
        exit(EXIT_FAILURE);
    }

    printf("PTY master fd = %d\n", master_fd);
    printf("PTY slave device = %s\n", slave_name);
    /* slave_name will be something like: /dev/pts/3 */

    close(master_fd);
    return 0;
}
Sample output:
PTY master fd = 3
PTY slave device = /dev/pts/3

Every time you open a new terminal tab or SSH connection, the kernel allocates a new PTY pair. Run ls /dev/pts/ in your terminal to see all active PTY slaves on your system.

5. The Key Insight — PTY Slave Looks Exactly Like a Real Terminal

This is the most important concept in the entire chapter. The PTY slave device is indistinguishable from a real hardware terminal from the point of view of the terminal-oriented program running on it.

Real Hardware Terminal
  • Physical serial port
  • Terminal line discipline in kernel
  • isatty() returns 1
  • tcsetattr() works
  • TIOCGWINSZ ioctl works
  • Ctrl+C → SIGINT to fg process
  • Can be controlling terminal
PTY Slave (/dev/pts/N)
  • Virtual kernel device
  • Same line discipline in kernel
  • isatty() returns 1 ✓
  • tcsetattr() works ✓
  • TIOCGWINSZ ioctl works ✓
  • Ctrl+C → SIGINT to fg process ✓
  • Can be controlling terminal ✓

Some operations that don’t make sense for a virtual device (like setting baud rate or parity bits) are simply silently ignored by the PTY slave. The program gets a success return code and moves on, never knowing it’s not on real hardware.

Fun fact: When you open a terminal window (GNOME Terminal, Konsole, xterm, etc.), it opens a PTY pair. The terminal emulator is the driver program (opens master), and your shell (bash/zsh) is connected to the PTY slave. You can verify this by running tty in your shell — it will print something like /dev/pts/2.

Interview Questions — Pseudoterminal Introduction
Q1: What is a pseudoterminal and why is it needed?
A pseudoterminal (PTY) is a pair of virtual kernel devices — a master and a slave — that together simulate a real terminal. It is needed because terminal-oriented programs (bash, vi, ssh shells) require a controlling terminal to perform operations like setting terminal modes, receiving job-control signals, and opening /dev/tty. When connecting such programs over a network (via sockets), these operations fail because sockets are not terminals. The PTY slave acts as a virtual terminal that satisfies all these requirements.
Q2: What error do terminal-oriented programs get when connected to a socket instead of a terminal?
They get ENOTTY (errno 25, “Inappropriate ioctl for device”) when calling tcsetattr(), tcgetattr(), or any terminal-related ioctl. isatty(fd) returns 0 when the fd is a socket or pipe.
Q3: What is the difference between the PTY master and PTY slave?
The PTY master (/dev/ptmx) is opened by the driver program (like an SSH server or terminal emulator). It is a regular file descriptor with no terminal features itself. The PTY slave (/dev/pts/N) is connected to the terminal-oriented program. It behaves like a complete terminal device — supports all terminal ioctls, can be a controlling terminal, and has the kernel line discipline applied. Data written to the master appears as input on the slave, and vice versa.
Q4: What POSIX functions are used to open a PTY master and find its slave?
posix_openpt(O_RDWR) — opens /dev/ptmx and returns master fd.
grantpt(master_fd) — sets correct ownership/permissions on slave device.
unlockpt(master_fd) — unlocks the slave so it can be opened.
ptsname(master_fd) — returns the pathname of the slave (e.g., /dev/pts/3).
Q5: What three things does a terminal-oriented program require from its environment?
1. Terminal operations: ability to call tcsetattr/tcgetattr, change terminal modes, set foreground process group.
2. Terminal driver processing: the kernel line discipline must process special characters (Ctrl+D for EOF, Ctrl+C for SIGINT, echo, etc.).
3. Controlling terminal: must have a controlling terminal so it can open /dev/tty and receive job-control signals (SIGINT, SIGTSTP, SIGTTIN).
Q6: How can you check whether a file descriptor is connected to a terminal from a C program?
Use isatty(fd) — returns 1 if fd refers to a terminal, 0 otherwise. Alternatively call tcgetattr(fd, &t) — if it returns -1 with errno ENOTTY, the fd is not a terminal. Running the shell command tty also tells you the terminal device name if stdin is a terminal.
Q7: What happens to operations like setting baud rate on a PTY slave?
The PTY slave silently ignores operations that are not meaningful for a virtual device, such as setting baud rate or parity. The system calls return success but have no effect. This design allows existing terminal programs to run unmodified on PTY slaves.

Up Next: Part 2

How Programs Use PTYs — fork/exec workflow, SSH use case, and complete coding example

Part 2: PTY Usage and SSH → EmbeddedPathashala Home

Leave a Reply

Your email address will not be published. Required fields are marked *