4つの驚くべきプロセスの真実:OSがプログラムを動かす仕組み
はじめに:プロセスという隠れた世界
PCを使っているとき、ブラウザで記事を読みながら音楽を再生し、裏ではチャットアプリやエディタが動いているかもしれません。ユーザーからすると、これは当たり前のように同時進行しているように見えます。しかし実際には、これはOSカーネルが精密に orchestrate(調整)している「プロセス」という仕組みによって実現されています。
プロセスはそれぞれ独立した実行単位で、メモリやリソースを隔離して管理されます。この隔離性があるからこそ、複数のプログラムが同時に動いても安全性と安定性が保たれているのです。
この記事では、Linuxを例に取りながら、プロセス管理に関する「直感に反する4つの驚きの真実」を解説します。普段当たり前だと思っている仕組みの裏側には、意外なトリックが隠れています。
1. 「実行中」のプロセスは、実はほとんど動いていない
「running状態」と聞くと、そのプロセスがCPUで命令を実行していると思うかもしれません。しかし、Linuxの実際の定義はもう少し広く、CPUで実行中か、実行可能キューで順番待ちしている状態を指します。
現実にはCPUコアの数よりもはるかに多くのプロセスが「running状態」にいます。そのため、多くのプロセスはCPU時間を待っているだけで、実際には止まっています。
この仕組みが「マルチタスクの幻想」の正体であり、カーネルが高速にタスクを切り替えることで同時実行しているように見せているのです。
2. 新しいプログラムは「コピーして置き換える」二段階のダンスで生まれる
新しいプログラムを起動するのは一見シンプルですが、Linuxでは実際には2ステップの「ダンス」で行われます。
Step 1: fork() によるコピー
親プロセスが fork() を呼ぶと、子プロセスが生成されます。子は親のほぼ完全なコピーですが、実際には Copy-on-Write(CoW) という最適化により、メモリは共有され、書き込みが発生したときに初めて複製されます。
Step 2: exec() による置き換え
forkで作られた子プロセスは、すぐに exec() を呼び出し、新しいプログラムでメモリ空間を完全に置き換えます。
例えばシェルでコマンドを実行する際、シェル自身を fork() でコピーし、その子が exec() でコマンドのプログラムに変わります。
3. ゾンビプロセスという「死んでいるのに残る存在」
プロセスが終了しても、親が wait() で終了コードを受け取るまで、そのプロセスは「ゾンビ状態」でプロセステーブルに残ります。
ゾンビは既にリソースを解放しているため害はほとんどありませんが、数が増えすぎるとテーブルが埋まり問題になります。通常は親がきちんと wait() で回収しますが、親が終了してしまった場合は init や systemd が孤児を引き取り、自動で処理してくれます。
4. Linuxカーネルにとって「プロセスとスレッドは同じもの」
Linuxでは、プロセスとスレッドを区別するのは人間側の概念であり、カーネルにとってはどちらも「task_struct」という同じデータ構造で表現される「タスク」です。
新しいプロセスもスレッドも clone() システムコールで生成されます。
- プロセスは共有を最小限にして clone()
- スレッドはアドレス空間やファイル記述子を共有するよう clone()
こうして生まれる「タスク」にはそれぞれTID(タスクID)が割り当てられ、スレッドグループにはTGID(Thread Group ID)が割り当てられます。外からはこのTGIDがPID(プロセスID)として見えるのです。
補足:psやtopでスレッドが見える理由
ps -L などのオプションでスレッドごとの情報を表示できるのは、カーネルがスレッドも「個別のタスク」として扱っているからです。
おわりに:OSが作り出す優雅な幻想
OSの役割は「複雑さを隠すこと」です。
高速なタスク切り替えによるマルチタスクの幻想、forkとexecによる効率的なプロセス生成、ゾンビプロセスの存在、そしてプロセスとスレッドを統一的に扱うカーネルの設計。
こうした仕組みがあるからこそ、私たちは「プログラムが同時に動いている」と錯覚できるのです。次にアプリを立ち上げたとき、背後でどんな優雅な仕組みが働いているか、少し想像してみると面白いでしょう。
Discussion