终端窗口关掉了,正在跑的活会不会死:三种起法各自的下场

在一台阿里云 ECS 上掐掉客户端的 ssh 进程,三种起法各自的下场:前台那条停在第 12 行,nohup 加 disown 从 12 行跑到 74 行,放进 tmux 的同样跑到 74 行。

本章只回答一个问题:终端窗口被关掉之后,正在运行的进程会不会因此结束。 四条结论里三条是测量出来的(其中前台那条换了第二台机器才测量到),第四条写得很短,因为没有测量到。

关掉终端窗口时,内核发出的挂断信号

终端窗口关掉时,内核会给终端的前台进程组(在终端前台运行、结束时会一起收到信号的一组进程)发一个挂断信号,很多程序就此结束。程序要不要跟着结束,取决于上级进程(谁把这条命令生出来,术语叫父进程)是谁、进程有没有被从终端的会话里摘出去。

一句话:进程只认上级进程是谁,不认窗口开不开。

关键就在这一句。下面这张图要看的是每一格右边那一句------同一个终端被掐掉,三格的结果并不一样。

图: 三种起法各自的上级进程是谁:屏一前台直跑,进程的父亲是终端会话,窗口关掉时该终端的前台进程组会收到挂断信号,很多程序就此结束,这一格在第二台机器上测量到了------日志停在第 4 行、进程变成僵尸;屏二用 nohup 加 disown 起的进程,父进程是 1 号,原来的 shell 退出之后日志从 13 行涨到 21 行;屏三跑在 tmux 会话里的进程,宿主是 tmux 自身一个不属于任何终端的进程,伪终端被掐断之后会话仍在、日志从 6 行涨到 23 行。图底三条:关掉终端窗口打的是窗口不是进程的父亲;反过来 tmux kill-server 之后所有会话一起没了;断网的情况本章没有测

测量到的第一条:分离出去的进程活着

在一台 Linux 实验机上跑一个每秒钟往日志里写一行的循环,用 nohup 起、再 disown 把循环从当前 shell 的任务表里摘掉,然后让启动循环的 shell 真的退出(这台机器上每条命令都由一个独立 shell 执行、执行完就退出,所以"换一条命令"就等于换了一个 shell)。

结果:日志行数从 13 涨到 21,进程还在,ps -ef 显示循环进程的上级进程编号已经是 1。

bash 复制代码
13 /data/proof/ticks.log   ← 起完之后 6 秒
21 /data/proof/ticks.log   ← 换一个 shell、再等 8 秒
root 176 1 ... sh -c for i in $(seq 1 120); do echo "tick $i ..." >> /data/proof/ticks.log; sleep 1; done
       ↑   ↑
     进程号  父进程已经是 1

这一条可以直接搬到你自己的机房里:把一个长活交给 agent 时,别把长活挂在 ssh 进去所用的终端上。

测量到的第二条:tmux 会话也活着,但要先确认掐断的是哪一条连接

同一台机器上建一个分离的 tmux 会话跑同样的循环,然后用一个假终端连进去看一眼,3 秒后掐断假终端(timeout 到点杀掉,退出码 124------这是故意掐断,不是失败)。

结果:会话还在(tmux ls 仍是同一条会话、同一个创建时间),日志从 6 行涨到 23 行;直到执行 tmux kill-server,会话才消失。

所以 tmux 带来的不是"进程变强了",而是多出一层不属于任何终端的宿主 :窗口关掉只是没有人看着 tmux 会话,宿主还在运行。真正需要小心的是反过来的那一步------执行 kill-server 之后,所有会话一起消失。

测量到的第三条:前台直跑那一格,这一轮记录到了现场

前两格在云端那台 Linux 实验机上量。那台机器上没有 sshd、不能被连进来 ,所以"关掉窗口"这一步只能用 script 造的伪终端(由程序模拟出来的终端设备)代替。第三格换了一台能连进来的机器:一台阿里云 ECS(CentOS 8,2 核 2G,sshd 在,tmux 没装),进出的通道是厂商自带的网页终端。做法跟前两格一样------一条每秒钟往日志写一行的循环,这一次前台 运行在 script 造的伪终端里,4 秒之后把伪终端关掉。

bash 复制代码
$ wc -l /root/amr-proof/fg.log
4 /root/amr-proof/fg.log
$ tail -1 /root/amr-proof/fg.log
fg 4 15:19:55                ← 掐断就在这一秒

三分钟后重连回去查,那行循环进程变成了 [ticker.sh] <defunct>------僵尸,意思是进程已经死了,父进程却卡在原地没有回收已经结束的子进程 ;wc -l 隔 3 秒量两次都还是 4 行。伪终端一旦消失,前台进程就会停下,这一句从前是常识陈述,现在是测量到的。

这一轮还测量出两件附带事实,这两件附带事实决定了后面几格能不能继续写:

  1. 同一条循环改用 nohup ... & 启动,想做对照组,结果没有测成 :连输出文件都没有创建出来。原因是进程在"已经 fork(fork 指创建子进程)、还没有执行到 nohup"的那一刻就被挂断信号打掉------伪终端关得太快,nohup 还没来得及把"忽略挂断信号"装上。这一条不能读成"nohup 也会死" ;当时 nohup 能活下来的证据只有第一条结论那台 Linux 实验机上的 13 行涨到 21 行。后面第四条结论补做了这一格 :改成先确认日志已经写出 12 行再掐,nohup 正常活过了断连,来源从这一个变成两个。
  2. 关掉网页终端的标签页,不等于服务器侧的连接断开 :重连之后 who 里的 pts/0 仍然存在,timeout 和 script 两个进程还挂在 pts/0 上。所以"掉线之后远端的进程会不会死"这一格,从厂商自带的网页终端测不出来,需要用真正的 ssh 客户端掐断连接再量。

测量到的第四条:客户端掉线时,三格各自的结果

第二条附带事实已经说明:厂商自带的网页终端测不了"掉线之后远端的进程会不会死"。这一格补上了------用真正的 ssh 客户端连进去,然后把客户端这一侧的 ssh.exe 进程结束掉 。不碰服务器网络、不碰本机网络配置、不拔网线:ssh.exe 一结束,TCP 连接被重置,服务器上的 sshd 立刻发现会话消失。

三格各跑一遍同样的四步:启动 → 确认日志已经在增长 → 掐断 → 等待 60 秒后重连读回。掐断之前先做一道自检,who 里那条会话必须消失;不消失就说明根本没有断开,那一格作废。三格都通过了这道自检。

格子 掐之后 60 秒读回 断连那 60 秒里写了几行 还在跑吗 上级进程 终端列
前台直跑 日志停在第 12 行 0 行 否 --- ---
nohup + disown 12 行涨到 74 行 64 行 是 1 ?
tmux 会话 12 行涨到 74 行 63 行 是 tmux 的服务进程 pts/2

表里 tmux 那一行的 pts/2 是重点:不是被掐断的那条 pts/1,而是 tmux 自己新建的一块伪终端。所以"活下来"不是运气------那行循环从启动那一刻起就没有挂在你的连接上。

第一轮实验作废,原因和原始记录如下。 第一轮用 Git Bash 的 ssh ... & 加 kill -9 $! 去掐断,结果三份日志全都一路增长到实验结束:kill -9 结束的是 Git Bash 里的包装进程,ssh.exe 从未停止,服务器全程没有断过连。那一轮"三格全活"的结果作废。 换成 Windows 自身的进程管理(Start-Process -PassThru 取 PID、Stop-Process 结束)并加上 who 自检之后,才得到上面这张表。写在这里是因为这一条与你的操作无关,是工具的性质:掐断客户端的实验,必须先证明客户端真的已经结束。

仍然没有测量到的那一条:真·断网

上面掐断的是客户端进程 ,TCP 连接会被立即重置,服务器当场发现。拔掉网线、关掉 Wi-Fi、路由器掉电这类"不发重置包"的断法,仍然没有测量过。 那种情况下 sshd 可能要等到自身的超时或者 keepalive 才发现连接消失,前台进程收到挂断信号的时点会更晚------会不会最终也停下来,目前还没有证据。想补这一条,需要在场的两台机器,把其中一边的网络真的掐掉。

另外两件这一轮没有做的:每格只跑了一次 ,没有重复三轮;前台那一格这一次没有留下僵尸进程,而上一轮在同一台机器上测量到过 [ticker.sh] <defunct>,差别可能出在父 shell 有没有来得及回收,没有做对照,所以只报现象、不给解释 。原始日志、进程表和自检输出在 evidence/disconnect-three-arms-2026-10-12.md。

另外一条负面事实

这台 Linux 实验机不能被另一台机器连进来 :sshd 不在盘上(ls 直接报不存在),ss -ltnp 的监听列表里没有 22 端口,也没有 tailscale。所以这台 Linux 实验机能当作"跑命令的实验台",不能当作"你的第二台电脑"。真·断网那一格的实验,在这台机器上永远做不出来。

本章的三块屏

模拟器里有三块屏,分别代表前台直跑、nohup 分离、tmux 会话。剧本在 scenario.json,可核对的运行记录在 reference.jsonl, 哪些话有依据、证据是什么等级,全写在 verify.md 里。国内网络环境下的额外差异见 china.md。


这一章的决策表(由 fusion 的投影器从本章记录生成)

这张表不是手写的:它是 fusion 发布的 review-cli.mjs(v0.1.2)读本节的记录文件 chapters/06-kill-the-terminal/reference.jsonl 投影出来的结果,直接贴在下面。表里每一行是一次拍板: 当时被问到什么、选了什么、之后哪一格的状态变了。同一局的随机种子写在记录文件的头部, 所以这张表可以逐字复现。

复盘:关掉终端窗口,这三块屏谁会死(seed 2519033159,拍板 1 次,22 拍,23714 ms,结束于 未结束)

# 第几拍 哪一格 问到什么 / 派了什么活 你选的 它引发的跃迁
1 n=10 a1 你现在要关掉这个终端窗口: 直接关,先看会发生什么 blocked→working、working→done、done→idle

其余 0 条是环境跃迁,不由你的拍板引发。


想自己点这里

这一章的模拟器嵌在我们自己的站点上:xiaomijituan.github.io/agent-machi...

打开就是本章那一局:剧本已经递给它了,点格子、拍板、导出这一局都能做。 掘金会把页面里的交互内容过滤掉,所以这一章在掘金只有图文,能点的那部分在上面那个地址里。 那个站点由本仓库每次合进 main 自动构建,和正文是同一份来源,不是另写的一份演示。

本章剧本全文(复制下来就能在自己的模拟器里玩)

打开你手上的 fusion(离线单文件那份也行),进「剧本库」,把下面这段整段粘进输入框,按「导入」。 导入走的是同一套校验,不合格会当场指出是哪一个字段。

json 复制代码
{
  "schemaVersion": 1,
  "id": "amr-ch06-kill-the-terminal",
  "version": "0.1",
  "name": "关掉终端窗口,这三块屏谁会死",
  "hosts": [
    {
      "id": "lab-linux",
      "role": "Linux 实验机",
      "place": "同一台机器上三种起法,各自等一句话"
    }
  ],
  "tasks": [
    {
      "key": "foreground-job",
      "project": "训练脚本",
      "title": "前台直接跑的长活",
      "lines": {
        "zh": [
          "在一块屏的前台跑着一个要两小时的脚本",
          "它的父亲就是这个终端会话"
        ]
      },
      "question": {
        "text": "你现在要关掉这个终端窗口:",
        "options": [
          {
            "id": "just-close",
            "label": "直接关,先看会发生什么"
          },
          {
            "id": "ctrl-c-first",
            "label": "先手动停掉再关"
          },
          {
            "id": "kill",
            "label": "这块屏不管了,关掉"
          }
        ]
      }
    },
    {
      "key": "detached-job",
      "project": "训练脚本",
      "title": "用 nohup 分离出去的长活",
      "lines": {
        "zh": [
          "同一个脚本,用 nohup 起、并且 disown 掉了",
          "它的父进程已经变成 1,不再挂在这个终端上"
        ]
      },
      "question": {
        "text": "关掉这个终端窗口之前,你想知道的是:",
        "options": [
          {
            "id": "check-log",
            "label": "关完之后换个 shell 去看日志有没有继续长"
          },
          {
            "id": "think-same",
            "label": "觉得反正都一样,不用看"
          },
          {
            "id": "kill",
            "label": "这块屏不管了,关掉"
          }
        ]
      }
    },
    {
      "key": "tmux-job",
      "project": "训练脚本",
      "title": "在 tmux 会话里跑的长活",
      "lines": {
        "zh": [
          "脚本跑在一个 tmux 会话里,这个会话是分离创建的",
          "你正用一个假装出来的终端看着它"
        ]
      },
      "question": {
        "text": "你把这个看进去的终端掐断:",
        "options": [
          {
            "id": "reattach",
            "label": "过一会儿重新 attach 回去看进度"
          },
          {
            "id": "assume-dead",
            "label": "认为会话跟着终端一起没了"
          },
          {
            "id": "kill",
            "label": "这块屏不管了,关掉"
          }
        ]
      }
    }
  ],
  "panes": [
    {
      "host": "lab-linux",
      "task": "foreground-job",
      "kind": "claude",
      "status": "blocked",
      "progress": 30
    },
    {
      "host": "lab-linux",
      "task": "detached-job",
      "kind": "codex",
      "status": "blocked",
      "progress": 40
    },
    {
      "host": "lab-linux",
      "task": "tmux-job",
      "kind": "opencode",
      "status": "blocked",
      "progress": 50
    }
  ]
}

© 2026 xiaomijituan · 正文 CC BY 4.0,代码与数据 MIT · 原仓库 github.com/xiaomijitua...

相关推荐
程序员-Benothing1 小时前
Linux CPU性能分析:top、mpstat、pidstat、perf实战
linux·运维·服务器
小诗懂技术1 小时前
Linux设备驱动并发控制总结_CSDN
linux·运维·服务器
北风toto2 小时前
银河麒麟 V10 内网 YUM 源修复实战教程(U盘中转版)
linux·服务器
小白鸽i2 小时前
【linux】麒麟桌面版安装人大金仓V8
linux·运维
Ruiery2 小时前
Linux 6.6内核内存管理深度解析(三):页表与缺页处理 — 虚拟地址怎么按需映射到物理页
linux·运维·服务器
ouynagda2 小时前
iMX6ull Linux 嵌入式驱动开发笔记(LCD / ADXL345 / SPI 子系统)
linux·arm开发·驱动开发·笔记·嵌入式硬件
小诗懂技术2 小时前
linux驱动中断,相关中断的方式与适用场景
linux·arm开发·驱动开发·算法
xiaoqiMikko2 小时前
Linux 实战(九·完):重定向、sudo、通配符的坑 + 报错速查表 —— 三台真机实测
linux·centos
H.莓飛2 小时前
【C++】模板初阶
linux·开发语言·c++·visual studio