プロセスの状態とライフサイクル(process states)- 実行・待機・ゾンビの正体
この記事で解決できること
psのSTAT列に出る R / S / D / T / Z が何を意味するか分かる- プロセスが
forkで生まれexitで消えるまでの ライフサイクル を追える - 「ゾンビが消えない」「
kill -9でも死なない」ときの 切り分けの軸 が手に入る
結論(読み方の型)
psの状態は 1 文字の符号 でカーネル内部状態を表すR=実行/実行可能、S=待機(割り込み可)、D=待機(割り込み不可)、T=停止、Z=ゾンビ- ゾンビは「終了済みだが親が回収していない」状態。プロセスではなく 後始末漏れ
前提(対象環境)
- 一般的な Linux(procps 系の
ps/top) - 状態符号は
ps auxのSTAT列・ps -eo statで確認する
プロセスの状態とは何か?
結論: プロセス状態とは、カーネルがそのプロセスを今どう扱っているか(CPU 上で動かす・待たせる・止める・回収待ち)を示す内部区分であり、
psはそれを 1 文字に射影して見せる。
Linux のプロセスは生まれてから消えるまで、常にいずれかの 状態(state) にある。状態はカーネルがスケジューリングのために管理する内部属性で、「CPU を割り当てる対象か」「何かを待っているか」「もう終わっているか」を区別する。
ps や top が表示する状態符号は、この内部状態を 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 文字目以降は補助フラグ(後述)である。COMMAND が defunct の行がゾンビだ。
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+Z(SIGTSTP)やkill -STOPで入る。fg/bgまたはSIGCONTで再開する。t(Tracing stop):gdbやstraceなどのデバッガにトレースされて停止している状態。
$ 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 では Z、COMMAND 欄では <defunct> と表示される。
ゾンビの重要な性質は次の通り。
- すでに メモリ・ファイル・CPU は解放済み。残るのはプロセステーブルの 1 エントリ(PID と終了ステータス)だけ
- 「すでに死んでいる」ため
killで殺せない(signal を受け取る実体がない) - 親プロセスが
wait()/waitpid()を呼べば 即座に消える(reap = 刈り取り)
# ゾンビだけを抽出
$ ps aux | awk '$8 ~ /Z/ { print $2, $11 }'ゾンビを消す相手は ゾンビ自身ではなく親プロセス である。kill -9 <ゾンビのPID> は無意味。親が wait を怠っている(バグ)なら、親プロセスに SIGCHLD を送る、あるいは 親を終了させる ことで、reap を促す(または PID 1 に引き取らせる)。
少数のゾンビは無害だが、大量に溜まる場合は親プロセスのバグ(子を 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 されて完全に消える。ゾンビはこの最後の一歩が未完の状態。
ここまでの状態をライフサイクルとして繋げると、プロセスの一生は次の流れになる。
fork(): 親プロセスが自分の複製として子プロセスを生成する。直後の子は親とほぼ同一。exec(): 子プロセスが自分のメモリイメージを 別のプログラムで上書き する(fork+execが新規コマンド起動の基本形)。- 実行と待機: スケジューラの管理下で
R(実行)⇄S/D(待機)⇄T(停止)を行き来しながら仕事をする。 exit(): プログラムが終了する。終了ステータスを残して ゾンビ(Z) になり、親の回収を待つ。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 aux の STAT が Ss や R+ のように 2 文字以上になるのは、主状態に 補助フラグ が付いているためだ。主なものを押さえる。
| フラグ | 意味 |
|---|---|
s |
セッションリーダー(session leader) |
l |
マルチスレッド(multi-threaded) |
+ |
前面(foreground)プロセスグループに所属 |
< |
優先度高(nice 値が負) |
N |
優先度低(nice 値が正) |
L |
メモリページがロックされている |
たとえば systemd の Ss は「割り込み可能な待機(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 の送り方とセットで覚えると、プロセスのトラブル対応が一段速くなる。