终端里到底有什么 书架

第 10 章 / 共 12 章

第十章 · Ctrl-C 的真相

用过命令行的人都按过 Ctrl-C。但它到底干了什么?常见的误解是「Ctrl-C 给程序发了个字符」。真相更有意思:Ctrl-C 产生的那个字节,程序根本收不到——它半路就被 tty 层截胡,翻译成了另一种东西:信号。

信号:内核递给进程的小纸条

信号(signal)是 Unix 里内核通知进程「出事了」的机制。它不是数据流,不带内容,只有一个编号和名字,像一张写着暗号的小纸条:SIGINT(2 号,「打断一下」)、SIGTERM(15 号,「请收拾东西退出」)、SIGHUP(1 号,「终端挂了」)、SIGKILL(9 号,「立即处决,不得申诉」)。

进程收到信号有三条路:按默认动作办(大多是终止);捕获它,执行自己登记的处理函数(比如先把文件存盘再退出);或者忽略它。只有两个例外:SIGKILL 和 SIGSTOP 既不能被捕获也不能被忽略——它们由内核直接执行,这就是 kill -9 是「最后手段」的原因:被杀的进程连一句遗言、一次清理都没机会说,可能留下半截写坏的文件。优雅的顺序永远是:先 15(请退),不听再 9(枪毙)。

Ctrl-C 的完整路径

现在把链路走一遍。你在规范模式的终端里按 Ctrl-C:

  1. 键盘送出数字 3 的字节;
  2. tty 层(第二章那位打字机幽灵)一看:3 是配置里的「打断键」,于是不把它交给程序,而是让内核给「前台进程组」所有成员各发一张 SIGINT;
  3. 程序没登记处理函数,按默认动作终止。shell 发现孩子死了,打印下一个提示符。

三个推论立刻可解释:

Ctrl-Z 与后台戏法

Ctrl-Z(字节 26)同理,被翻译成 SIGTSTP「请暂停」。程序被当场冻住,shell 拿回前台。然后:

启动时就加 &(如 python train.py &)是「生下来就在后台」。注意边界:后台程序往终端写输出还是可以的(会糊你一脸),但它想读键盘时会被自动冻结——终端的键盘只归前台进程组用。这也再次印证第八章:后台程序仍挂在这个终端的 pty 上,ssh 一断照样被 SIGHUP 连坐——后台 ≠ 免疫,免疫要靠 tmux 或 nohup。

窗口拉大拉小时,程序怎么知道

你拖动 iTerm2 窗口的边缘把它拉宽,vim 立刻重排内容填满新宽度。谁通知它的?又是一张纸条:模拟器把新尺寸告诉 pty,内核给前台进程组发 SIGWINCH(window change)。程序收到后重新查询尺寸、重画。这就是「窗口尺寸」为什么也是 pty 的属性之一(第七章提过 pty 附赠全套打字机行为,这是其中一件)。顺便说,stty size 能看到当前的行列数。

退出码:死后的验尸报告

程序死了还留一句话:退出码。echo $? 看上一个命令的。约定:0 = 成功,非 0 = 各种失败;如果一个程序是被信号杀死的,退出码是 128 + 信号编号——所以看到 130,就是 128+2,被 Ctrl-C(SIGINT)打死的;看到 137,128+9,是被 kill -9 处决的(内存超限被系统杀掉时也常是它)。看日志时这两个数字是最好的线索。

一句话总结:Ctrl-C/Z 不是字符,是 tty 层翻译成信号发给「前台进程组」的小纸条;信号可以被捕获或忽略(9 号和 STOP 除外);窗口缩放靠 SIGWINCH;退出码 128+n 告诉你程序死于哪张纸条。

原理部分到这就齐了。下一章换个姿势:不往下挖了,往上走——有了这套心智模型,登录一台陌生服务器后,工程师到底怎么干活。

终端里到底有什么
费曼式写法 · 由多个 AI 代理撰写与互相审校