// Linux · 2025-03-08

搞懂Linux终端:从TTY到PTS,从历史到实战,一篇就够了!

1. 引言:从物理终端到伪终端

在 Linux/Unix 系统中,终端是用户与系统交互的核心界面。当我们打开一个终端模拟器(如 GNOME Terminal、iTerm2、Windows Terminal),输入命令并看到输出时,背后是一整套复杂的终端设备抽象体系在运作。TTY、PTS、PTY 这些术语正是这套体系的核心组成部分。

一句话理解:

  • TTY(Teletypewriter):任何类型的终端设备的总称,源于早期电传打字机
  • PTS(Pseudo-Terminal Slave):伪终端从设备,现代终端模拟器的“客户端”
  • PTY(Pseudo-Terminal):伪终端设备对(主设备 PTMX + 从设备 PTS)
graph LR
    subgraph "物理终端时代"
        A[电传打字机] --> B[TTY设备]
    end
    
    subgraph "现代虚拟终端"
        C[终端模拟器] --> D[PTY主设备]
        D --> E[PTS从设备]
        E --> F[Shell进程]
    end
    
    style A fill:#f9f9f9
    style C fill:#e1f5fe
    style E fill:#c8e6c9

本文将系统性地剖析这些终端概念的历史渊源、工作原理和现代应用场景,帮助读者彻底理解每次打开终端时后台发生的一切。


2. TTY 的起源与演进

2.1 历史:电传打字机(Teletypewriter)

TTY 是 Teletypewriter 的缩写,直译为“电传打字机”。在计算机早期时代(1960-1970年代),大型机(Mainframe)非常昂贵,通常放在专门的机房中。用户无法直接坐在“电脑前”操作,而是通过一种叫做“电传打字机”的设备连接到中央计算机。

物理 TTY 的工作方式:

  • 用户在电传打字机上敲击键盘
  • 按键信号通过串行电缆发送给计算机
  • 计算机处理后将输出结果打印在纸上(后来才出现显示器)

著名的 ASR-33 Teletype 是当时最流行的型号,每分钟只能打印 10 个字符。Unix 系统最初就是为连接这种 TTY 设备而设计的,TTY 这个名字也因此被固化在系统中。

2.2 终端类型演进

时代 终端类型 特点 典型例子
1960s-1970s 物理 TTY 电传打字机,纸带输入/输出 ASR-33 Teletype
1970s-1980s 智能终端 独立显示器,支持光标控制 VT100、VT220
1980s-1990s 控制台终端 直接连接在主机上的显示设备 Linux 虚拟控制台
1990s-至今 伪终端 图形界面下的终端模拟器 GNOME Terminal、iTerm2

2.3 现代 Linux 中的 TTY 类型

在现代 Linux 系统中,/dev 目录下存在多种终端设备:

# 查看系统中的终端设备
ls -la /dev/tty*
ls -la /dev/pts/*
设备类型 设备路径示例 说明
当前控制终端 /dev/tty 指向当前进程的控制终端
虚拟控制台 /dev/tty1 ~ /dev/tty63 物理机上的文本控制台(Ctrl+Alt+F1~F6)
伪终端从设备 /dev/pts/0 ~ /dev/pts/255 终端模拟器使用的从设备
串口终端 /dev/ttyS0, /dev/ttyUSB0 通过串口连接的物理终端
SSH 会话 /dev/pts/* SSH 登录也会分配伪终端

3. PTY 与 PTS:伪终端的核心架构

3.1 什么是伪终端(PTY)?

伪终端(PTY, Pseudo-Terminal) 是一个软件模拟的终端设备,用于在图形界面下模拟传统物理终端的行为。它由一对双向设备组成:

  • 主设备(PTMX / PTY Master):由终端模拟器程序打开和控制
  • 从设备(PTS, Pseudo-Terminal Slave):由 Shell 进程使用,行为类似于传统 TTY
graph TD
    subgraph "用户空间"
        A[终端模拟器<br>GNOME Terminal]
        C[Shell 进程<br>bash/zsh]
    end
    
    subgraph "内核空间"
        B[PTY Master<br>/dev/ptmx]
        D[PTS Slave<br>/dev/pts/0]
    end
    
    A -->|写入| B
    B -->|传递| D
    D -->|读取| C
    C -->|写入| D
    D -->|传递| B
    B -->|读取| A
    
    style B fill:#e1f5fe
    style D fill:#c8e6c9

3.2 PTY 的数据流

以用户在终端模拟器中输入 ls 命令为例:

1. 用户按下键盘 → 终端模拟器捕获按键
2. 终端模拟器将 'l'、's'、'\n' 写入 PTY Master (/dev/ptmx)
3. 内核将数据从 Master 传递给对应的 Slave (/dev/pts/0)
4. Shell 从 /dev/pts/0 读取数据,识别为命令
5. Shell 执行命令,输出 "file1 file2..."
6. Shell 将输出写入 /dev/pts/0
7. 内核将输出从 Slave 传回 Master
8. 终端模拟器从 Master 读取输出并显示到屏幕

3.3 PTS 与 PTMX 的关系

组件 全称 角色 打开方式
PTMX PTY Master “服务端”,由终端模拟器控制 open("/dev/ptmx", ...)
PTS PTY Slave “客户端”,由 Shell 使用 ptsname() 获取路径后打开

历史变迁:

  • 传统 Unix:/dev/pty[p-za-e][0-9a-f](大量预先创建的设备文件)
  • 现代 Linux:/dev/ptmx + /dev/pts/(动态分配,Unix 98 标准)
# 查看当前使用的伪终端
$ tty
/dev/pts/0

# 查看系统中所有 PTS 设备
$ ls /dev/pts/
0  1  2  3  ptmx

4. TTY 命令与工具

4.1 tty 命令:查看当前终端

# 查看当前终端的设备路径
$ tty
/dev/pts/0

# 在 SSH 会话中
$ tty
/dev/pts/1

# 在物理控制台(Ctrl+Alt+F2)
$ tty
/dev/tty2

4.2 who 命令:查看登录用户及其终端

$ who
user   :0           2024-01-15 09:00 (:0)
user   pts/0        2024-01-15 09:05 (192.168.1.100)
user   tty2         2024-01-15 09:10

输出解读:

  • :0:图形界面显示(第一个 X 会话)
  • pts/0:伪终端,来自远程 SSH 连接
  • tty2:虚拟控制台

4.3 ps 命令查看进程关联的终端

# 查看进程的 TTY 关联
$ ps aux | grep bash
user   1234  0.0  0.1  12345  6789 pts/0    Ss   09:00   0:00 -bash
user   5678  0.0  0.1  23456  7890 tty2     Ss+  09:10   0:00 /bin/login

# TTY 列说明
# pts/0   -> 关联到伪终端
# tty2    -> 关联到虚拟控制台
# ?       -> 守护进程,无控制终端

4.4 stty 命令:配置终端参数

# 查看当前终端设置
stty -a

# 禁用回车时的回车字符转换
stty -icrnl

# 设置删除键为 Backspace
stty erase ^H

# 显示特殊字符设置
stty -a | grep -E 'erase|kill|intr|quit'

常见终端控制字符:

控制字符 功能 默认按键
INTR 中断当前进程 Ctrl+C
QUIT 退出并生成 core dump Ctrl+\
SUSP 挂起当前进程 Ctrl+Z
ERASE 删除前一个字符 Backspace / Ctrl+H
KILL 删除整行 Ctrl+U
EOF 文件结束 Ctrl+D

5. 终端的工作模式

5.1 规范模式 vs 原始模式

终端驱动可以工作在不同的模式下:

模式 特点 典型应用
规范模式 行缓冲,编辑功能(退格、删除) 普通 Shell 输入
原始模式 字符立即传递,无行编辑 vim、less、top 等全屏程序
cbreak 模式 字符立即传递,但信号处理保留 部分交互式程序
graph LR
    A[按键] --> B{终端模式}
    B -->|规范模式| C[行缓冲<br>Enter 后才发送]
    B -->|原始模式| D[立即发送]
    C --> E[Shell 处理]
    D --> E

5.2 通过 stty 切换模式

# 原始模式(常用于编程)
stty raw

# 恢复到规范模式
stty cooked

# 保存当前设置
stty -g > stty.save
stty raw
# ... 执行需要原始模式的操作 ...
stty $(cat stty.save)

5.3 作业控制与信号

终端与进程组、会话、信号密切关联:

信号 触发方式 作用
SIGINT Ctrl+C 中断前台进程组
SIGTSTP Ctrl+Z 挂起前台进程组
SIGQUIT Ctrl+\ 退出前台进程组
SIGHUP 终端关闭 通知进程控制终端断开
# 后台运行并忽略挂起信号
nohup ./long_running_task &

# 使用 disown 将进程从当前终端脱离
./task &
disown

6. 实战场景

6.1 场景一:理解 SSH 登录的终端分配

当通过 SSH 登录远程服务器时,SSH 服务端会自动分配一个伪终端:

# 客户端执行 SSH
$ ssh user@server

# 服务器端产生一个新 PTS
$ who
user   pts/2        2024-01-15 10:00 (192.168.1.100)

# 查看进程树
$ pstree -p
sshd(1234)───sshd(5678)───bash(5679)───pstree(5680)

关键点:

  • SSH 守护进程监听连接
  • 认证成功后,sshd 调用 fork() 创建子进程
  • 子进程打开 /dev/ptmx 获取 PTY 主设备
  • 子进程 fork() 后再 setsid() 创建新会话
  • 子进程将 STDIN/STDOUT/STDERR 重定向到 PTS 从设备
  • 最后 exec() 执行 Shell

6.2 场景二:screen / tmux 的终端工作原理

终端复用器本质上是创建了一个独立的伪终端层次:

graph TD
    A[终端模拟器<br>/dev/pts/0] --> B[tmux 客户端]
    B --> C[tmux 服务器]
    C --> D[tmux 窗格1<br>/dev/pts/10]
    C --> E[tmux 窗格2<br>/dev/pts/11]
    C --> F[tmux 窗格3<br>/dev/pts/12]
    
    D --> G[Shell 1]
    E --> H[Shell 2]
    F --> I[Shell 3]
    
    style C fill:#e1f5fe

工作原理:

  1. tmux 服务器启动时,创建自己的 PTY 主设备
  2. 每个窗格对应一个 PTY 从设备
  3. tmux attach 时,客户端读取服务器状态并显示
  4. tmux detach 时,客户端退出但服务器继续运行
  5. 重新 attach 时,客户端重新连接到同一个服务器

验证:

# 开启 tmux
$ tmux
$ tty
/dev/pts/10   # tmux 内的终端

# 在另一个终端查看
$ ps aux | grep tmux
user   1234  0.0  0.1  12345  6789 ?        Ss   09:00   0:00 tmux
# 注意:tmux 服务器的 TTY 列是 ?,表示无控制终端

6.3 场景三:守护进程为何脱离终端

守护进程(如 nginx、sshd、cron)通常会主动脱离终端:

// 典型的守护进程创建步骤
pid_t pid = fork();
if (pid > 0) exit(0);      // 父进程退出
setsid();                   // 创建新会话,脱离原终端
chdir("/");                // 切换到根目录
umask(0);                  // 重设文件掩码
close(0); close(1); close(2); // 关闭标准文件描述符
open("/dev/null", O_RDWR); // 重定向到 /dev/null

原因:

  • 避免终端关闭时收到 SIGHUP 信号
  • 防止被前台进程组信号影响
  • 实现后台独立运行

6.4 场景四:/dev/tty 的特殊用途

/dev/tty 始终指向当前进程的控制终端,无论它是什么类型:

# 在任何终端中执行
$ echo "hello" > /dev/tty
hello   # 输出到当前屏幕

# 编写需要密码输入的程序时
$ cat > getpass.sh << 'EOF'
#!/bin/bash
echo -n "Enter password: "
stty -echo
read password < /dev/tty
stty echo
echo
echo "Password received"
EOF
$ chmod +x getpass.sh

应用场景:

  • 从标准输入重定向的程序中读取用户输入
  • su、sudo 等程序读取密码
  • 交互式脚本确保从终端读取,不受输入重定向影响

7. 常见问题与排查

7.1 PTS 耗尽问题

# 问题:无法打开新的终端窗口
# 错误:ptyp: No more ptys

# 查看当前 PTS 使用情况
$ ls /dev/pts/ | wc -l
256

# 修改系统最大 PTS 数量
$ echo "fs.nr_open=1048576" >> /etc/sysctl.conf
$ sysctl -p

# 清理僵尸 PTS(需要找到并终止对应进程)
$ fuser -v /dev/pts/0

7.2 终端乱码问题

# 问题:输出显示乱码

# 查看当前编码
$ locale
LANG=en_US.UTF-8

# 设置正确的编码
$ export LANG=en_US.UTF-8
$ export LC_ALL=en_US.UTF-8

# 重置终端
$ reset

# 查看终端类型
$ echo $TERM
xterm-256color

7.3 作业控制失效

# Ctrl+Z 无法挂起进程

# 检查作业控制是否启用
$ set -o | grep monitor
monitor         on

# 启用作业控制
$ set -m

# 检查终端是否支持作业控制
$ stty -a | grep -E 'isig|ixon'

7.4 SIGHUP 导致的进程终止

# 场景:关闭终端后后台进程被终止

# 方法1:使用 nohup
nohup ./long_task &

# 方法2:使用 disown
./long_task &
disown

# 方法3:使用 setsid
setsid ./long_task

# 方法4:在子 shell 中使用 &(屏蔽 HUP)
( ./long_task & )

8. 总结:终端抽象的本质

8.1 核心概念速记表

概念 含义 定位 现代对应
TTY 终端设备的总称 抽象概念 任何终端
PTY 伪终端设备对 内核机制 ptmx + pts
PTS 伪终端从设备 实际使用者 /dev/pts/n
PTMX 伪终端主设备 模拟器控制端 /dev/ptmx

8.2 层次关系图

graph TD
    subgraph "用户交互层"
        A[终端模拟器<br>GNOME Terminal]
        B[SSH 客户端]
        C[getty 登录]
    end
    
    subgraph "终端抽象层"
        D[PTY 主设备<br>/dev/ptmx]
        E[PTY 从设备<br>/dev/pts/0]
        F[虚拟控制台<br>/dev/tty2]
    end
    
    subgraph "应用层"
        G[Shell<br>bash/zsh]
        H[用户程序<br>vim/top]
    end
    
    A --> D
    B --> D
    C --> F
    
    D --> E
    E --> G
    F --> G
    
    G --> H
    
    style D fill:#e1f5fe
    style E fill:#c8e6c9
    style F fill:#fff3e0

8.3 最终总结

  1. TTY 是历史遗产,PTS 是现代化实现

    • TTY 作为概念保留,但实际使用的是 PTS
    • 理解历史有助于理解系统设计决策
  2. 终端是程序交互的桥梁

    • 每个 Shell 进程都关联到一个终端设备
    • 终端不仅负责输入输出,还处理信号和作业控制
  3. 伪终端是现代图形界面的基础

    • 从 GUI 终端模拟器到 SSH 远程连接,都依赖 PTY/PTS
    • 终端复用器(tmux/screen)基于 PTY 构建多路复用能力

掌握 TTY、PTS 等终端概念,意味着你理解了 Linux/Unix 系统与用户交互的底层机制。这不仅是系统编程的基础,也是排查终端相关问题的必备知识。无论是处理 SSH 连接异常、编写需要终端交互的程序,还是理解容器终端的实现原理,这些概念都是绕不开的知识点。

原文 https://blog.csdn.net/2301_79518550/article/details/145815565