unix

标签

Cards List
#unix

终端、TTY 与 Shell --- I've been curious about how terminals and shells work for a while now. I know that when I open a terminal emulator like iTerm and run `ls`, somehow the shell and the program `ls` run and print out results. But how does the terminal emulator communicate with the shell? What is a TTY? What even is a pseudoterminal? I've found a lot of existing resources on this kind of confusing or incomplete, so I'm going to try to write the clearest explanation I can. 我对终端和 Shell 的工作原理一直很好奇。我知道当我打开像 iTerm 这样的终端模拟器并运行 `ls` 时,Shell 和 `ls` 程序会运行并打印出结果。但终端模拟器是如何与 Shell 通信的呢?TTY 是什么?伪终端又是什么? 我发现现有的很多资料要么令人困惑,要么不够完整,所以我打算尽力写出最清晰的解释。 --- ## some history: physical terminals Before we talk about terminal emulators, let's discuss their predecessor: physical terminals. The very first computers were programmed not with a terminal but with physical switches. You would flip the switches to enter a binary program and see the output on some LEDs. Then computers got terminals. The earliest terminals were teletypes. These were basically fancy typewriters: they had a keyboard that you could type commands to the computer on, and a printer that would print out the results. Later, CRT screens replaced the printers. But the terminals still worked the same way: you type a character on the keyboard, the character gets sent to the computer, the computer sends the character back (this is called "echoing"), and the character gets displayed on the screen. The terminal itself wasn't doing a lot of computing – it was mainly just a way to communicate with the computer. Here's a picture of a VT100 terminal, which was introduced by DEC in 1978. This terminal was very important historically because it introduced support for ANSI escape codes (more on those later). ## 一些历史:物理终端 在谈论终端模拟器之前,我们先来聊聊它的前身:物理终端。 最早的计算机并不是通过终端来编程的,而是通过物理开关。你需要拨动开关来输入二进制程序,并通过一些 LED 灯查看输出结果。 后来,计算机有了终端。最早的终端是电传打字机(teletype)。这些基本上就是高级打字机:它们有一个键盘,可以向计算机输入命令,还有一台打印机,用于打印输出结果。 再后来,CRT 屏幕取代了打印机。但终端的工作方式仍然相同:你在键盘上输入一个字符,字符被发送到计算机,计算机再将字符发回(这称为"回显"),然后字符显示在屏幕上。终端本身并不承担太多计算工作——它主要只是一种与计算机通信的方式。 这是 VT100 终端的图片,由 DEC 于 1978 年推出。这款终端在历史上非常重要,因为它引入了对 ANSI 转义码的支持(后面会详细介绍)。 --- ## what's a TTY? TTY is short for "teletypewriter" – basically just another name for "terminal". In Linux, if you look at `/dev/`, you'll see a bunch of TTY devices. Historically, these TTY devices corresponded to physical terminals plugged into the computer. Each physical terminal connected to the computer had a corresponding TTY device in `/dev/`, and the operating system would communicate with those physical terminals through those TTY devices. ## 什么是 TTY? TTY 是"teletypewriter"(电传打字机)的缩写——基本上就是"终端"的另一种叫法。在 Linux 中,如果你查看 `/dev/` 目录,会看到一堆 TTY 设备。 从历史上看,这些 TTY 设备对应的是连接到计算机的物理终端。每台连接到计算机的物理终端在 `/dev/` 下都有一个对应的 TTY 设备,操作系统通过这些 TTY 设备与物理终端进行通信。 --- ## terminal emulators These days, most of us aren't using physical terminals. We're using terminal emulators – programs like iTerm2, GNOME Terminal, Terminal.app, etc. A terminal emulator emulates a physical terminal in software. Terminal emulators need to: 1. Emulate some physical terminal (usually VT100 or a variant of it), including ANSI escape codes. For example, if your shell sends the escape code `\x1b[34m`, that means "change the text colour to blue" and your terminal emulator needs to render the following text as blue. 2. Show the terminal on your screen 3. Accept keyboard/mouse input and send it to the shell Now, how does the terminal emulator communicate with the shell? ## 终端模拟器 如今,我们大多数人都不再使用物理终端,而是使用终端模拟器——如 iTerm2、GNOME Terminal、Terminal.app 等程序。 终端模拟器在软件层面模拟物理终端。终端模拟器需要: 1. 模拟某种物理终端(通常是 VT100 或其变体),包括 ANSI 转义码。例如,如果你的 Shell 发送转义码 `\x1b[34m`,这意味着"将文本颜色改为蓝色",你的终端模拟器需要将随后的文本以蓝色渲染。 2. 在屏幕上显示终端界面 3. 接收键盘/鼠标输入并将其发送给 Shell 那么,终端模拟器是如何与 Shell 通信的呢? --- ## pseudoterminals The way terminal emulators communicate with shells is through a **pseudoterminal** (also called a pty). A pseudoterminal is a fake terminal: it's an API that looks to programs like a terminal but is just two file descriptors in a Linux program: a "master" (or "controller") side and a "secondary" (or "replica") side. The way pseudoterminals work is: 1. The terminal emulator opens the master side of a pseudoterminal 2. The shell (and its child processes, like `ls`) gets the secondary side of the pseudoterminal as its stdin/stdout/stderr 3. The terminal emulator reads characters from the master side (which is the shell's output) and displays them on the screen 4. The terminal emulator also writes characters to the master side (which is the keyboard input) and the shell reads them from the secondary side Here's a diagram: ``` keyboard --> terminal emulator --> PTY master --> PTY secondary --> shell screen <-- terminal emulator <-- PTY master <-- PTY secondary <-- shell ``` The PTY (pseudoterminal) is managed by the operating system – the terminal emulator writes to the master side and the OS is responsible for making the data available on the secondary side. ## 伪终端 终端模拟器与 Shell 通信的方式是通过**伪终端**(也称为 pty)。 伪终端是一种虚拟终端:它是一个 API,对程序来说看起来像一个终端,但在 Linux 程序中实际上只是两个文件描述符:一个"主"(master,也称 controller)端和一个"从"(secondary,也称 replica)端。 伪终端的工作方式如下: 1. 终端模拟器打开伪终端的主端 2. Shell(及其子进程,如 `ls`)将伪终端的从端作为其 stdin/stdout/stderr 3. 终端模拟器从主端读取字符(即 Shell 的输出)并显示在屏幕上 4. 终端模拟器也向主端写入字符(即键盘输入),Shell 从从端读取这些字符 下面是一个示意图: ``` 键盘输入 --> 终端模拟器 --> PTY 主端 --> PTY 从端 --> Shell 屏幕显示 <-- 终端模拟器 <-- PTY 主端 <-- PTY 从端 <-- Shell ``` PTY(伪终端)由操作系统管理——终端模拟器向主端写入数据,操作系统负责将数据在从端提供给 Shell。 --- ## what is the TTY driver? But there's something else in the middle: the TTY driver. The TTY driver sits between the PTY master and secondary. ``` keyboard --> terminal emulator --> PTY master --> TTY driver --> PTY secondary --> shell screen <-- terminal emulator <-- PTY master <-- TTY driver <-- PTY secondary <-- shell ``` The TTY driver does a few things: 1. Line editing. The TTY driver keeps a buffer of the current line. When you press backspace, the TTY driver removes the last character from the buffer, and the character gets deleted from the terminal screen. When you press enter, the TTY driver sends the buffer to the shell. This is called "cooked mode" and is the default mode for terminals. 2. Echo. The TTY driver echoes characters back to the terminal emulator, so that the characters you type are displayed on the screen. 3. Handling special characters like Ctrl+C, Ctrl+Z, and Ctrl+D: when you press Ctrl+C, the TTY driver sends a SIGINT signal to the foreground process group, which usually causes the process to exit. The reason for the TTY driver is historical: back in the day, the TTY driver was a kernel module that handled communication with physical terminals. The pseudoterminal was designed to fit into the same framework. So the OS has a TTY driver even when there's no physical terminal. ## 什么是 TTY 驱动? 但中间还有另一个组件:TTY 驱动。TTY 驱动位于 PTY 主端和从端之间。 ``` 键盘输入 --> 终端模拟器 --> PTY 主端 --> TTY 驱动 --> PTY 从端 --> Shell 屏幕显示 <-- 终端模拟器 <-- PTY 主端 <-- TTY 驱动 <-- PTY 从端 <-- Shell ``` TTY 驱动负责以下几件事: 1. **行编辑**。TTY 驱动维护当前行的缓冲区。当你按下退格键时,TTY 驱动从缓冲区中删除最后一个字符,该字符也会从终端屏幕上消失。当你按下回车键时,TTY 驱动将缓冲区内容发送给 Shell。这称为"熟模式"(cooked mode),是终端的默认模式。 2. **回显**。TTY 驱动将字符回显给终端模拟器,使你输入的字符显示在屏幕上。 3. **处理特殊字符**,如 Ctrl+C、Ctrl+Z 和 Ctrl+D:当你按下 Ctrl+C 时,TTY 驱动向前台进程组发送 SIGINT 信号,通常会导致进程退出。 TTY 驱动存在的原因是历史性的:早年间,TTY 驱动是一个内核模块,负责处理与物理终端的通信。伪终端的设计是为了融入同一套框架。因此,即使没有物理终端,操作系统中也有 TTY 驱动。 --- ## raw mode and cooked mode As I mentioned above, in "cooked mode", the TTY driver does line editing. Programs can put the TTY into "raw mode" which disables line editing. Most interactive programs (like `vim`, `python`, `bash`) put the terminal into raw mode and handle all the keyboard input themselves, because they need to do their own line editing (e.g. handling arrow keys for history in bash) and they don't want the TTY driver to intercept special characters like Ctrl+C. For example, when you run vim, vim puts the terminal into raw mode. If you press Ctrl+C in vim, vim will handle the Ctrl+C itself (by cancelling the current operation), instead of the TTY driver sending a SIGINT signal to vim and killing it. ## 原始模式与熟模式 如前所述,在"熟模式"(cooked mode)下,TTY 驱动负责行编辑。程序可以将 TTY 设置为"原始模式"(raw mode),这会禁用行编辑。 大多数交互式程序(如 `vim`、`python`、`bash`)会将终端切换到原始模式,自行处理所有键盘输入,因为它们需要实现自己的行编辑功能(例如 bash 中用方向键浏览历史记录),并且不希望 TTY 驱动拦截 Ctrl+C 等特殊字符。 例如,当你运行 vim 时,vim 会将终端设置为原始模式。如果你在 vim 中按下 Ctrl+C,vim 会自行处理这个 Ctrl+C(取消当前操作),而不是让 TTY 驱动向 vim 发送 SIGINT 信号并将其终止。 --- ## ANSI escape codes Earlier I mentioned ANSI escape codes. These are sequences of characters that terminals interpret as commands. For example: - `\x1b[34m` means "change the text colour to blue" - `\x1b[0m` means "reset the text colour to the default" - `\x1b[1m` means "bold text" - `\x1b[2J` means "clear the screen" The `\x1b` is the escape character (ASCII code 27). The `[` character is the "Control Sequence Introducer". The rest of the sequence is the command. When a shell script uses `echo -e "\x1b[34mhello\x1b[0m"`, it's telling the terminal emulator to display "hello" in blue. ## ANSI 转义码 前面我提到了 ANSI 转义码。这些是终端将其解释为命令的字符序列。例如: - `\x1b[34m` 表示"将文本颜色改为蓝色" - `\x1b[0m` 表示"将文本颜色重置为默认值" - `\x1b[1m` 表示"粗体文本" - `\x1b[2J` 表示"清屏" `\x1b` 是转义字符(ASCII 码 27)。`[` 字符是"控制序列引导符"(Control Sequence Introducer)。序列的其余部分是具体命令。 当 Shell 脚本使用 `echo -e "\x1b[34mhello\x1b[0m"` 时,它是在告诉终端模拟器以蓝色显示"hello"。 --- ## what's a shell? I've been talking about shells throughout, but I haven't defined what a shell is. A shell is a program that: 1. Reads commands from the user (from a TTY or from a script file) 2. Runs those commands 3. Shows the output to the user Examples of shells are bash, zsh, fish, and sh. How does a shell run a command? The shell uses the `fork` and `exec` system calls. `fork` creates a copy of the shell process, and `exec` replaces the copy with the new program. The shell then waits for the program to finish. The shell also sets up the child process's stdin/stdout/stderr, and handles job control (foreground/background processes). ## 什么是 Shell? 我在整篇文章中一直在提 Shell,但还没有给出定义。Shell 是一个程序,它: 1. 从用户处读取命令(来自 TTY 或脚本文件) 2. 执行这些命令 3. 将输出显示给用户 Shell 的例子有 bash、zsh、fish 和 sh。 Shell 是如何运行命令的?Shell 使用 `fork` 和 `exec` 系统调用。`fork` 创建 Shell 进程的一个副本,`exec` 将该副本替换为新程序。然后 Shell 等待程序执行完毕。 Shell 还负责设置子进程的 stdin/stdout/stderr,以及处理任务控制(前台/后台进程)。 --- ## what happens when you run `ls`? Let's walk through what happens when you type `ls` in a terminal emulator. 1. You type `l` on the keyboard 2. The terminal emulator receives the `l` character and sends it to the PTY master 3. The TTY driver receives the `l` character and (in cooked mode) echoes it back to the PTY master 4. The terminal emulator receives the echoed `l` character and displays it on the screen 5. Same for `s` and then Enter 6. When Enter is pressed, the TTY driver sends the buffer `ls\n` to the PTY secondary 7. The shell reads `ls\n` from the PTY secondary 8. The shell forks and execs `ls` 9. `ls` writes its output to its stdout (which is the PTY secondary) 10. The TTY driver passes the output through to the PTY master 11. The terminal emulator reads the output from the PTY master and displays it on the screen ## 运行 `ls` 时发生了什么? 让我们来梳理一下在终端模拟器中输入 `ls` 时发生的事情。 1. 你在键盘上按下 `l` 2. 终端模拟器接收到字符 `l`,并将其发送到 PTY 主端 3. TTY 驱动接收到字符 `l`,并(在熟模式下)将其回显给 PTY 主端 4. 终端模拟器接收到回显的字符 `l`,并将其显示在屏幕上 5. `s` 和回车键同理 6. 当按下回车键时,TTY 驱动将缓冲区内容 `ls\n` 发送到 PTY 从端 7. Shell 从 PTY 从端读取 `ls\n` 8. Shell 执行 fork 和 exec 来运行 `ls` 9. `ls` 将其输出写入 stdout(即 PTY 从端) 10. TTY 驱动将输出传递到 PTY 主端 11. 终端模拟器从 PTY 主端读取输出,并将其显示在屏幕上 --- ## what about SSH? SSH is interesting because it involves two computers. Here's what happens when you run `ssh user@server` and then run `ls`: 1. You type `ls` on the keyboard 2. Your terminal emulator sends `ls` to the PTY master on your local computer 3. The TTY driver echoes the characters back 4. The terminal emulator displays the echoed characters on the screen 5. The **SSH client** reads the characters from the PTY master and sends them over the network to the SSH server 6. The SSH server receives the characters and writes them to the PTY secondary on the remote computer 7. The shell running on the remote computer reads the characters from the PTY secondary 8. The shell runs `ls` and writes the output to the PTY secondary on the remote computer 9. The SSH server reads the output from the PTY master on the remote computer and sends it over the network to the SSH client 10. The SSH client writes the output to the PTY secondary on the local computer 11. The terminal emulator on the local computer reads the output from the PTY master and displays it on the screen Wait, that doesn't make sense. Let me reconsider. The SSH client is running on the local computer and the SSH server is running on the remote computer. The SSH client replaces the shell in the local PTY. ## SSH 呢? SSH 很有趣,因为它涉及两台计算机。下面是当你运行 `ssh user@server` 后再运行 `ls` 时发生的事情: 1. 你在键盘上输入 `ls` 2. 你的终端模拟器将 `ls` 发送到本地计算机的 PTY 主端 3. TTY 驱动将字符回显 4. 终端模拟器将回显的字符显示在屏幕上 5. **SSH 客户端**从 PTY 主端读取字符,并通过网络将其发送到 SSH 服务器 6. SSH 服务器接收字符,并将其写入远程计算机的 PTY 从端 7. 在远程计算机上运行的 Shell 从 PTY 从端读取字符 8. Shell 运行 `ls`,并将输出写入远程计算机的 PTY 从端 9. SSH 服务器从远程计算机的 PTY 主端读取输出,并通过网络发送给 SSH 客户端 10. SSH 客户端将输出写入本地计算机的 PTY 从端 11. 本地计算机上的终端模拟器从 PTY 主端读取输出,并将其显示在屏幕上 等等,这说不通。让我重新理一理。SSH 客户端运行在本地计算机上,SSH 服务器运行在远程计算机上。SSH 客户端在本地 PTY 中取代了 Shell 的角色。 --- Here's a better diagram for what happens with SSH: **Local computer:** ``` keyboard --> terminal emulator --> PTY master --> TTY driver --> PTY secondary --> ssh client screen <-- terminal emulator <-- PTY master <-- TTY driver <-- PTY secondary <-- ssh client ``` **Remote computer:** ``` (network) --> ssh server --> PTY master --> TTY driver --> PTY secondary --> shell (network) <-- ssh server <-- PTY master <-- TTY driver <-- PTY secondary <-- shell ``` So the SSH client is the "shell" from the perspective of the local terminal emulator, and the SSH server is the "terminal emulator" from the perspective of the remote shell. The SSH client and server communicate over the network using the SSH protocol. One interesting thing about SSH is that both the local and remote computers have a PTY. The local PTY handles things like Ctrl+C on the local side (in addition to the remote PTY handling Ctrl+C on the remote side). 下面是 SSH 场景下更清晰的示意图: **本地计算机:** ``` 键盘输入 --> 终端模拟器 --> PTY 主端 --> TTY 驱动 --> PTY 从端 --> ssh 客户端 屏幕显示 <-- 终端模拟器 <-- PTY 主端 <-- TTY 驱动 <-- PTY 从端 <-- ssh 客户端 ``` **远程计算机:** ``` (网络) --> ssh 服务器 --> PTY 主端 --> TTY 驱动 --> PTY 从端 --> Shell (网络) <-- ssh 服务器 <-- PTY 主端 <-- TTY 驱动 <-- PTY 从端 <-- Shell ``` 因此,从本地终端模拟器的角度来看,SSH 客户端扮演的是"Shell"的角色;而从远程 Shell 的角度来看,SSH 服务器扮演的是"终端模拟器"的角色。SSH 客户端和服务器通过 SSH 协议在网络上进行通信。 SSH 有一个有趣的地方:本地和远程计算机各自都有一个 PTY。本地 PTY 在本地端处理 Ctrl+C 等操作(同时远程 PTY 在远程端也处理 Ctrl+C)。 --- ## what about `tmux`? `tmux` is a terminal multiplexer: it lets you have multiple terminal sessions in a single window. It's also interesting from a PTY perspective. When you run `tmux`, `tmux` creates a new PTY for each window/pane and runs a shell in each PTY. `tmux` itself is the "terminal emulator" for those shells, but `tmux` is also running inside a PTY (the one created by your actual terminal emulator). So if you're running `tmux` inside iTerm, the chain is: ``` iTerm --> PTY --> tmux --> PTY --> shell ``` `tmux` acts as both a terminal emulator (for the shells it manages) and as a program (from iTerm's perspective). This is why `tmux` can keep running even if you close iTerm – the `tmux` process and its shells are attached to PTYs that don't depend on iTerm. When you reconnect to `tmux`, it re-attaches to the existing PTYs. ## `tmux` 呢? `tmux` 是一个终端复用器:它允许你在单个窗口中拥有多个终端会话。从 PTY 的角度来看,它也很有趣。 当你运行 `tmux` 时,`tmux` 为每个窗口/面板创建一个新的 PTY,并在每个 PTY 中运行一个 Shell。`tmux` 本身充当这些 Shell 的"终端模拟器",但 `tmux` 自身也运行在一个 PTY 中(由你实际使用的终端模拟器创建的那个)。 因此,如果你在 iTerm 中运行 `tmux`,整个链路是: ``` iTerm --> PTY --> tmux --> PTY --> shell ``` `tmux` 既充当终端模拟器(对其管理的 Shell 而言),又作为一个普通程序(从 iTerm 的角度而言)。 这就是为什么即使你关闭 iTerm,`tmux` 仍然可以继续运行——`tmux` 进程及其 Shell 附加在不依赖 iTerm 的 PTY 上。当你重新连接到 `tmux` 时,它会重新附加到已有的 PTY 上。 --- ## summary Here's a summary of what we've covered: - **Physical terminals**: the historical predecessors to terminal emulators. Teletypes that sent characters to the computer and received characters back. - **TTY**: short for teletypewriter. In Linux, TTY devices are in `/dev/`. Historically they corresponded to physical terminals. - **Terminal emulators**: programs that emulate physical terminals in software (iTerm2, GNOME Terminal, etc.) - **Pseudoterminals (PTYs)**: fake terminals – an API that looks like a terminal to programs. Has a master side (used by the terminal emulator) and a secondary side (used by the shell). - **TTY driver**: sits between PTY master and secondary. Handles line editing (cooked mode), echo, and special characters (Ctrl+C, etc.) - **Raw mode vs cooked mode**: in cooked mode, the TTY driver does line editing. In raw mode, the program handles all input itself. - **ANSI escape codes**: sequences of characters that terminals interpret as commands (e.g. change text colour, clear screen). - **Shell**: a program that reads commands, runs them, and shows output. Uses `fork` and `exec` system calls. Examples: bash, zsh, fish. ## 总结 以下是我们所涵盖内容的总结: - **物理终端**:终端模拟器的历史前身。向计算机发送字符并接收字符的电传打字机。 - **TTY**:teletypewriter 的缩写。在 Linux 中,TTY 设备位于 `/dev/` 下。历史上对应物理终端。 - **终端模拟器**:在软件层面模拟物理终端的程序(iTerm2、GNOME Terminal 等)。 - **伪终端(PTY)**:虚拟终端——一种对程序来说看起来像终端的 API。有主端(由终端模拟器使用)和从端(由 Shell 使用)。 - **TTY 驱动**:位于 PTY 主端和从端之间。处理行编辑(熟模式)、回显和特殊字符(Ctrl+C 等)。 - **原始模式与熟模式**:熟模式下,TTY 驱动负责行编辑;原始模式下,程序自行处理所有输入。 - **ANSI 转义码**:终端将其解释为命令的字符序列(例如更改文本颜色、清屏)。 - **Shell**:读取命令、执行命令并显示输出的程序。使用 `fork` 和 `exec` 系统调用。例如:bash、zsh、fish。

Lobsters Hottest · 5天前 缓存

# 终端背后的秘密:终端模拟器、TTY 与 Shell 作为开发者,我们每天都在使用终端。但你有没有想过,当你打开一个"终端窗口"时,究竟发生了什么?你输入的字符经过怎样的路径,最终变成命令的输出? 大多数人将整个体验笼统地称为"终端",但实际上,这背后存在三个截然不同的层次,它们各司其职、协同工作。理解这三个层次,不仅能帮助你排查奇怪的问题,更能让你对自己每天使用的工具有更深刻的认识。 ## 三个层次概览 在深入细节之前,先来认识这三位主角: 1. **终端模拟器(Terminal Emulator)**:你在屏幕上看到的那个窗口 2. **TTY / 伪终端(Pseudo-terminal)**:操作系统内核中的一个抽象层 3. **Shell**:真正解释并执行你命令的程序 它们的关系可以这样理解: ``` 你的键盘输入 ↓ 终端模拟器(GUI 窗口) ↓ 伪终端(内核层,PTY) ↓ Shell(bash / zsh / fish …) ↓ 命令输出原路返回 ``` --- ## 第一层:终端模拟器 ### 它是什么 终端模拟器是一个**图形界面程序**。常见的有 iTerm2、GNOME Terminal、Alacritty、Windows Terminal、Kitty 等。它的核心职责是: - 渲染文字到屏幕上 - 捕获你的键盘输入 - **模拟**老式硬件终端(如 VT100、xterm)的行为 "模拟器"这个词至关重要。在个人电脑普及之前,终端是一台真实的硬件设备——一台带显示器和键盘的哑终端,通过串口连接到大型主机。现代的终端模拟器用软件重现了那台硬件的行为。 ### 它做什么 当你按下键盘上的一个键,终端模拟器会将这个按键**转换成字节序列**,然后写入伪终端。例如,方向键 `↑` 通常被转换为转义序列 `\x1b[A`。 反过来,当程序向终端写入转义序列时,终端模拟器负责**解释**这些序列,并作出相应动作,例如: ``` \x1b[31m → 将后续文字渲染为红色 \x1b[2J → 清空整个屏幕 \x1b[1A → 光标上移一行 ``` 这就是为什么同一个程序(比如 `vim`)在不同的终端模拟器里,行为可能略有差异——不同的模拟器对转义序列的支持程度不同。 ### 一个常见的误解 很多人以为终端模拟器"运行"了 Shell。实际上,终端模拟器只是**启动**了 Shell,并为它提供一个可以读写的伪终端接口。之后,终端模拟器和 Shell 是两个独立运行的进程,通过内核中的伪终端相互通信。 --- ## 第二层:TTY 与伪终端(PTY) 这是三层中最容易被忽视、也最难理解的一层,但它是整个系统的**核心枢纽**。 ### TTY 的历史渊源 TTY 是 **Teletype**(电传打字机)的缩写。在计算机的早期历史中,人们用电传打字机与计算机交互——输入字符,打印机打印出响应。这套设备通过串口连接到计算机。 Unix 操作系统从一开始就将这些串口设备抽象为文件,放在 `/dev/tty*` 路径下。程序只需要读写这些文件,就能与用户交互,而不需要关心底层是什么硬件。 ### 伪终端(PTY)的出现 当我们转向图形界面,不再有真实的硬件串口时,Unix 需要一种方式来保持这套抽象,同时支持终端模拟器这样的软件。于是**伪终端(Pseudo-Terminal,PTY)**诞生了。 PTY 是内核提供的一对相互连接的虚拟设备: - **主端(master side)**:终端模拟器持有这一端 - **从端(slave side)**:Shell 及其子进程持有这一端,对应 `/dev/pts/0`、`/dev/pts/1` 这样的设备文件 你可以把它想象成一条**双向管道**,但这条管道远比普通管道聪明——它内置了一个叫做**行规程(line discipline)**的模块。 ### 行规程:被遗忘的功能 行规程是 TTY 层中最精妙的设计之一。它在内核中处理大量"低级"的终端行为,让每个 Shell 和应用程序不必自己重新实现这些功能: | 功能 | 说明 | |------|------| | **回显(Echo)** | 将你输入的字符显示在屏幕上 | | **行编辑** | `Backspace` 删除字符,`Ctrl+U` 清除整行 | | **信号生成** | `Ctrl+C` 发送 `SIGINT`,`Ctrl+Z` 发送 `SIGTSTP` | | **规范模式** | 缓冲整行输入,直到你按下回车 | 这解释了一个有趣的现象:即使在 Shell 还没启动、或 Shell 崩溃的情况下,`Backspace` 键依然"有效"——因为删除字符这个操作是由**内核**在 TTY 层处理的,而不是由 Shell 处理的。 ### 用命令亲眼验证 在终端里输入: ```bash tty ``` 你会看到类似这样的输出: ``` /dev/pts/3 ``` 这就是当前 Shell 正在使用的伪终端从端设备文件。你可以用 `ls -la /dev/pts/` 查看系统中所有活跃的伪终端。 更有趣的是,你可以直接向另一个终端窗口写入文字: ```bash # 在终端 A 中运行 tty,假设输出是 /dev/pts/3 # 然后在终端 B 中运行: echo "你好,终端 A" > /dev/pts/3 ``` 终端 A 的屏幕上会直接出现这段文字——这直观地展示了 TTY 设备文件的本质。 --- ## 第三层:Shell Shell 是你**最熟悉**却往往被与终端混为一谈的那一层。 ### Shell 是一个普通进程 Shell(bash、zsh、fish 等)本质上是一个**普通的用户空间进程**。它的特别之处在于: - 它将 `/dev/pts/N` 作为自己的**标准输入(stdin)**、**标准输出(stdout)**和**标准错误(stderr)** - 它读取你输入的文本,解析为命令,然后通过 `fork()` + `exec()` 创建子进程来执行这些命令 - 子进程同样继承了对 TTY 设备的连接 ### 原始模式 vs 规范模式 Shell 在启动后,通常会让 TTY 层工作在**规范模式(canonical mode)**下。此时行规程会帮 Shell 缓冲输入、处理退格键等,Shell 直接读取一整行已处理好的输入。 但当你运行 `vim` 或其他全屏程序时,情况就不同了。`vim` 会将 TTY 切换到**原始模式(raw mode)**: - 行规程的大部分处理被**绕过** - 每个按键立即传递给应用程序 - 应用程序自行决定如何处理每一个字节 这就是为什么在 `vim` 里,`Backspace` 的行为可以被完全自定义,而在普通 Shell 提示符下,`Backspace` 的行为是由内核保证的。 ### Shell 不是终端 这个区别在实践中非常重要。考虑以下场景: ```bash # 这会失败,因为 ssh 命令没有分配 TTY ssh user@host vim /etc/hosts # 这会成功,-t 参数强制分配一个伪终端 ssh -t user@host vim /etc/hosts ``` `vim` 需要一个真实的 TTY 才能工作(它需要将终端切换到原始模式)。当 `ssh` 不分配 TTY 时,远端的 `vim` 无法正常运行——因为它的标准输入只是一个普通管道,而不是一个 TTY 设备。 --- ## 三层如何协同工作:一次完整的按键之旅 让我们追踪一次按键——假设你在 Shell 提示符下输入字母 `l`,准备输入 `ls` 命令: ``` 1. 你按下键盘上的 "l" 键 2. 操作系统检测到按键事件 3. 终端模拟器收到按键事件, 将其编码为字节 0x6C(ASCII 'l'), 写入 PTY 主端 4. 内核 TTY 层(行规程)收到这个字节: - 将字节追加到输入缓冲区 - 因为开启了回显,将 'l' 写回 PTY 主端 5. 终端模拟器从 PTY 主端读取到回显的 'l', 将其渲染到屏幕上 (这就是你"看到"自己输入的原因) 6. Shell 此时还没有收到任何东西—— 它在等待一个完整的行(规范模式) 7. 你继续输入 "s",然后按下回车 8. 行规程收到回车,将完整的行 "ls\n" 送入 Shell 可读取的队列 9. Shell 的 read() 调用返回,得到 "ls\n" 10. Shell 解析命令,fork() 出子进程, exec() 执行 /bin/ls 11. ls 将输出写入其标准输出(同一个 PTY 从端) 12. 内核 TTY 层将输出传递到 PTY 主端 13. 终端模拟器读取输出,渲染到屏幕上 ``` 整个过程在毫秒之内完成,但涉及了用户空间和内核空间之间多次切换,以及三个独立组件的协作。 --- ## 为什么这些知识对开发者有用 理解这三个层次,能帮助你解释和解决很多实际问题: **1. 为什么有些程序检测到自己的输出被重定向后,行为会改变?** ```bash ls --color=auto # 输出彩色 ls --color=auto | cat # 输出变成黑白 ``` `ls` 通过检查标准输出是否是 TTY(使用 `isatty()` 系统调用)来决定是否输出颜色代码。管道不是 TTY,所以颜色被关闭。 **2. 为什么 `sudo` 有时会提示输入密码失败?** `sudo` 需要从 TTY 读取密码。如果你在一个没有 TTY 的环境中运行 `sudo`(例如某些 CI 环境),它就无法工作。 **3. 为什么 `screen` 和 `tmux` 能"保持"会话?** `screen` 和 `tmux` 本身就是终端模拟器(运行在终端里的终端模拟器)。它们创建自己的 PTY,Shell 连接到这个 PTY。当你断开 SSH 连接时,真正的终端模拟器消失了,但 `tmux` 创建的 PTY 和连接到它的 Shell 仍然存在于服务器上。 **4. 理解 `stty` 命令** `stty` 命令直接操作 TTY 层的设置: ```bash stty -echo # 关闭回显(输入密码时脚本里常用) stty echo # 重新开启回显 stty -a # 查看当前 TTY 的所有设置 ``` --- ## 总结 | 层次 | 代表 | 职责 | |------|------|------| | 终端模拟器 | iTerm2, GNOME Terminal, Alacritty | 图形渲染、转义序列解释、按键捕获 | | TTY / PTY | `/dev/pts/N`(内核模块) | 数据路由、行规程、信号生成 | | Shell | bash, zsh, fish | 命令解析、进程管理、脚本执行 | 这三层各自解决了不同的问题,通过清晰的接口相互协作。Unix 的设计哲学在这里体现得淋漓尽致:每个组件做好一件事,通过标准化的接口(文件描述符、设备文件)组合在一起,形成一个灵活而强大的整体。 下次当你打开终端窗口,看到那个闪烁的光标时,你知道自己看到的不只是一个"终端"——而是三个精心设计的软件层,在内核与用户空间之间默默协作的成果。

0 人收藏 0 人点赞
#unix

@TrisH0x2A: 2008年,James Molloy撰写了一篇免费教程,教人们如何从零开始用C和汇编构建一个类UNIX操作系统……

X AI KOLs Following · 2026-06-02 缓存

一条推文提到了James Molloy于2008年发布的免费教程《Roll Your Own Toy UNIX Clone OS》,该教程教授如何用C和汇编从零开始构建一个类Unix内核,涵盖引导加载程序、内存管理、文件系统和多任务处理。

0 人收藏 0 人点赞
#unix

Openrsync:由OpenBSD团队开发的rsync实现

Hacker News Top · 2026-05-30 缓存

Openrsync是OpenBSD团队采用BSD许可重新实现的rsync,兼容现代rsync协议并支持Unix系统。

0 人收藏 0 人点赞
#unix

东德(GDR)的Unix(1990)

Hacker News Top · 2026-05-29 缓存

关于东德(GDR)早期采用Unix的历史回顾,详细描述了卡尔·马克思城工业大学在20世纪80年代为将C编译器移植到ESER IBM兼容大型机并引入Unix所做的努力。

0 人收藏 0 人点赞
#unix

remind(1) -- 日历和闹钟程序

Lobsters Hottest · 2026-05-23 缓存

Remind 是一个功能丰富的日历和闹钟程序,具有脚本语言、多种输出格式,并支持希伯来历法和多种语言。

0 人收藏 0 人点赞
#unix

花括号:Unix和C语言的演变

Hacker News Top · 2026-05-20 缓存

详细探讨了早期Unix系统在Teletype Model 33上如何输入花括号,涵盖了ASCII 1963、三字符组、双字符组以及终端驱动程序转换。

0 人收藏 0 人点赞
#unix

TTY 解密 (2008)

Hacker News Top · 2026-05-19 缓存

详细解释Linux和UNIX中的TTY子系统,涵盖从电传打字机到现代模拟终端的历史,以及线路规程的作用。

0 人收藏 0 人点赞
#unix

管道、Fork 和僵尸进程

Hacker News Top · 2026-05-14 缓存

本文来自哈佛大学的 CS 61 课程,涵盖了 Unix 中的管道、Fork 和僵尸进程概念,解释了在关闭时管道如何自动终止程序,以及如何使用管道实现对子进程的阻塞等待。

0 人收藏 0 人点赞
#unix

面向类 UNIX 系统的 9p 文件服务器守护进程

Lobsters Hottest · 2026-05-10 缓存

kamid 是一个免费的、可移植的 9p 文件服务器守护进程,适用于类 UNIX 系统,强调安全性和正确性。本文介绍了 0.2 版本,包含安装指南和源代码仓库。

0 人收藏 0 人点赞
#unix

Plexus P/20 模拟器

Hacker News Top · 2026-04-19 缓存

一款全新的开源 WebAssembly 模拟器,重现 1980 年代 Plexus P/20 Unix 服务器,让用户可在浏览器中运行 SystemV Unix。

0 人收藏 0 人点赞
← 返回首页

提交意见反馈