Ctrl-Cはなぜプログラムを終了できるのか
Ctrl-Cとは、端末のラインディシプリンが前景プロセス群へSIGINTを配送させる仕組みである。
中級者がつまずきがちなポイントは、Ctrl-Cが「CPUを止める魔法のキー」ではなく「シグナル配送を起動するきっかけ」にすぎないこと。ここでは、TTYとプロセスグループ、シグナル、そして例外的に止まらないケースまで、実務で役立つ粒度で解説する。
仕組みの全体像
端末へCtrl-Cを送ると、端末ドライバが割り込み文字を検知し、カーネルが前景プロセスグループにSIGINTを送る。多段パイプラインでも同じプロセスグループに所属していれば全員へ飛ぶ。アプリがRAWモードにしていれば、ラインディシプリンは介在せずアプリが0x03をただのバイトとして受け取る。
キーワード早見:
- ラインディシプリン 端末入力の編集や制御を司る層。stty intr で割り込み文字を変更できる。
- 前景プロセスグループ 同じジョブに属するプロセスの集合。シェルが制御する。
- SIGINT デフォルトは終了。アプリが捕捉すれば挙動は変えられる。
Ctrl-Cは何を止めているのか
本質は「プロセスのメインスレッドへ非同期イベントを通知している」こと。POSIXではSIGINTのデフォルト動作は終了だが、言い換えると終了するかどうかは最終的に各プロセス次第だ。
- どこへ届くか 端末の前景プロセスグループ全体。パイプ a | b | c なら a b c の全プロセスへSIGINT。
- システムコール中なら 多くのブロッキング呼び出しはEINTRで中断され、アプリが戻り値を見て処理を打ち切る。
- ただしSA_RESTARTありなら 同じシステムコールが自動再開され、ユーザ視点では効いていないように見えることがある。
似て非なるシグナルやキーとの比較
| 入力や操作 | 送られるシグナル | デフォルト動作 | 典型的用途 | 備考 |
|---|---|---|---|---|
| Ctrl-C | SIGINT | 終了 | 実行の中断と終了 | 前景プロセスグループ全体へ配送 |
| Ctrl-Z | SIGTSTP | 停止 | 一時停止してシェルへ戻る | bg fg で再開可能 |
| Ctrl backslash | SIGQUIT | コアダンプ付き終了 | デバッグ用強制終了 | 多くの端末でquitに割り当て |
| 端末を閉じる | SIGHUP | 終了 | 端末切断の通知 | nohupで無視させる運用あり |
| kill TERM pid | SIGTERM | 終了 | 丁寧な終了 | デフォルトの終了要求 |
| kill KILL pid | SIGKILL | 強制終了 | 最終手段 | 捕捉不可 無視不可 |
Ctrl-Cで止まらないものは何か
現場で遭遇しがちなパターンを、原因と対処の観点でまとめる。
| ケース | 止まらない理由の本質 | 典型例 | 実務での対処 |
|---|---|---|---|
| RAWモードのフルスクリーンアプリ | ラインディシプリンをバイパスしており、0x03はただの入力 | エディタやTUI | アプリ側のショートカットやコマンドで抜ける 設定次第でSIGINTをハンドル |
| プロセスがSIGINTを無視または捕捉 | signalで無視や独自処理している | サーバやREPL | kill TERM や設定変更 終了パスを実装 |
| シグナルをマスク中 | 一時的に配達が保留される | クリティカルセクション | マスク解除を待つ 設計見直し |
| システムコールが自動再開 | SA_RESTARTで再開されユーザが効き目を感じにくい | ネットワークI O | ループでEINTRを扱い中断パスを用意 |
| カーネルのD状態でブロック | アンインタラプタブルスリープで割り込み不可 | NFSやデバイス待ち | 原因I Oの復旧のみ有効 再起動検討 |
| バックグラウンドジョブ | 前景グループでないため受け取らない | コマンド末尾にampersand | fgしてからCtrl-C もしくはkill |
| 制御端末を持たないデーモン | 端末由来のシグナル経路が存在しない | サービスプロセス | systemctlやkillで管理 |
| 別のプロセスグループに分離 | setsidで独立 | nohup setsid | pgidやセッションを確認してkill |
パイプラインではどう見えるか
次のようなパイプラインを考える。
- yes | head
Ctrl-Cは両者に飛ぶが、しばしばheadが先に終了してパイプを閉じ、yesにはSIGPIPEが飛んで自然終了する。パイプライン全体が落ちるため、ユーザの体感としてはCtrl-Cで全部止まったように見える。
実務で役立つ観点
-
端末側の設定確認
- stty a で intr erase susp などの割り当てを確認
- intrを変えたいなら stty intr ^]
-
シグナルの扱い
- プログラムではSIGINTを捕捉して安全に終了するパスを必ず用意する
- ブロッキングI Oを使うならEINTRとSA_RESTARTの挙動を設計に織り込む
-
現場調査の指針
- ps o pid,ppid,pgid,stat,tty,cmd でプロセスグループと状態を可視化
- proc pid statusやwchanでブロック点を探る D状態ならI Oの復旧が先
- どうしても抜けないならSIGTERM SIGQUIT それでもダメならSIGKILLへ段階的に
Windowsコンソールとの違い
| 観点 | Unix系 | Windowsコンソール |
|---|---|---|
| 生成されるイベント | SIGINT | CTRL_C_EVENT |
| 配送対象 | 前景プロセスグループ | コンソールのプロセスグループ |
| 既定動作 | 終了 | 既定は終了だがハンドラで抑止可能 |
| APIの扱い | sigaction など | SetConsoleCtrlHandler |
| RAWモード相当 | 端末をrawへ | コンソールモードの設定で近似 |
実務ではWSLを含め、同じキーでも実装差があることを念頭に置く。
よくある誤解の撲滅メモ
- Ctrl-Cは常に即座に止まるわけではない 再開可能なシステムコールやD状態がある
- Ctrl-Cは特定スレッドだけを止めるものではない プロセスへシグナルが届き、スレッドへの配送はOSとランタイムの領域
- Ctrl-Cが届かないのは端末やジョブ制御の設計次第 制御端末が無ければ届かない
まとめ
Ctrl-Cは、端末ドライバが検知した割り込み文字を合図に、カーネルが前景プロセスグループへSIGINTを配送する仕組みでプログラムを終わらせる。止まらないときは、RAWモード 無視やマスク D状態 ジョブ制御の境界など、伝播経路のどこが詰まっているかを見極めれば、正しい手段を選べる。設計では、安全な終了パスとEINTR対応を最初から組み込み、運用ではシグナルを段階的に使い分けるのが堅牢さへの近道だ。