Cプログラミング
 Computer >> コンピューター >  >> プログラミング >> Cプログラミング

C言語におけるfork()とexec()システムコールの違いを徹底解説

はじめに:fork()とexec()とは

本記事では、C言語におけるfork()exec()システムコールの動作と、両者の違いについて詳しく解説します。fork()は、呼び出し元のプロセスを複製することで新しいプロセスを生成するシステムコールです。新しく生成されたプロセスは「子プロセス」と呼ばれます。

子プロセスの主な特性

  • 子プロセスは独自のプロセスID(PID)を持ちます。
  • 子プロセスの親プロセスID(PPID)は、呼び出し元プロセスのPIDと同一になります。
  • 子プロセスは親プロセスのメモリロックやセマフォを継承しません。

fork()の返り値は子プロセスのPIDです。返り値が0以外の場合は親プロセス側の処理、0の場合は子プロセス側の処理であることを示します。この仕組みにより、1つのプログラム内で親プロセスと子プロセスそれぞれに異なる処理を記述できます。

一方、exec()システムコールは、現在のプロセスイメージを新しいプロセスイメージで置き換えるために使用されます。指定されたプログラムを現在のプロセス空間にロードし、エントリポイントから実行を開始します。exec()が正常に完了すると、元のプログラムには制御が戻りません。

fork()とexec()の主な違い

  • fork():メインプロセス(親プロセス)のコピーである新しいプロセスを生成します。親プロセスと子プロセスは並行して実行されます。
  • exec():新しいプロセスは生成せず、現在のプロセスイメージを新しいプログラムに置き換えて実行します。

実践では、fork()で子プロセスを生成し、その子プロセス内でexec()を呼び出して別のプログラムを実行するという組み合わせが広く使われています。シェルが外部コマンドを実行する際も、まさにこの仕組みが利用されています。

サンプルコード

以下は、fork()で子プロセスを生成し、execv()によって「ls」コマンドを実行するサンプルプログラムです。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
int main() {
    pid_t process_id;
    int return_val = 1;
    int state;
    process_id = fork();
    if (process_id == -1) { //プロセスIDが負の値の場合はエラー、forkに失敗
        printf("can't fork, error occured\n");
        exit(EXIT_FAILURE);
    } else if (process_id == 0) { //子プロセスが生成された場合
        printf("The child process is (%u)\n",getpid());
        char * argv_list[] = {"ls","-lart","/home",NULL};
        execv("ls",argv_list); // execv()はエラー発生時のみ戻る
        exit(0);
    } else { //親プロセスの場合
        printf("The parent process is (%u)\n",getppid());
        if (waitpid(process_id, &state, 0) > 0) { //子プロセスの状態が変化するまで待機
            if (WIFEXITED(state) && !WEXITSTATUS(state))
                printf("program is executed successfully\n");
            else if (WIFEXITED(state) && WEXITSTATUS(state)) {
                if (WEXITSTATUS(state) == 127) {
                    printf("Execution failed\n");
                } else
                    printf("program terminated with non-zero status\n");
            } else
                printf("program didn't terminate normally\n");
        }
        else {
            printf("waitpid() function failed\n");
        }
        exit(0);
    }
    return 0;
}

実行結果

The parent process is (8627)
The child process is (8756)
program is executed successfully

実行結果のポイントは以下の通りです。

  • 親プロセスはfork()呼び出し後、waitpid()で子プロセスの終了を待機します。
  • 子プロセスではexecv()により「ls -lart /home」コマンドが実行されます。
  • 子プロセスが終了ステータス0で正常終了すると、「program is executed successfully」と表示されます。

補足:execファミリー関数について

exec()には用途に応じて複数のバリエーションが用意されています。

  • execl() / execv():引数をリスト形式/配列形式で渡します。
  • execle() / execve():環境変数を明示的に指定できます。
  • execlp() / execvp():PATH環境変数を検索して実行ファイルを探します。

サンプルコードで使用しているexecv()は、コマンドライン引数をNULL終端の文字列配列として渡す形式です。なお、execv()の第1引数には実行ファイルのパスを指定する必要があるため、実際の環境では「ls」ではなく「/bin/ls」のようにフルパスを指定するのが確実です。

まとめ

fork()はプロセスを複製して新しいプロセスを生み出すシステムコール、exec()は現在のプロセスを別のプログラムに置き換えるシステムコールです。fork()で生成した子プロセス内でexec()を呼び出せば、親プロセスを維持したまま別のプログラムを実行できます。プロセス制御の基礎となる重要な仕組みなので、動作をしっかり理解しておきましょう。

  1. 【CSS入門】display:noneとvisibility:hiddenの違いを徹底解説!使い分けのポイント

    CSSでは、visibilityプロパティとdisplayプロパティを使って、HTMLドキュメント内の要素を非表示にすることができます。一見すると、どちらのプロパティを使っても見た目は同じように思えるかもしれませんが、実は両者には重要な違いがあります。 display: none と visibility: hidden の基本的な違い display: none — 要素そのものがドキュメント上にレンダリングされず、領域(スペース)も一切確保されません。要素が最初から存在しなかったかのような挙動になります。 visibility: hidden — 要素はドキュメント上にレンダリングされ、

  2. Bashの特殊変数「$$」と「$BASHPID」の違いを徹底解説

    シェルスクリプトの作業中に、Bashの特殊変数 $$ と $BASHPID の間に大きな動作の違いがあることに気づきました。Linux上で実行されるすべてのプロセスにはプロセスID(PID)が割り当てられ、OSはこのIDをもとにプロセスを管理しています。 関連記事: Bashにおけるソース読み込み(source)とフォーク(fork)の違いを学ぶ 同様に、あなたが開いているBashターミナルのセッションにもプロセスIDが割り当てられます。そして、現在のシェルのプロセスIDを格納する特別な変数として $$ と $BASHPID の2つが用意されています。 現在のシェルのPIDを確認する まずは以