プロセスの状態とライフサイクル(process states)- 実行・待機・ゾンビの正体

プロセスの状態とライフサイクル(process states)- 実行・待機・ゾンビの正体

この記事で解決できること

  • psSTAT 列に出る R / S / D / T / Z が何を意味するか分かる
  • プロセスが fork で生まれ exit で消えるまでの ライフサイクル を追える
  • 「ゾンビが消えない」「kill -9 でも死なない」ときの 切り分けの軸 が手に入る

結論(読み方の型)

  • ps の状態は 1 文字の符号 でカーネル内部状態を表す
  • R=実行/実行可能、S=待機(割り込み可)、D=待機(割り込み不可)、T=停止、Z=ゾンビ
  • ゾンビは「終了済みだが親が回収していない」状態。プロセスではなく 後始末漏れ

前提(対象環境)

  • 一般的な Linux(procps 系の ps / top
  • 状態符号は ps auxSTAT 列・ps -eo stat で確認する

プロセスの状態とは何か?

結論: プロセス状態とは、カーネルがそのプロセスを今どう扱っているか(CPU 上で動かす・待たせる・止める・回収待ち)を示す内部区分であり、ps はそれを 1 文字に射影して見せる。

Linux のプロセスは生まれてから消えるまで、常にいずれかの 状態(state) にある。状態はカーネルがスケジューリングのために管理する内部属性で、「CPU を割り当てる対象か」「何かを待っているか」「もう終わっているか」を区別する。

pstop が表示する状態符号は、この内部状態を 1 文字に対応づけた表示 である。まず全体像を ps で観察する。

$ ps -eo pid,stat,comm
  PID STAT COMMAND
    1 Ss   systemd
  812 S    sshd
 1043 R+   ps
 1044 S+   bash
 2210 Z    defunct

STAT 列の 先頭 1 文字が主状態、2 文字目以降は補助フラグ(後述)である。COMMANDdefunct の行がゾンビだ。

R / S / D - 実行と待機の違いは?

結論: R は CPU で動いている(または順番待ち)、S は割り込み可能な待機、D は割り込み不可能な待機。D が増えていたら I/O 詰まりを疑う。

日常的に最も多く目にするのが、実行系の R と待機系の S / D だ。

  • R(Running / Runnable): CPU 上で実行中、または実行待ちの ランキュー に載っている状態。「動ける」プロセス。
  • S(Interruptible Sleep): 何かのイベント(入力・タイマー・ソケット受信など)を 待っている 状態。signal で割り込める。デスクトップやサーバのプロセスの大半は普段この S
  • D(Uninterruptible Sleep): 主に ディスク I/O など低レベルの完了待ち。signal でも割り込めない。通常は一瞬で抜けるが、長く D に留まるプロセスが増えるのは要注意サイン(ストレージ障害・NFS 応答なし等)。
# D 状態(I/O 待ち)のプロセスだけ抽出
$ ps -eo pid,stat,comm | awk '$2 ~ /D/'

D のプロセスは signal を受け付けないため、kill -9 でも すぐには死なない。「kill -9 したのに消えない」プロセスの多くは、この D(I/O 待ち)かゾンビ(Z)のどちらか。原因の I/O が完了するまで待つしかない。

T / t - 止まっているプロセスとは?

結論: T は SIGSTOP / Ctrl+Z などで一時停止された状態、t はデバッガにトレースされて止まっている状態。どちらも CPU は使わず、再開待ち。

  • T(Stopped): ジョブ制御で停止された状態。Ctrl+ZSIGTSTP)や kill -STOP で入る。fg / bg または SIGCONT で再開する。
  • t(Tracing stop): gdbstrace などのデバッガにトレースされて停止している状態。
$ sleep 300 &
$ kill -STOP %1     # 停止 → STAT が T になる
$ ps -o pid,stat,comm -p $(pgrep -n sleep)
  PID STAT COMMAND
 3120 T    sleep
$ kill -CONT %1     # 再開 → S に戻る

T は「死んでいる」のではなく「一時停止して再開を待っている」。終了させたい場合は、SIGCONT で再開してから SIGTERM を送るのが安全(停止中のプロセスは TERM を処理できないことがある)。

Z(ゾンビ)の正体は?なぜ消えないのか?

結論: ゾンビ(Z / defunct)は終了済みだがまだ親プロセスが終了ステータスを回収(reap)していない状態。本体は消えており、残るのはプロセステーブルの 1 エントリだけ。

プロセスが exit() で終了すると、即座に消えるわけではない。カーネルは 終了ステータス(exit code) を保持したまま、親プロセスがそれを受け取りに来るのを待つ。この回収待ちの状態が ゾンビ(zombie) であり、ps では ZCOMMAND 欄では <defunct> と表示される。

ゾンビの重要な性質は次の通り。

  • すでに メモリ・ファイル・CPU は解放済み。残るのはプロセステーブルの 1 エントリ(PID と終了ステータス)だけ
  • 「すでに死んでいる」ため kill で殺せない(signal を受け取る実体がない)
  • 親プロセスが wait() / waitpid() を呼べば 即座に消える(reap = 刈り取り)
# ゾンビだけを抽出
$ ps aux | awk '$8 ~ /Z/ { print $2, $11 }'

少数のゾンビは無害だが、大量に溜まる場合は親プロセスのバグ(子を wait し損ねている)を示す。PID は有限資源なので、ゾンビが PID を食い潰すと新規プロセスを作れなくなる。

孤児(orphan)プロセスとゾンビの違いは?

結論: 孤児は親が先に死んだ生きているプロセスで、PID 1(init / systemd)に引き取られる。ゾンビは死んだプロセス。孤児は正常、放置ゾンビは異常。

混同されやすいのが 孤児プロセス(orphan) とゾンビだ。両者は正反対に近い。

観点 孤児(orphan) ゾンビ(Z)
生死 生きている 死んでいる(終了済み)
発生原因 親が先に終了した 親が wait していない
引き取り先 PID 1(init/systemd) 親(または PID 1)が reap
問題か 通常は無害 大量なら親のバグ

親プロセスが子より先に終了すると、子は 孤児 になり、自動的に PID 1(init または systemd)に再ペアレント(reparent) される。PID 1 は定期的に wait を呼ぶので、その孤児が将来終了しても すぐ reap され、ゾンビとして残らない。これが「nohup やデーモン化したプロセスがターミナルを閉じても生き続ける」仕組みの一部でもある。

# 親 PID が 1 になっていれば孤児(= PID 1 に引き取られた)
$ ps -eo pid,ppid,stat,comm | awk '$2 == 1'

fork から exit まで - プロセスのライフサイクル

結論: プロセスは fork で複製され、exec で中身を入れ替え、実行(R/S/D)と停止(T)を経て exit で終了、親の wait で reap されて完全に消える。ゾンビはこの最後の一歩が未完の状態。

ここまでの状態をライフサイクルとして繋げると、プロセスの一生は次の流れになる。

  1. fork(): 親プロセスが自分の複製として子プロセスを生成する。直後の子は親とほぼ同一。
  2. exec(): 子プロセスが自分のメモリイメージを 別のプログラムで上書き する(fork + exec が新規コマンド起動の基本形)。
  3. 実行と待機: スケジューラの管理下で R(実行)⇄ S / D(待機)⇄ T(停止)を行き来しながら仕事をする。
  4. exit(): プログラムが終了する。終了ステータスを残して ゾンビ(Z) になり、親の回収を待つ。
  5. wait() / waitpid(): 親が子の終了ステータスを受け取る(reap)。この瞬間にゾンビは消え、PID が解放される。

このモデルから、よくある症状が一本の線で説明できる。

  • ゾンビが残る → ステップ 5 が抜けている(親が wait していない)
  • 孤児になる → ステップ 4 より前に親が終了 → PID 1 が肩代わり
  • kill -9 で死なない → ステップ 3 の D(I/O 待ち)か、すでにステップ 4 のゾンビ
# 状態ごとのプロセス数をざっくり集計
$ ps -eo stat --no-headers | cut -c1 | sort | uniq -c | sort -rn
    142 S
     12 I
      4 R
      1 Z

STAT 列の補助フラグの読み方

結論: STAT の 2 文字目以降は補助情報。+ は前面ジョブ、s はセッションリーダー、l はマルチスレッド、< / N は優先度。主状態の文字と組み合わせて読む。

ps auxSTATSsR+ のように 2 文字以上になるのは、主状態に 補助フラグ が付いているためだ。主なものを押さえる。

フラグ 意味
s セッションリーダー(session leader)
l マルチスレッド(multi-threaded)
+ 前面(foreground)プロセスグループに所属
< 優先度高(nice 値が負)
N 優先度低(nice 値が正)
L メモリページがロックされている

たとえば systemdSs は「割り込み可能な待機(S)かつセッションリーダー(s)」、ps 自身の R+ は「実行中(R)かつ前面ジョブ(+)」と読む。

状態の 主文字だけ で集計したいときは ps -eo stat | cut -c1 のように先頭 1 文字を取り出すと、補助フラグを無視できる(前掲の集計例がこの手法)。

まとめ:状態符号の早見表

結論: R は動ける、S/D は待機(D は割り込み不可)、T は停止、Z はゾンビ。ゾンビと D は kill で消えない点が実務上の落とし穴。

符号 状態 意味 kill で消える?
R 実行 / 実行可 CPU 上 or ランキュー
S 割り込み可待機 イベント待ち(通常)
D 割り込み不可待機 I/O 完了待ち 不可(I/O 待ち)
T 停止 Ctrl+Z / SIGSTOP CONT で再開後に可
Z ゾンビ 終了済み・reap 待ち 不可(親を処理)

状態を読めるようになると、「重い」「終わらない」「消えない」という曖昧な症状を、カーネルがそのプロセスに何をさせているか という具体に翻訳できる。signal の送り方とセットで覚えると、プロセスのトラブル対応が一段速くなる。

次に読む