第八章结尾的死因很明确:ssh 一断,sshd 一死,pty 主面一关,你的程序被 SIGHUP 连坐处死。要让程序活下来,思路只有一条:别让你的程序挂在「会死的终端」上,给它换一台永远不会死的假终端。tmux 干的就是这件事——它自己在服务器上常驻,扮演那台不死终端。
tmux 的真身:服务器上的一个常驻进程
第一次敲 tmux 时,发生了两件事:
- 服务器上启动了一个 tmux server 进程(开机不关机的那种,只要你不杀它,它一直在);
- 启动了一个 tmux client,连上 server,把你的屏幕画出来。
然后,server 为每个窗格申请一对 pty:主面自己拿着,从面启动 shell。结构变成了这样——
你的程序 → tmux 的 pty → tmux server(常驻)→ tmux client → ssh → 你的屏幕
关键就在中间这一层:你的程序挂在 tmux 的 pty 上,而不是 ssh 的 pty 上。ssh 断线,死的是 tmux client;server 纹丝不动,pty 主面好端端的,程序完全无感,继续跑。下次重连,tmux attach,新 client 连上老 server——一切如旧。
一句话理解 tmux:它在服务器上养了一台永不下班的「假终端值班员」。你上班时它把屏幕转播给你看,你下班(断线)它继续帮你盯着,程序从头到尾只跟值班员打交道,根本不知道你走过。
为什么重新 attach,屏幕瞬间原样恢复
这里有个值得咂摸的细节:你 detach 三天后 attach 回来,屏幕上 vim 开着、日志停在原地、颜色分毫毕现。tmux 怎么做到的?
用第三章的模型一想就通:tmux server 拿着每个 pty 的主面,所以它必须执行终端的全部职责——它内部就维护着每个窗格那张「字符表格」和回滚缓冲区,逐字节执行转义序列。attach 时,server 把表格当前状态发给 client,client 用转义序列把它重绘到你屏幕上。所以「恢复」不是录像回放,是把实时维护的表格重新抄一份给你。
这也解释了一个 tmux 小怪癖:在 tmux 里用鼠标滚轮,滚的是 tmux 自己的回滚缓冲区(有时要按 Ctrl-b [ 进入复制模式才能滚),因为外层终端看到的只是一块不断重绘的画面,真正的历史在 server 手里。
client 只是遥控器:一屏多看的魔术
既然 client 只是「连上 server 的显示器+键盘」,那一个 server 当然可以挂多个 client:你在公司 attach,回家换个设备再 attach 同一个会话,两个屏幕实时同步——这是命令行结对编程的经典玩法。甚至两个 client 窗口大小不一也行,server 会按较小者裁剪(角落那些小圆点标记的就是「有内容但你屏幕太小看不到」的区域)。
三层套娃:session、window、pane
tmux 里的组织结构是三层:
- session(会话):一组工作现场,比如「部署」「写代码」各一个。server 可以同时养多个 session。
- window(窗口):session 里的一整屏,像浏览器标签页。
- pane(窗格):window 切出来的小格子,每个窗格一对自己的 pty、一个自己的 shell。
操作口诀是「先按 Ctrl-b 请示,再按功能键」(Ctrl-b 叫前缀键,避免和你正常敲的字冲突):
Ctrl-b d:detach,走人(会话活着)。Ctrl-b %/Ctrl-b ":竖切 / 横切窗格。Ctrl-b 方向键:在窗格间跳。Ctrl-b [:进入复制模式,可以滚动翻历史。
命令行侧的常用三件:tmux new -s work(开个叫 work 的会话)、tmux ls(看看服务器上有哪些会话还活着)、tmux attach -t work(接回去)。
穷人版替代,以及它们的边界
如果只是想「程序别跟着 ssh 陪葬」,其实有更轻的办法:nohup cmd &(对 SIGHUP 免疫并丢到后台)、shell 的 disown、或者 setsid。它们都能保住进程不死。但 tmux 保的不只是进程,是整个工作现场:开着一半的 vim、跑到一半的交互式命令、五个窗格的布局、翻得回去的历史——这些是 nohup 给不了的。进程活着 ≠ 现场还在。
顺带解开第四章埋的扣:tmux 里 TERM 会变成 tmux-256color 之类——因为对程序来说,它的「终端」现在是 tmux,能力表得按 tmux 的报。套了两层终端,暗号要过两道手。
一句话总结:tmux = 服务器上常驻的 server,替每个窗格举着 pty 主面并实时维护屏幕状态;你 attach 的只是可随时替换的 client。断线死的是 client,现场永远在。
下一章回头清理一件一直没细说的事:Ctrl-C 到底「发」给了谁?为什么有时候按了没反应?这要把「信号」这个机制讲透。