はじめに
多くの計算機実験を行う学生が日常的に tmux を利用していることでしょう。
「とりあえず tmux 叩いとけば、SSHが切れても解析が止まらない」
その通りです。しかし、そのSSHを切断しても何故かプロセスを生存させている挙動が、どのようなロジックにより達成されるのか気になりませんか?
1. そもそも、SSH接続とはどういう状態なのか?
tmuxの話をする前に、まずは 「普通のSSH接続」 で何が起きているか、そしてなぜ 「回線が切れるとプロセスが死ぬのか」 を整理しましょう。ここが理解の出発点です。
sshd と bash の親子関係
あなたが手元のPCから ssh user@server コマンドを叩いたとき、サーバー側では sshd (SSH Daemon) という常駐プロセスがあなたの接続を受け付けます。そして、この sshd があなた専用の子プロセス (例えば bash) を生成します。
sshdには「親」と「子」がいる
下図にある sshd は、正確にはあなたがログインした瞬間に fork された 「セッション用の子プロセス」 です。 1
サーバー内には常駐して接続を待ち受ける「親の sshd (Daemon)」もいますが、これはSSHを切断しても死にません。
今回問題になるのは、シェル (bash) の親となる「担当者の sshd」の方です。
SIGHUPシグナル
さて、研究が一息ついたのでPCをスリープし、PCのssh接続が途切れてしまったとします。この時、以下のようなプロセスが辿られます。
-
通信断絶: サーバー側の
sshdは、あなたのPCからの応答がなくなったこと (またはTCP切断) を検知します。 -
親の死:
sshdは「役割終了」と判断して自身のプロセスを閉じます。 -
子の巻き添え: ここでUNIX/Linuxの重要な仕様が発動します。端末 (この場合は
sshdが提供していた接続) が切断されると、OSはそこぶら下がっていたセッションリーダー (bash) に対して、SIGHUP (Signal Hangup: 終了シグナル) を送信します。2 -
全滅:
SIGHUPを受け取ったbashは終了し、その子供であるpythonプロセスも道連れに強制終了されます。
これが、我々が恐れる「SSH切断=計算プロセスの終了」のメカニズムです。
2. tmuxの介入
tmuxを起動すると、構造が劇的に変わります。ここで重要なのが、tmuxのクライアント・サーバー(C/S)モデルです。3
構造の分離
tmuxを起動すると、sshd と bash の間に tmux Server が割り込みます。
切断時の挙動
Wi-Fiが切れたとき、死ぬのは sshd と、その直下にある tmux Client だけです。
tmux Server は sshd とは親子関係にない (独立してバックグラウンドで動いている) ため、sshd が死んでも影響を受けません。これにより、ssh接続を切断しても計算プロセスを生存させることができます。
実際に確認してみる
tmuxを起動した状態とそうでない状態とで、プロセスツリーに差異があるかを確認してみましょう。
- ssh接続した直後に実行
$ pstree -aps $$
systemd,1
└─sshd,1501
└─sshd,105887
└─sshd,106038
└─zsh,106041
└─pstree,106534 -aps 106041
- tmuxを起動した状態で実行
$ pstree -aps $$
systemd,1
└─tmux: server,106370
└─zsh,106371
└─pstree,106510 -aps 106371
この結果から、tmux server の親プロセスは sshd ではなく、systemd (PID: 1) になっていることがわかります。
tmuxが起動時に自身をバックグラウンドへ逃がしていることが確認できますね。
このおかげで、もし sshd が死んでも、親が違う tmux server は連鎖的なプロセス終了の対象から外れ、涼しい顔で生き残ることができます。
3. なぜ中のプロセスは「主人が消えた」ことに気づかないのか?
しかし、疑問が残ります。いくら tmux Server が生きていても、実際にユーザーからの入力を受け付けていた ssh が切れたなら、中の bash は「入力元がなくなった」と気づいてエラーになるはずではないでしょうか?
ここで登場するのが、PTY (Pseudo-Terminal / 疑似端末) という仕組みです。
MasterとSlave
PTYは Master と Slave のペアで構成される仮想デバイスです。
-
Slave側: 従来の物理端末と同じ振る舞いをします。
bashはこれに接続され、「自分は端末に繋がっている」と信じ込みます。 - Master側: ここを握っているプロセスが「端末の向こう側」として振る舞います。
通常のSSHでは sshd がMasterを握っていましたが、tmux環境下では tmux Server がMasterを握り続けます。
tmux Server がMaster側をガッチリ掴んで離さない限り、OSは切断を検知しません。したがって、中の bash に死のシグナル (SIGHUP) が届くことは起きないわけですね。
実際に確認してみる
tty コマンドを叩いてみると、以下のような表示が確認できるはずです。
$ tty
/dev/pts/4
このように、仮想的なデバイスファイルが表示されます。
あなたが tmux セッション内でテキストを入力したとき、その文字は以下の経路をたどります。
-
あなた のキーボード入力
-
tmux client が受け取り、サーバーへ転送
-
tmux server が Master側から書き込む
-
Slave側 (
/dev/pts/4) からbashがそれを読み込む
bash からすれば、端末デバイスファイルに対して read/write を行うだけで、相手が物理的なケーブルの先の人間なのか、tmux 経由なのかは区別がつきません。
このようにうまく shell を騙し、働かせ続けることができるわけです。
例外:SIGHUPが飛ぶケース
以下のケースでは、ロジック通り SIGHUP(またはプロセスの終了) が発生します。
-
tmux自体を終了させた場合
tmux kill-serverやtmux kill-sessionコマンドを実行したり、サーバー機自体を再起動した場合です。これらが起きると身代わり役の tmux Server プロセスが終了 するので、当然ながら握っていた「PTY Master」も閉じられ、POSIXの仕様通り OS は切断を検知し、中のプロセスに SIGHUP が送られます。 -
シェル設定によるハングアップ
稀なケースですが、シェルの設定(huponexitオプションなど)や、ログインスクリプト(logout hook)の記述によっては、セッション終了時に明示的にシグナルを送る挙動をする場合があります。
つまり、tmuxは「回線切れ」からは守ってくれますが、「サーバーダウン」や「意図的なプロセス終了」からは守れません。
まとめ
tmuxがSSH切断に強い理由は、黒魔術ではなく以下の3段階のロジックによるものです。
-
構造の分離:
通信担当 (sshd/tmux Client) と、処理担当 (tmux Server) を別プロセスに分離 -
身代わり (Master):
sshdの代わりにtmux Serverが仮想端末の Master 側を握ることで、プロセスから「本当の切断」を隠蔽 -
仕様の回避:
切断を検知させないことで、POSIX標準のSIGHUP(強制終了シグナル) の発生条件を回避
この記事を読んだ後 tmux で実験を回すことがあれば、裏で tmux Server が仮想のケーブル (PTY Master) を握りしめ、「まだ大丈夫だ」とシェルを騙し続けている姿を想像してあげてください。
参考文献
-
sshd(8) — OpenBSD manual pages (DESCRIPTION, LOGIN PROCESS). ↩
-
General Terminal Interface (The Controlling Terminal, Modem Disconnect), The Open Group Base Specifications Issue 7 (POSIX.1-2017). ↩
-
Tony Narlock, The Tao of tmux, Leanpub. (Chapter 4: Server) ↩
-
pty(7) — Linux manual page, man7.org. ↩
-
tty(4) — Linux manual page, man7.org. ↩



