モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

工作HardwareHub ロゴ画像 (Laptop端末利用時)
工作HardwareHub ロゴ画像 (Mobile端末利用時)

fork 関連のシステムコールのサンプルコード (C 言語)

モーダルを閉じる

ステッカーを選択してください

モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2018/06/09
最終更新最終更新
2020/10/14
記事区分記事区分
一般公開

以下のシステムコールに関する、C 言語のサンプルコードです。

  • fork
  • execv
  • exit
  • wait

子プロセスを fork で作成して execve でプログラムを実行

以下のプログラムでは、シェルで echo コマンドを実行したときと同じように、シェルに相当する親プロセスで fork で子プロセスを生成して、子プロセス内で execve によって echo プログラムを実行しています。外部プログラムを実行するためではなく、サーバプログラムの workerのように、複数のクライアントにサービスを行う目的で fork することもあり、必ずしも execve は実行しません。

main.c

#include <unistd.h> // fork(), execve()
#include <stdio.h>

int main() {

    // `man 2 fork` で確認できる、fork() の返り値を格納
    pid_t pid;

    // execve() の引数
    char *argv[3];
    extern char **environ; // プロセスの環境変数

    if((pid = fork()) < 0) { // 失敗時は -1 が返る
        perror("fork failed");
        return 1;
    }
    else if(pid == 0) { // 子プロセスでは pid は 0
        printf("ok from child\n");

        // execve() で実行するプログラムの引数を設定
        argv[0] = "echo"; // 実行プログラム名
        argv[1] = "message from echo program"; // 実行プログラムの引数
        argv[2] = NULL; // 実行プログラムの引数は NULL 終端する必要があります
        execve("/bin/echo", // 実行プログラムへの絶対パス (バイナリまたは実行可能なスクリプトファイル)
               argv,
               environ  // これも NULL 終端されています
               );

        // execve() に成功すると、プロセスID 等は変わらずに実行プログラムに処理が置き換わるため
        // 以下の処理は実行されません。

        _exit(1); // execve() に失敗するとここに到達
    }

    // 親プロセスの場合は、子プロセスの pid > 0
    printf("parent: child process pid = %d\n", pid);
    return 0;
}

実行例

$ gcc -Wall -O2 main.c && ./a.out
parent: child process pid = 12051
ok from child
message from echo program

environ について

外部リンケージをもつグローバル変数 environ にはプロセスの環境変数が格納されています。

extern char **environ; // プロセスの環境変数

int i = 0;
while(environ[i] != NULL) {
    printf("%s\n", environ[i++]);
}

main 関数の第三の引数にも同じ値が格納されているため、これを利用することもできます。

int main(int argc, char **argv, char **envp) {}
int main(int argc, char *argv[], char *envp[]) {}

execve 関連のライブラリ関数について

execve を内部的に利用する類似のライブラリ関数が存在します。

execve("/bin/echo", argv, environ);

e が名称に含まれない場合は環境変数は指定できず、現在のプロセスのものが引き継がれます。

execv("/bin/echo", argv);

l が名称に含まれている場合は、argv に相当する情報を直接引数として指定できます。

execl("/bin/echo", "echo", "message from echo program", NULL);

p が名称に含まれている場合は、実行プログラムを環境変数 $PATH から探すため、絶対パスを指定する必要がなくなります。

execvp("echo", argv);

v(e|p)l(e|p) で合わせて 6 種類存在することになります。

execle("/bin/echo", "echo", "message from echo program", NULL, environ);
execlp("echo", "echo", "message from echo program", NULL);

exit で返した終了ステータスを wait で受け取る

man 2 _exit で確認できるシステムコール exit でプログラムを終了すると、親プロセスは wait システムコールによって終了ステータスを受け取ることができます。親プロセスがシェルの場合は、特殊変数 $? で確認できる値です。子プロセスよりも先に親プロセスが終了した場合は、プロセスID 1 の init プロセスが子プロセスの親となり、終了ステータスを受け取ります。C ライブラリ関数の exit()_exit() を内部的に利用する関数で、親プロセスから引き継いだバッファを flush して出力する等の処理も行うため、親と子で重複して出力してしまう問題等を回避するためには _exit() を利用します。

main.c

#include <sys/wait.h> // wait()
#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t pid;

    // exit 終了ステータスの情報を格納するための変数
    int status;

    if((pid = fork()) < 0) {
        perror("fork failed");
        return 1;
    }
    else if(pid == 0) {
        _exit(16); // 子プロセスをステータス 16 で終了
    }

    // wait() によって、親プロセスは子プロセスが終了するまで待機
    if((pid = wait(&status)) < 0) {
        perror("wait failed");
        return 1;
    }

    // wait() から取得できる status はそのままでは利用できません
    printf("status = %d\n", status);

    // マクロ関数 WIFEXITED() と WEXITSTATUS() で終了ステータスに変換します
    // シグナル等で終了した場合は偽、exit で終了した場合は真になります
    if(WIFEXITED(status)) {
        // exit で終了した場合の終了ステータスを取得します
        printf("pid = %d, exit status = %d\n", pid, WEXITSTATUS(status));
    }
    return 0;
}

実行例

$ gcc -Wall -O2 main.c && ./a.out
status = 4096
pid = 54472, exit status = 16

pid を指定して待ちたい場合

wait(&status) は、一つの子プロセスが終了するまで待機しますが、waitpid(pid, &status, 0) を利用すると、指定した子プロセスが終了するまで待機できます。第三引数を利用しない場合は 0 を指定します。待つ対象となるプロセスID を指定するためには引数の pid に正の値を指定します。自分と同じプロセスグループの子プロセスを待つためには 0 を指定します。

子プロセスが利用したリソース情報を取得したい場合

wait3() または wait4() を利用します。

struct rusage usage;

wait3(&status, 0, &usage)
wait4(pid, &status, 0, &usage); // pid を指定したい場合

// リソース例: ユーザ時間、システム時間 (実時間ではない)
usage.ru_utime.tv_sec // long 秒
usage.ru_utime.tv_usec // long マイクロ秒
usage.ru_stime.tv_sec
usage.ru_stime.tv_usec
0
詳細設定を開く/閉じる
アカウント プロフィール画像 (本文下)

どもです

記事の執筆者にステッカーを贈る

有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

さらに詳しく →
ステッカーを贈る コンセプト画像

Feedbacks

Feedbacks コンセプト画像

    ログインするとコメントを投稿できます。

    関連記事

    • cmake で ccache を有効化するための設定
      YOCTO Linux で開発している場合など、ビルド速度が開発効率にそのまま影響する際は ccache (compiler cache) で C/C++ ビルドを高速化することを考えます。cmakeと併用する場合の設定およびコマンド例を記載します。 インストール sudo apt install ccache 以下のバイナリファイルに加えて $ w
      AWS NinjaAWS Ninja11/22/2022に更新
      いいねアイコン画像0
    • Python から C ライブラリを利用 (ctypes)
      FFI (Foreign Function Interface) の一つである ctypes を利用すると、C 言語のライブラリを Python から利用できます。サンプルコードを記載します。 適宜参照するための公式ドキュメント libm の sqrt を利用する例 main.py ``
      coderinacoderina9/2/2021に更新
      いいねアイコン画像0
    • 低レイヤーネットワークプログラミングに関する雑多な知識
      TCP/IP モデルのうちトランスポート層ではなく、インターネット層およびネットワークインターフェイス層のパケット (正確には PDU) を扱う低レイヤープログラミングの雑多なテクニックをまとめます。『ルーター自作でわかるパケットの流れ』などを参考にしています。バックアップ目的で書籍のサンプルコードをホスティングしました。 検証環境
    • ファイルディスクリプタ関連のシステムコールのサンプルコード (C 言語)
      ファイル記述子 (File Descriptor) に関連するシステムコールを利用した C 言語のサンプルコードを記載します。 ファイルの読み書き open/close main.c #include <unistd.h> #include <fcntl.h> #include <stdio.h> int main() { int fd_r, fd_w;...
      フロックフロック10/7/2021に更新
      いいねアイコン画像0
    • C言語の資産を利用 (C++をもう一度)
      サンプルコード メルセンヌ・ツイスタなど、C言語で記述されたライブラリをC++から利用するためには extern "C" を利用します。その際、組み込みマクロ __cplusplus を利用するとC言語からもC++からも利用できるヘッダファイルを作成できます。 sub.h #ifndef SUB_H_ #define SUB_H_ #ifdef __cplu