0. 理解 tmux

tmux 是一个运行在服务器上的“长期终端工作台”。

普通 SSH:

本地电脑 SSH 连接
远程 bash
python train.py

SSH 断了,bash 和 Python 任务可能受影响。

tmux:

本地电脑 SSH 连接
tmux client
tmux server
bash
python train.py

SSH 断了,只是 tmux client 断开,tmux server 还在,里面的 bash 和 Python 任务继续运行。


1. tmux 解决的核心问题

科研实验、深度学习训练、远程服务器开发经常有长时间任务:

python train.py

普通 SSH 连接存在几个风险:

网络断开
VS Code Remote SSH 掉线
电脑关机
终端窗口误关
本地代理不稳定

这些都会导致远程终端断开。

问题是:

程序看起来是在“服务器上跑”,但它实际上依赖当前 SSH 终端环境。

tmux 的作用是把:

网络连接
终端会话
实验进程

三者拆开。

拆开后:

网络连接可以断
终端会话留在服务器
实验进程继续运行

2. tmux 计算机知识

tmux 主要涉及两大类知识:

操作系统知识:核心
计算机网络知识:使用场景

2.1 操作系统知识

tmux 背后强相关的操作系统知识包括:

进程 process
父子进程 fork/exec
会话 session
进程组 process group
控制终端 controlling terminal
信号 signal
SIGHUP / SIGINT
TTY / PTY
文件描述符 stdin/stdout/stderr
I/O 多路复用 select/poll/epoll
Unix Domain Socket

2.2 计算机网络知识

tmux 常用于 SSH 远程服务器场景,涉及:

SSH 协议
TCP 长连接
网络断连
远程登录
客户端/服务器模型
VS Code Remote SSH
NAT / 防火墙 / 心跳超时

准确说:

tmux 本身主要是操作系统工具,但它解决的是远程网络连接不稳定带来的实验中断问题。


3. 普通 SSH 终端中,程序是怎么跑的?

SSH 到服务器后执行:

python train.py

进程关系大概是:

sshd
└── bash
    └── python train.py

其中:

sshd   负责远程登录连接
bash   负责解释你的命令
python 负责运行实验程序

从代码角度看,bash 执行命令大概是:

pid_t pid = fork();

if (pid == 0) {
    execlp("python", "python", "train.py", NULL);
} else {
    waitpid(pid, NULL, 0);
}

也就是:

bash fork 出子进程
子进程 exec 成 python
python train.py 开始运行

4. 为什么 SSH 断了,程序可能会停?

普通 SSH 结构可以理解为:

你的电脑键盘/屏幕
SSH 客户端
        ↓ TCP 网络连接
SSH 服务端 sshd
伪终端 PTY
bash
python train.py

当网络断开:

TCP 连接断开
SSH 连接断开
远程终端关闭
bash 收到 SIGHUP
bash 退出
子进程 python 可能被影响

这里最关键的是:

SIGHUP = hang up signal = 终端挂断信号

早期 Unix 时代,用户通过物理终端连接主机。终端线断了,系统就给相关进程发送 SIGHUP

现代 SSH 继承了这个机制。

所以普通 SSH 中:

SSH 断线
≈ 控制终端消失
≈ bash 收到 SIGHUP
≈ 相关进程可能退出

5. 操作系统中的几个关键概念

5.1 进程 process

每个运行中的程序都是进程。

例如:

python train.py

就是一个 Python 进程。

可以用下面命令查看:

ps -o pid,ppid,sid,pgid,tty,stat,cmd

字段含义:

PID   进程 ID
PPID  父进程 ID
SID   会话 ID
PGID  进程组 ID
TTY   控制终端
STAT  进程状态
CMD   命令

普通 SSH 中可能看到:

PID    PPID   SID    PGID   TTY      STAT   CMD
1200   1190   1200   1200   pts/0    Ss     -bash
1300   1200   1200   1300   pts/0    R+     python train.py

重点:

bash 和 python 都绑定在 pts/0 这个终端上

5.2 会话 session

Linux 本身就有 session 概念,不是 tmux 独有。

SSH 登录后,系统会创建一个登录会话:

SSH 登录会话
└── bash
    └── python train.py

一个 session 通常绑定一个控制终端。


5.3 进程组 process group

进程组用于作业控制。

例如你在终端里运行:

python train.py

它会成为当前前台进程组。

你按:

Ctrl+C

终端会把 SIGINT 发给前台进程组。

所以:

Ctrl+C
前台进程组
python train.py

这就是为什么 Ctrl+C 能停掉当前程序。


5.4 控制终端 controlling terminal

可以用:

tty

查看当前终端:

/dev/pts/0

普通 SSH 中:

bash 和 python 的控制终端 = SSH 创建的 /dev/pts/0

问题是:

如果 SSH 连接断开,这个控制终端可能关闭,进程可能收到 SIGHUP。

tmux 的核心目标,就是让程序不要直接依赖 SSH 的控制终端。


6. tmux 改变了什么?

tmux 的核心改变是:

让你的程序不再直接依赖 SSH 终端,而是依赖 tmux 创建的伪终端。

不用 tmux:

SSH 终端
└── bash
    └── python train.py

使用 tmux:

tmux server
└── bash
    └── python train.py

SSH 只负责连接 tmux:

你的电脑
    ↓ SSH
tmux client
tmux server
bash
python train.py

所以 SSH 断了以后:

tmux client 消失
tmux server 还在
bash 还在
python 还在

7. tmux 的 server-client 架构

tmux 分为两部分:

tmux server
tmux client

7.1 tmux server

负责真正管理:

session
window
pane
PTY
shell
进程
历史输出
终端状态

7.2 tmux client

负责:

接收你的键盘输入
显示 tmux server 的画面
把输入转发给 server

结构:

SSH 连接
└── tmux client
   tmux server
   pane 对应的 PTY
       bash
  python train.py

当你执行:

tmux new -s mt

tmux 会创建一个 server,并创建一个名为 mt 的 session。

当你按:

Ctrl+b d

只是断开 client:

tmux client 退出
tmux server 继续运行
bash/python 继续运行

这就是 detach。


8. tmux 的核心技术:PTY 伪终端

8.1 什么是 PTY?

PTY 是 pseudo terminal,伪终端。

它通常有两端:

PTY master
PTY slave

结构:

程序 A  ↔  PTY master  ↔  PTY slave  ↔  程序 B

在 tmux 中:

tmux server 控制 PTY master
bash/python 连接 PTY slave

从 bash 的角度看:

我连接在一个正常终端上

但实际上这个终端是 tmux 创建出来的。


8.2 tmux pane 与 PTY 的关系

tmux 中每个 pane 背后通常都有一个 PTY。

tmux server
└── session
    └── window
        └── pane
            └── PTY
                └── bash
                    └── python train.py

所以 pane 不是单纯的视觉分屏,它背后是真正独立的终端环境。


9. 用 Python 理解 PTY

下面是一个极简版“伪 tmux pane”。

它创建一个 PTY,并在里面启动 bash。

# mini_pty.py
import os
import pty
import subprocess
import select
import sys
import tty
import termios

# 创建一对伪终端:master 和 slave
master_fd, slave_fd = pty.openpty()

# 在 slave 端启动 bash
proc = subprocess.Popen(
    ["/bin/bash"],
    stdin=slave_fd,
    stdout=slave_fd,
    stderr=slave_fd,
    close_fds=True
)

os.close(slave_fd)

print("启动了一个 bash,PID =", proc.pid)
print("现在你输入的内容会被转发给这个 bash")
print("输入 exit 可以退出")

# 当前终端切换为 raw 模式,方便逐字符转发
old_settings = termios.tcgetattr(sys.stdin)
tty.setraw(sys.stdin.fileno())

try:
    while True:
        rlist, _, _ = select.select([sys.stdin, master_fd], [], [])

        # 用户键盘输入 → PTY master → bash
        if sys.stdin in rlist:
            data = os.read(sys.stdin.fileno(), 1024)
            if not data:
                break
            os.write(master_fd, data)

        # bash 输出 → PTY master → 用户屏幕
        if master_fd in rlist:
            data = os.read(master_fd, 1024)
            if not data:
                break
            os.write(sys.stdout.fileno(), data)

finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

运行:

python mini_pty.py

你会进入一个由 Python 托管的 bash。

这个程序的结构是:

你的键盘输入
Python 程序
PTY master
PTY slave
bash

bash 输出
PTY slave
PTY master
Python 程序
你的屏幕

这就是 tmux 的核心机制。

真实 tmux 更复杂,但最底层的思路类似。


10. tmux 如何处理输入和输出?

普通终端:

键盘输入 → bash/python
bash/python 输出 → 屏幕

tmux 中:

键盘输入
tmux client
tmux server
PTY master
PTY slave
bash/python

输出反过来:

bash/python 输出
PTY slave
PTY master
tmux server
tmux client
屏幕

tmux 接管了:

stdin
stdout
stderr
终端尺寸
颜色控制
光标位置
滚动缓冲区
窗口状态
分屏布局

11. detach 和 attach 的本质

11.1 detach

你按:

Ctrl+b d

表面上是退出 tmux。

本质上是:

断开 tmux client
保留 tmux server

detach 前:

SSH
└── tmux client
   tmux server
      bash
   python train.py

detach 后:

tmux server
  bash
python train.py

所以 detach 不会杀掉程序。


11.2 attach

你执行:

tmux attach -t mt

本质是:

新建一个 tmux client
连接旧的 tmux server
显示旧 session 的当前状态

attach 不是恢复程序。

更准确地说:

程序从来没死,只是你重新连接回去了。


12. tmux server 和 client 怎么通信?

tmux server 和 client 通常通过 Unix Domain Socket 通信。

路径类似:

/tmp/tmux-0/default

完整结构:

你的电脑
    ↓ SSH / TCP
服务器上的 tmux client
    ↓ Unix Domain Socket
服务器上的 tmux server
    ↓ PTY
bash / python

注意区分:

SSH 是本地电脑到服务器之间的网络通信
tmux client/server 是服务器内部通信

这就是网络知识和操作系统知识的结合点。


13. tmux 的事件循环

tmux 要同时处理很多东西:

用户键盘输入
pane 0 输出
pane 1 输出
pane 2 输出
client 连接
client 断开
窗口大小变化

所以 tmux 需要事件循环。

极简伪代码:

while True:
    readable_fds = select.select(all_fds, [], [])[0]

    for fd in readable_fds:
        if fd == client_fd:
            data = read_from_client()
            send_to_active_pane(data)

        elif fd in pane_master_fds:
            output = read_from_pane(fd)
            save_to_buffer(output)
            render_to_client(output)

本质是:

从 client 读输入 → 发给对应 pane
从 pane 读输出 → 保存并显示给 client

14. 为什么 tmux 可以保存历史输出?

因为程序输出不是直接显示到你的本地屏幕,而是先进入 tmux server。

python 输出日志
PTY
tmux server
tmux buffer
tmux client
屏幕

tmux server 会维护滚动缓冲区:

scrollback buffer

所以你 detach 后,程序继续输出,tmux server 仍然能接收一部分历史内容。

attach 回来后,你可以看到当前画面,也可以进入滚动模式查看历史。


15. 为什么 tmux 可以分屏?

因为 tmux 本质上是一个终端窗口管理器。

假设你的屏幕是:

120 列 × 40 行

tmux 可以切成:

左边 60 列 × 40 行
右边 60 列 × 40 行

每个 pane 背后有独立的 PTY:

pane 0 → PTY 0 → bash 0
pane 1 → PTY 1 → bash 1
pane 2 → PTY 2 → bash 2

tmux server 负责:

把键盘输入发给当前 pane
把不同 pane 的输出画到不同屏幕区域
处理边框
处理状态栏
处理窗口切换

16. Ctrl+C 在 tmux 中为什么仍然有效?

你在 tmux 里按:

Ctrl+C

路径是:

键盘 Ctrl+C
tmux client
tmux server
PTY master
PTY slave
前台进程组
python train.py

所以 Python 仍然能收到正常的 SIGINT

tmux 并不是粗暴地“截获一切”,而是正确模拟了一个终端环境。


17. tmux 与 nohup 的区别

17.1 nohup

nohup python train.py > train.log 2>&1 &

含义:

忽略 SIGHUP
输出写入 train.log
进程放到后台

它保护的是一个命令。

17.2 tmux

tmux 保护的是整个终端会话:

当前目录
环境变量
conda/micromamba 环境
shell 状态
多个窗口
多个 pane
实时输出
交互式操作

对比:

工具本质适合场景
&放到 shell 后台临时后台任务
nohup单命令忽略 SIGHUP简单长任务
disown从 shell 作业表移除避免 shell 退出影响
screen老牌终端复用器类似 tmux
tmux完整终端会话管理科研实验、远程服务器
systemd系统服务管理长期服务

对科研实验来说,tmux 更合适。


18. tmux 的层级结构

tmux 的核心层级:

tmux server
└── session
    ├── window
    │   ├── pane
    │   │   └── PTY
    │   │       └── bash
    │   │           └── python train.py
    │   └── pane
    │       └── PTY
    │           └── watch nvidia-smi
    └── window
        └── pane
            └── PTY
                └── vim

18.1 session

session 是一组工作空间。

例如:

tmux new -s flash
tmux new -s 3dgs
tmux new -s monogs

可以分别用于不同项目。

18.2 window

window 类似浏览器标签页。

例如:

flash session
├── window 0: preprocess
├── window 1: train
└── window 2: monitor

18.3 pane

pane 是分屏区域。

例如:

window 0
├── pane 0: python train.py
└── pane 1: watch nvidia-smi

19. 用伪代码理解 tmux 数据结构

一个极简 tmux 可以这样抽象:

class Pane:
    def __init__(self):
        self.master_fd = None
        self.slave_fd = None
        self.process = None
        self.buffer = []

class Window:
    def __init__(self, name):
        self.name = name
        self.panes = []
        self.active_pane = 0

class Session:
    def __init__(self, name):
        self.name = name
        self.windows = []
        self.active_window = 0

class TmuxServer:
    def __init__(self):
        self.sessions = {}

    def new_session(self, name):
        session = Session(name)
        window = Window("0")
        pane = Pane()

        window.panes.append(pane)
        session.windows.append(window)
        self.sessions[name] = session

真实 tmux 比这个复杂很多,但核心结构类似。


20. 用伪代码理解 detach/attach

20.1 detach

def detach_client(client):
    clients.remove(client)
    client.close()

注意:

只移除 client
不删除 session
不关闭 pane
不杀 bash
不杀 python

20.2 attach

def attach_client(client, session_name):
    session = sessions[session_name]
    client.session = session

    active_pane = session.current_pane()
    client.send(active_pane.buffer)

attach 的本质是:

新的 client 连接已有 session
重新显示已有 pane 的状态

21. 真实 tmux 源码可以按这些模块理解

tmux 是 C 写的。

可以按模块理解:

server.c        后台 server 主逻辑
client.c        客户端连接逻辑
session.c       session 管理
window.c        window 管理
window-pane.c   pane 管理
tty.c           终端输入输出
screen.c        屏幕缓冲区
input.c         解析终端控制序列
cmd-*.c         各种 tmux 命令

执行:

tmux new -s mt

大概流程:

解析 new-session 命令
创建 session
创建 window
创建 pane
创建 PTY
在 PTY slave 端启动 shell
server 进入事件循环
client 连接 server 显示画面

执行:

Ctrl+b d

大概流程:

client 发送 detach-client 命令
server 解除 client 和 session 的绑定
client 退出
server 保留 session/window/pane

执行:

tmux attach -t mt

大概流程:

新 client 连接 server socket
请求 attach 到 mt session
server 把 session 当前画面发给 client
后续继续转发输入输出

22. 观察 tmux 原理的实验命令

22.1 查看当前终端

普通 SSH 中:

tty

可能输出:

/dev/pts/0

进入 tmux 后再执行:

tty

可能输出:

/dev/pts/2

说明 tmux 创建了新的伪终端。


22.2 查看是否在 tmux 中

echo $TMUX

在 tmux 中可能输出:

/tmp/tmux-0/default,12345,0

不在 tmux 中一般为空。


22.3 查看进程树

ps -ef --forest

或者:

pstree -p

你可能看到:

tmux(12345)
 └─bash(12360)
    └─python(12400)

说明 Python 是挂在 tmux 下面的。


22.4 查看进程关系

ps -o pid,ppid,sid,pgid,tty,stat,cmd

可以观察:

PID
PPID
SID
PGID
TTY

这能帮助理解:

进程
父子进程
会话
进程组
控制终端

23. 推荐科研服务器使用流程

以后只要任务超过 5 分钟,都建议用 tmux。

23.1 新建会话

tmux new -s experiment_name

例如:

tmux new -s flash
tmux new -s 3dgs
tmux new -s monogs

23.2 在 tmux 中配置环境

例如:

export PATH="$HOME/.local/bin:$PATH"
eval "$(micromamba shell hook -s bash)"

micromamba activate /root/autodl-tmp/envs/mt39

cd /root/autodl-tmp/flashavatar_tools/metrical-tracker

23.3 运行实验

python train.py

或者:

python tracker.py

23.4 暂时离开

Ctrl+b
然后按 d

注意不是三个键同时按。

23.5 查看已有会话

tmux ls

23.6 回到会话

tmux attach -t flash

简写:

tmux a -t flash

23.7 杀掉会话

确认任务不需要后:

tmux kill-session -t flash

24. 常用快捷键

tmux 默认前缀键是:

Ctrl+b

常用操作:

Ctrl+b d       detach,退出但不停止任务
Ctrl+b c       新建 window
Ctrl+b n       下一个 window
Ctrl+b p       上一个 window
Ctrl+b "       上下分屏
Ctrl+b %       左右分屏
Ctrl+b x       关闭当前 pane
Ctrl+b [       进入滚动模式
q              退出滚动模式

常用命令:

tmux new -s name          # 新建会话
tmux ls                   # 查看会话
tmux a -t name             # 进入会话
tmux kill-session -t name  # 删除会话

25. tmux 的边界:它不能解决什么?

tmux 能解决:

SSH 断开
VS Code Remote SSH 掉线
本地终端关闭
网络不稳定

tmux 不能解决:

服务器关机
AutoDL 实例被释放
程序自己崩溃
CUDA OOM
磁盘满了
环境配置错误
训练代码报错

所以:

tmux 防的是连接断开,不是防服务器宕机,也不是防程序错误。


26. 最重要的心智模型

普通 SSH:

SSH 连接控制终端
终端控制 bash
bash 控制 python

所以 SSH 断开,链条容易断。

tmux:

tmux server 控制伪终端
伪终端控制 bash
bash 控制 python

SSH 连接只控制 tmux client

所以 SSH 断开,只是 client 没了:

tmux client 消失

真正任务还在:

tmux server
└── bash
    └── python train.py

27. 最终总结

tmux 的本质是:

一个运行在服务器上的用户态终端复用器。

它通过:

server-client 架构
PTY 伪终端
Unix Domain Socket
I/O 转发
终端缓冲区
session/window/pane 管理

实现:

会话保持
断线重连
多窗口
分屏
历史输出保存
长期任务托管

从计算机专业角度看,tmux 是一个非常好的综合案例。

它把这些知识串起来:

操作系统:
进程、父子进程、会话、进程组、信号、控制终端、PTY、文件描述符、I/O 多路复用

计算机网络:
SSH、TCP、远程连接、网络断开、客户端/服务器模型

一句话总结:

程序不是跑在“窗口”里,而是跑在操作系统的进程模型里;窗口只是你观察和控制程序的入口。tmux 的价值,就是把这个入口和真正运行的程序解耦。