はじめに
LinuxやUnix系OSにおけるプロセスの生成と実行は、すべてfork() → exec()という2つのシステムコールの流れで行われています。これは、PythonやC、bashなどの言語やツールに関係なく、OSレベルで共通する基本的な動作モデルです。
しかし、初学者にとってはこの「fork() して exec() する」という流れが直感的に理解しづらく、「なんとなく動いている」としか見えないことも多いでしょう。
この記事では、Linuxのプロセスがどうやって生まれて動いていくのか、fork() と exec() がそれぞれ何をしているのかを、概念と具体例の両面から解説していきます。
※なお、この記事では Python を具体例として取り上げており、Mac のターミナルでコマンドを実行する場面を想定しています。
「プロセス」とは何か
プロセスとは、簡単に言えば「メモリ上に展開された実行中のプログラム」です。
単にファイルがストレージにあるだけでは実行されておらず、OSがその実行ファイルを読み込んでメモリ上に展開し、CPUで実行できる状態にしたものがプロセスです。
例えば、ターミナルで python3
と入力してEnterを押すと、「pythonという実行ファイル」が実行されているように見えます。
実際には、その裏側で「プロセスが生成され、メモリにロードされ、動き出している」わけです。
fork() → exec() モデルとは
Unix/Linuxにおけるプロセス実行の基本構造は、以下のような流れです。
親プロセス(例:bash)
↓ fork()
子プロセス(bashのコピー)
↓ exec()
実行ファイル(例:.py)が読み込まれて実行開始
では、それぞれの関数が何をしているかを詳しく見ていきましょう。
fork(): プロセスの「コピー」を作る
fork() は現在のプロセス(親)をそのままそっくりコピーして、新しい子プロセスを作るためのシステムコールです。
ここで重要なのは、この時点では親も子も「まったく同じ内容のプログラム」を持っているということです。メモリの中身も、開いているファイルも、変数の値も同じです(実際にはコピーオンライトによって効率化されています)。
※なお、実際には物理的にすべてのメモリがコピーされるわけではなく、コピーオンライトと呼ばれる仕組みで、必要になったときに初めてコピーが行われるため、高速かつ省メモリです。
たとえば、ターミナルの中で python3
と打ったとき、まず bash(親プロセス)は fork() を呼び出し、自分自身のクローン(子プロセス)を作ります。
※https://web-develop.hatenadiary.org/entry/20071019/1192808737 より画像引用
このイメージ図でいう、「プロセスA」が fork() によって複製されていることを意味します。
親プロセスAと fork() によって生成されたその子プロセスであるプロセスAのコピーは、それぞれ別プロセスになるため、PID(プロセスID)は異なります。
exec(): 中身を「別のプログラム」に入れ替える
コピーされた子プロセスは、次に exec() を呼び出します。これは、現在のプロセスの中身(=実行中プログラム)を、別の実行ファイルで上書きするという意味です。
たとえば、python3
コマンドを実行した場合、子プロセスは exec("python3") のような形で、ストレージから実行ファイル python3
をメモリ上に読み込み、自身のプロセス空間にその中身を展開して上書き実行を始めます。
つまり、exec() によって、子プロセスは「bashのコピー」から「Python実行プロセス(プロセスB)」に変身します。
※ ここでいう「Python実行プロセス(プロセスB)」はPythonインタプリタを表します。
なお、exec() は現在のプロセスの中身を完全に置き換えるため、成功すれば戻ってくることはありません。以降は新しいプログラムとして動作を継続します。
ここまでの一連の流れ
ユーザー入力: $ python3
親プロセス(bash)
└── fork()
↓
子プロセス(bashのコピー)
└── exec("python3")
↓
子プロセスが python 実行ファイルを読み込み、Pythonインタプリタとして動き始める
なぜこの2ステップに分かれているのか?
最初から python3
を直接起動してもいいのでは?と感じるかもしれませんが、Unixの設計ではこの「fork() → exec()」という柔軟な2段階構成が大きなメリットを持っています。
-
親プロセス(例: bash)は、子プロセスの生成と監視ができる(wait処理など)
-
子プロセスに対して exec() の前に環境変数やリソース制限などを設定できる
-
複雑なプロセス制御が必要なサーバープログラム(Apache, gunicornなど)にも応用できる
この汎用的な構造によって、Unixはシンプルながらも非常に強力なプロセス管理が可能になっているのです。
まとめ
fork() → exec() のプロセス生成モデルは、LinuxやUnix系OSの根本的なプロセス実行の仕組みです。
普段私たちは「コマンドを打てばプログラムが動く」と感じていますが、その裏では親プロセス(シェル)が fork() で子を生み、exec() で実行ファイルにすり替えるという、論理的な手順が踏まれています。
この仕組みを理解すると、仮想環境(venv)の動作やWebサーバなど、さまざまな技術の「プロセスの構造」や「実行の原理」がグッと見えるようになります。このモデルこそが、Unixの堅牢性と柔軟性の源と言えるでしょう。
最後までお読みいただき、ありがとうございました。
参考・画像引用元URL
https://www.coins.tsukuba.ac.jp/~syspro/2005/No3.html
https://blog.longest-road.com/exec-fork/
https://www.scaler.com/topics/fork-system-call/
https://linuxhint.com/fork_linux_system_call_c/
https://lightning-brains.blogspot.com/2019/10/linux.html