C言語のfork()とexec()をマスターする:プロセスの作成と置換を徹底解説
この記事では、C言語のfork()関数とexec()関数を使って、プロセスを作成・実行したり、実行中のプロセスを別のプロセスに置き換えたりする方法を解説します。両関数の概要、構文、呼び出し方法を説明し、それぞれ実用的なサンプルコードも紹介します。最後に、fork()とexecv()を組み合わせて、新しいプロセスを作成し、それを別のプロセスに置き換える方法についても詳しく見ていきます。
C言語におけるfork()関数とは
fork()関数は、呼び出し元のプロセス(親プロセス)の複製(子プロセス)を作成します。子プロセスは親プロセスの複製ですが、割り当てられたメモリ領域やPIDなど、一部の属性は共有されません。以下にfork()関数の基本的な使い方の概念を示します。
fork()関数は、親プロセス側では子プロセスのPIDを戻り値として返します。一方、同じ呼び出しが子プロセス側では影響を持たず、0を返します。この仕組みにより、fork()の戻り値を条件とするif文を使うことで、2つのプロセスで異なるコードを実行できます。
if (fork() == 0)
{
/* 子プロセス用のコード */
}
else
{
/* 親プロセス用のコード */
}
このように、親プロセスはelseブロック内のコードを実行し、子プロセスはifブロック内のコードを実行します。
簡単な例で確認してみましょう。次のコードは2つの無限ループで構成されています。親プロセスではプログラムがelse文に対応するループに入り、「Write by parent process」というメッセージを表示します。一方、fork()で作成された子プロセスはif文のループに入り、「Write by child process」というメッセージを表示します。
#include <stdio.h>
#include <unistd.h>
void main(){
if (fork() == 0)
{
while (1){
printf ("Write by child process \n");
sleep(5);}
}
else
{
while (1){
printf ("Write by parent process \n");
sleep(2);}
}
}
次の画像は、このコードのコンパイルと実行結果です。コマンドコンソールを見ると、各プロセスが異なるコードを実行していることがわかります。

C言語におけるexecXXX()関数ファミリー
execXXX()系の関数は、実行中のプロセスを新しいプロセスに置き換えます。新しいプロセスのイメージは、置き換え対象のプロセスに割り当てられているメモリ領域にコピーされ、PIDや割り当て済みのリソースなどがそのまま引き継がれます。
このグループの関数は「unistd.h」ヘッダーで定義されており、引数の受け取り方に応じて異なる呼び出し形式を持ちます。また、可変長引数(variadic)型であるため、旧プロセスから新プロセスへ不定個の引数やポインタを渡すことが可能です。以下に各関数の構文を示します。
int execl(const char *path, const char *arg, ...(char *) NULL );
int execlp(const char *file, const char *arg, ... (char *) NULL );
int execle(const char *path, const char *arg, ... , (char *) NULL, char * const envp[] );
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
execl()、execle()、execv()関数は、最初の引数として新プロセスの実行ファイルの絶対パスを含む文字列へのポインタを受け取ります。一方、execlp()、execvp()、execvpe()関数はカレントディレクトリ内のファイル名を使用します。第2引数は新プロセスに渡す引数で、const char *arg型の文字列、またはchar *const argv[]型の文字列へのポインタのリストのいずれかを指定します。
それでは、execv()関数を使ってプロセスを置き換え、あるプログラムから別のプログラムへ入力引数を渡す具体例を見てみましょう。
そのために、非常にシンプルな2つのコードを作成します。1つはexecv()関数を呼び出して子プロセスを実行する親プロセスです。execv()関数が子プロセスを起動する際、文字列形式の2つの入力引数を渡し、子プロセスはそれを受け取ってシェル上に表示します。
子プロセスのコード
子プロセスは、「I am the child process」というメッセージを表示し、親プロセスから送信された入力引数を受け取ってシェルに出力するだけのシンプルなコードです。以下がそのコードです。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf ("I am the child process\n\n");
printf ("Argument 1: %s\n", argv[1]);
printf ("Argument 2: %s\n", argv[2]);
}
このコードをコンパイルし、生成物を「Documents」ディレクトリに「child」という名前で「.bin」拡張子を付けて保存します。
~$ gcc Documents/child.c -o Documents/child.bin
これで、子プロセスの実行ファイルが「Documents」に保存されました。この実行ファイルのパスが、親プロセスでexecv()を呼び出す際のpath引数となります。
親プロセスのコード
親プロセスは、execv()関数を呼び出して子プロセスに置き換えられる側のプロセスです。このコードでは、execv()関数が起動するプロセスへ渡す入力引数となる文字列へのポインタ配列を定義します。
文字列へのポインタ配列は、次のように正しく作成します。ここでは4つのポインタで構成される「arg_Ptr[]」という配列を使用します。
ポインタ配列を定義したら、各ポインタに子プロセスへ送る入力引数を含む文字列を代入します。execXX()系関数を使用する際の基本ルールとして、最初の引数は実行ファイル名と拡張子を含む文字列、最後のポインタはNULLにする必要があります。
そこで、各ポインタに対応する引数を文字列形式で代入します。
arg_Ptr[0] = "child.bin";
arg_Ptr[1] = " Hello from ";
arg_Ptr[2] = " process 2 ";
arg_Ptr[3] = NULL;
次のステップはexecv()関数の呼び出しです。第1引数に実行ファイルの絶対パスを含む文字列を、第2引数に文字列配列arg_Ptr[]を渡します。親プロセスの完全なコードは以下の通りです。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main (){
printf ("I am the parent process");
char *arg_Ptr[4];
arg_Ptr[0] = " child.c";
arg_Ptr[1] = " Hello from ";
arg_Ptr[2] = " process 2 ";
arg_Ptr[3] = NULL;
execv("/home/linuxhint/Documents/child.bin", arg_Ptr);
}
このコードを、「.c」ファイルのパスと出力ファイル名を指定してコンパイルします。
~$ gcc Documents/parent.c -o pattern
続いて、生成された実行ファイルを実行します。
親プロセスは「I am the parent process」というメッセージを表示した後、次のプロセスに渡す各入力引数に文字列を代入して文字列配列を作成し、execv()関数を呼び出します。
execv()関数が正常に実行されると、「child.bin」実行ファイルが親プロセスを置き換え、そのPIDと割り当てられたメモリを引き継ぎます。この操作は元に戻すことができない点に注意してください。
その後、子プロセスは「I am the child process」というメッセージを表示し、親プロセスから渡された各入力引数を受け取ってコマンドコンソールに出力します。

fork()とexecv()を組み合わせた新規プロセスの作成
これまで見てきたように、fork()関数はプロセスを複製し、execv()関数はプロセスを置き換えます。このセクションでは、これら2つの関数を組み合わせて、複製したプロセスから新しいプロセスを起動し、それを別の実行ファイルに置き換える方法を紹介します。
具体的には、先ほど紹介した2つのコードを組み合わせます。fork()が親プロセスを複製し、execv()がその複製を、前の「child.bin」の例で使用したものと同じ実行ファイルに置き換えます。
まず、「.c」拡張子の空のファイルを作成し、fork()関数の例で示したプログラムコードを挿入します。
このプログラムでは、子プロセス側のコードのみを変更し、execv()関数によって「child.bin」実行ファイルに置き換わるようにします。メインの処理構造自体はfork()関数の例と同一です。
そのために、execv()関数の例のmain()関数の内容をコピーし、if文のブロックをこのコードで置き換えます。完成したプログラム全体は以下のようになります。
#include <stdio.h>
#include <unistd.h>
void main(){
if (fork() == 0)
{
printf ("I am the child process\n");
char *arg_Ptr[4];
arg_Ptr[0] = " child.c";
arg_Ptr[1] = " Hello from ";
arg_Ptr[2] = " process 2 ";
arg_Ptr[3] = NULL;
execv("/home/linuxhint/Documents/child.bin", arg_Ptr);
}
else
{
while (1){
printf ("Write by parent process \n");
sleep(3);}
}
}
このように、システム上に新しいプロセスが複製として作成され、その直後にexecv()関数によって「child.bin」実行ファイルのプロセスへと置き換えられます。次の画像は、このコードのコンパイルと実行結果です。

画像からわかるように、fork()関数が複製によって新しいプロセスを作成し、その後execv()関数がそれを「child.bin」実行ファイルに置き換えています。
まとめ
この記事では、Linux環境でfork()関数とexecv()関数を使用して新しいプロセスを起動する方法を解説しました。各関数の概要、構文、入出力引数を説明するとともに、理解を深めるためにそれぞれのサンプルコードを紹介し、呼び出し方法と各関数が担う役割を学びました。さらに、2つの関数を組み合わせて使用する方法を説明することで、C言語が提供するLinuxにおける新規プロセス作成の仕組みを実践的に習得できたかと思います。ぜひご自身の環境でもコードを書いて、挙動を確かめてみてください。
-
マッチ棒で作る三角ピラミッドに必要な本数を計算するC/C++プログラム
この記事では、下図のようなピラミッド状の三角形を作るために必要なマッチ棒の本数を求める方法を解説します。ピラミッドの底辺のサイズが与えられたとき、そのサイズに応じて必要なマッチ棒の総本数を計算します。例えば、底辺のサイズが1の場合は3本、底辺が2の場合は9本、底辺が3の場合は18本のマッチ棒が必要になります。必要なマッチ棒の本数を求める公式この問題を解くには、以下の公式を使用します。必要なマッチ棒の本数 = 3 × x × (x + 1) ÷ 2ここで、x はピラミッドの底辺のサイズです。この公式を使えば、ループで一つずつ数え上げることなく、O(1)の計算量で即座に答えを求められます。C++で
-
C言語で二分木の左側ビュー(Left View)を出力する方法を解説
この記事では、与えられた二分木(バイナリツリー)の左側ビュー(Left View)、すなわち木を左側から見たときに見えるノードを出力する方法を解説します。ユーザーがデータを挿入して二分木を構築し、その後、完成した木の左側ビューを表示するという流れです。 二分木の各ノードが持てる子は最大2つなので、プログラムは各ノードに紐づく左ポインタを優先的にたどる必要があります。 左ポインタがNULLでなければ、その先に子ノード(データ)が存在します。各レベルで最初に到達した最左ノードを出力対象とし、それを画面に表示します。 例 入力:1 0 3 2 4 出力:1 0 2 図のオレンジ色のノードが、二分木