PK
パーネルカニック研究所
今日もどこかでkernel panic。
root@pk-lab:~#

プロセスとは何か:Linuxを動かす小さな働き者たち

連載ナビ

この連載は「Linuxを使えるようになる」ではなく、「Linuxがどう動いているのか」を理解するための実践シリーズです。

今回は Linuxを理解する 〜 LFSで学ぶLinux内部構造 〜 の Part 2 / 第3回として、仕組みのつながりを意識しながら学びます。

—

プロセスとは「カーネルに資源を割り当てられた、実行中のプログラムの実体」

「プロセス=実行中のプログラム」とよく説明されますが、もう一歩踏み込んで区別しておきましょう。

  • プログラムは、ディスク上に置かれた静的なファイルです。/bin/ls は、実行形式(ELF)で機械語が書かれた、ただのファイルに過ぎません。
  • プロセスは、そのプログラムをカーネルがメモリにロードし、PID(プロセスID)・専用の仮想メモリ空間・CPU時間・ファイルディスクリプタといった資源を割り当てて実行している動的な実体です。

結論として、プロセスとは「プログラムそのもの」ではなく、カーネルが管理する実行の単位です。だから同じ /bin/bash という1つのプログラムから、ターミナルを3枚開けば3つの独立したプロセスが生まれますし、前回見た /proc/[PID] のディレクトリは、カーネルが管理しているこの「実行単位」の情報を1つずつ公開したものだった、というわけです。

メモリ空間が分離されているから、マルチタスクが安全に成り立つ

なぜカーネルはプロセスという単位を設けて厳密に管理するのでしょうか。理由は、互いに干渉させずに多数のプログラムを同時に動かすためです。

各プロセスには独立した仮想メモリ空間が与えられます。プロセスから見えるメモリアドレスは自分専用のもので、他のプロセスのメモリを直接読み書きすることはできません。バグのあるプログラムが暴走しても、壊れるのは自分のメモリ空間だけで、隣のプロセスやカーネルは巻き込まれません。C言語で不正なアドレスにアクセスしたときに出る Segmentation fault は、カーネルがこの境界違反を検出してプロセスを停止させた結果です。

また、プロセスはハードウェアに直接触れられません。ファイルを開く、ネットワークに接続するといった操作は、すべてシステムコールという窓口を通じてカーネルに依頼します(Part 1第3回で strace を使って観察した仕組みです)。「資源の割り当てと隔離はカーネルが一元管理し、プロセスは依頼するだけ」という構造が、マルチタスクの安全性を支えています。

forkとexec: 新しいプロセスはコピーから生まれる

Linuxで新しいプロセスが生まれる仕組みは特徴的で、「無から作る」のではなく既存のプロセスの複製から作るという設計です。ターミナルで ls と打ったとき、次の4段階が起きています。

1. fork — シェル(bash)が fork() システムコールを発行し、自分自身のコピーである子プロセスを作ります。子は親とほぼ同じ内容ですが、新しいPIDを持ち、親のPIDを PPID(親プロセスID)として記録しています。

2. exec — 生まれた子プロセスが execve() システムコールを発行し、自分のメモリ内容を /bin/ls のプログラムで丸ごと置き換えます。PIDはそのままに、中身だけが bash のコピーから ls に変わります。

3. 実行 — ls となった子プロセスが、システムコールを使ってディレクトリを読み、結果を出力します。その間、親の bash は wait() で子の終了を待っています。

4. exit — 仕事を終えた子プロセスは exit() で終了ステータス(成功なら0)を残して終了し、親がそれを wait() で回収します。シェルで echo $? と打つと、直前のコマンドのこの終了ステータスが確認できます。

この流れは strace で実際に観察できます。


# bashが子プロセスを作り(clone)、その子がlsに変身する(execve)様子を追う
# ※Linuxではfork()は内部的にclone()として実装されています
strace -f -e trace=clone,execve bash -c 'ls' 2>&1 | head

出力に clone(...) と execve("/bin/ls", ...) の行が現れます。「fork(複製)してexec(置き換え)」という教科書どおりの動きが、そのまま目視できます。

プロセスの状態: R・S・Zの読み分け

カーネルは各プロセスの「状態」も管理しています。ps aux の STAT 列や /proc/[PID]/status の State 行で確認でき、主要なものは次のとおりです。

  • R (Running) — CPUで実行中、または実行可能でCPUの割り当て待ち。
  • S (Sleeping) — 入力やネットワーク応答などのイベント待ち。実は大半のプロセスは、ほとんどの時間この状態です。Webサーバーが100プロセス動いていても、リクエストが来ていなければ全員Sで、CPUはほぼ消費しません。
  • D (Uninterruptible Sleep) — ディスクI/Oなどの完了を待っていて、シグナルでも中断できない状態。Dのまま長時間固まっているプロセスは、ストレージ障害を疑うサインです。
  • Z (Zombie) — 実行はすでに終了したのに、親プロセスが wait() で終了ステータスを回収していない状態。プロセス表のエントリだけが残っています。少数なら一瞬で消えますが、増え続ける場合は親プロセス側の不具合を意味します。

親子関係は ps -ef の PID と PPID の列で、系統全体は pstree -p で確認できます。すべてのプロセスがfork(複製)で生まれる以上、系譜をさかのぼれば必ず1つの祖先に行き着くはずです——それがPID 1です。

すべてのプロセスの親をたどると、PID 1に行き着く

プロセスとは、カーネルがPID・メモリ空間・CPU時間を割り当てて管理する実行の単位であり、fork(自身の複製)とexec(プログラムの置き換え)によって生まれ、親子関係の木構造をなしている——これが今回の結論です。

そしてこの木構造の根、pstree の一番左に必ず表示されるプロセスこそ、次回の主役であるPID 1です。カーネルが唯一直接起動する、この特別なプロセスの責務を見ていきましょう。

コマンドでプロセスを観察する


# プロセス一覧をPID・PPID付きで表示し、親子関係を確認する
ps -ef | head -n 15

# プロセスの親子関係をツリー形式で表示する
pstree -p | head -n 15

# 各プロセスの状態(STAT列)を確認する
ps aux | head -n 10

# fork→execの流れをシステムコールレベルで観察する
strace -f -e trace=clone,execve bash -c 'ls' 2>&1 | head

チェックポイント

  • 「プログラム」と「プロセス」の違いを自分の言葉で説明できる
  • ps -ef の出力から、PID と PPID の列を見つけられる
  • fork と exec が、Linuxで新しいプロセスが生まれる際の基本的な仕組みであることを理解した
  • Zombie (Z) 状態が「親が終了ステータスを回収していない状態」だと説明できる

今回理解できたこと

  • プロセスは、カーネルによってメモリやCPUなどの資源を割り当てられた、動的な「実行単位」であること
  • 各プロセスの仮想メモリ空間は分離されており、これがマルチタスクの安全性を支えていること
  • プロセスは fork と exec によって生まれ、親子関係を持つ木構造をなしていること
  • ps や pstree は、カーネルが管理するプロセス情報(/proc由来)を表示するツールであること