C言語でのパイプとFIFOの効果的な使い方を解説

C言語におけるパイプとFIFOは、プロセス間通信を実現するための重要な機能です。本記事では、パイプとFIFOの基礎知識から具体的な使用方法、さらには応用例までを網羅的に解説します。これにより、プログラマは効率的にプロセス間通信を実装し、システムのパフォーマンスを向上させることができます。

目次

パイプとFIFOの基礎知識

パイプとFIFOは、C言語におけるプロセス間通信(IPC: Inter-Process Communication)の手法です。パイプは親子プロセス間の一方向データ転送に使用され、FIFOは名前付きパイプとも呼ばれ、独立したプロセス間でも使用可能です。どちらもバッファを介してデータを転送しますが、その使い方やシナリオには違いがあります。まず、それぞれの基本的な概念と仕組みを理解しましょう。

パイプの使い方

パイプは、親子プロセス間でデータを一方向に転送するための手段です。UNIXシステムで提供されるpipe()システムコールを使用して作成します。ここでは、基本的なパイプの使用方法とサンプルコードを紹介します。

パイプの作成

パイプはint pipe(int pipefd[2])関数を使用して作成されます。pipefdは2つのファイルディスクリプターを格納する配列で、pipefd[0]は読み取り用、pipefd[1]は書き込み用です。

#include <unistd.h>
#include <stdio.h>

int main() {
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }
    // pipefd[0] is for reading, pipefd[1] is for writing
    return 0;
}

データの送受信

親プロセスがデータを書き込み、子プロセスが読み取る基本的な例を示します。

#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main() {
    int pipefd[2];
    pid_t cpid;
    char buf;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        return 1;
    }

    if (cpid == 0) {    // Child process
        close(pipefd[1]);          // Close unused write end
        while (read(pipefd[0], &buf, 1) > 0) {
            write(STDOUT_FILENO, &buf, 1);
        }
        close(pipefd[0]);
    } else {            // Parent process
        close(pipefd[0]);          // Close unused read end
        write(pipefd[1], "Hello, world!\n", 14);
        close(pipefd[1]);          // Reader will see EOF
        wait(NULL);                // Wait for child
    }
    return 0;
}

この例では、親プロセスが「Hello, world!」という文字列をパイプに書き込み、子プロセスがそれを読み取って標準出力に表示します。

FIFOの使い方

FIFO(名前付きパイプ)は、パイプと同様にプロセス間通信を実現しますが、名前を持つことで無関係のプロセス間でも使用可能です。UNIXシステムで提供されるmkfifoコマンドやmkfifo()関数を使って作成します。ここでは、基本的なFIFOの使用方法とサンプルコードを紹介します。

FIFOの作成

FIFOはmkfifo(const char *pathname, mode_t mode)関数を使用して作成します。pathnameはFIFOの名前を指定し、modeはパーミッションを指定します。

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    const char *fifo = "/tmp/myfifo";
    if (mkfifo(fifo, 0666) == -1) {
        perror("mkfifo");
        return 1;
    }
    return 0;
}

データの送受信

FIFOを使ってデータを送受信する基本的な例を示します。まず、送信側のプロセスコードを示します。

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    const char *fifo = "/tmp/myfifo";
    int fd = open(fifo, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    write(fd, "Hello, FIFO!\n", 13);
    close(fd);
    return 0;
}

次に、受信側のプロセスコードを示します。

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    const char *fifo = "/tmp/myfifo";
    char buf[128];
    int fd = open(fifo, O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    read(fd, buf, sizeof(buf));
    printf("Received: %s\n", buf);
    close(fd);
    return 0;
}

この例では、送信側プロセスが「Hello, FIFO!」というメッセージをFIFOに書き込み、受信側プロセスがそれを読み取って表示します。FIFOを使用することで、独立したプロセス間でデータの送受信が可能になります。

パイプとFIFOの違い

パイプとFIFOはどちらもプロセス間通信(IPC)の手段として使用されますが、それぞれに特徴があります。ここでは、パイプとFIFOの違いを詳しく説明し、それぞれの利点と使用シナリオを比較します。

パイプの特徴

  • 一方向通信: パイプはデフォルトで一方向通信をサポートします。親プロセスと子プロセス間でデータを一方向に送受信するのに適しています。
  • 無名パイプ: パイプは名前を持たず、関連するプロセス内でのみ使用されます。
  • ライフサイクル: パイプはプロセスが終了すると消滅します。

FIFOの特徴

  • 双方向通信: FIFOは名前付きパイプであり、双方向通信が可能です。異なるプロセス間でデータの送受信に使用できます。
  • 名前付きパイプ: FIFOは名前を持ち、ファイルシステム上に存在します。これにより、異なるプロセスが名前を使ってアクセスできます。
  • 持続性: FIFOはファイルシステム上に存在し、削除されるまで持続します。プロセスが終了しても存在し続けます。

使用シナリオの比較

  • パイプ: 同じ親プロセスから生成された子プロセス間での通信に最適です。たとえば、コマンドラインツールでパイプ(|)を使ってデータを次のコマンドに渡す場合に適しています。
  • FIFO: 無関係なプロセス間での通信に適しています。たとえば、異なるユーザーのプロセス間でデータを交換する必要がある場合に便利です。

応用例:パイプを使ったプロセス間通信

パイプを使用することで、親プロセスと子プロセス間でデータを効果的に通信することができます。ここでは、パイプを用いて、プロセス間でデータを送受信する具体的な例を示します。

親プロセスから子プロセスへのデータ送信

この例では、親プロセスが子プロセスにメッセージを送信します。子プロセスはメッセージを受信し、それを標準出力に表示します。

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];
    pid_t cpid;
    char buf;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        return 1;
    }

    if (cpid == 0) {    // Child process
        close(pipefd[1]);          // Close unused write end
        while (read(pipefd[0], &buf, 1) > 0) {
            write(STDOUT_FILENO, &buf, 1);
        }
        close(pipefd[0]);
    } else {            // Parent process
        close(pipefd[0]);          // Close unused read end
        char *message = "Hello from parent process!\n";
        write(pipefd[1], message, strlen(message));
        close(pipefd[1]);          // Reader will see EOF
        wait(NULL);                // Wait for child to finish
    }
    return 0;
}

子プロセスから親プロセスへのデータ送信

この例では、子プロセスが親プロセスにメッセージを送信します。親プロセスはメッセージを受信し、それを標準出力に表示します。

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];
    pid_t cpid;
    char buf;

    if (pipe(pipefd) == -1) {
        perror("pipe");
        return 1;
    }

    cpid = fork();
    if (cpid == -1) {
        perror("fork");
        return 1;
    }

    if (cpid == 0) {    // Child process
        close(pipefd[0]);          // Close unused read end
        char *message = "Hello from child process!\n";
        write(pipefd[1], message, strlen(message));
        close(pipefd[1]);          // Send EOF to parent
    } else {            // Parent process
        close(pipefd[1]);          // Close unused write end
        while (read(pipefd[0], &buf, 1) > 0) {
            write(STDOUT_FILENO, &buf, 1);
        }
        close(pipefd[0]);
        wait(NULL);                // Wait for child to finish
    }
    return 0;
}

これらの例では、パイプを使って親プロセスと子プロセス間でメッセージを送受信しています。これにより、プロセス間での効率的なデータ通信が実現します。

応用例:FIFOを使ったプロセス間通信

FIFOを使用することで、独立したプロセス間でデータを送受信することができます。ここでは、FIFOを用いてプロセス間通信を行う具体的な例を示します。

送信プロセス

この例では、送信プロセスがFIFOを使ってメッセージを送信します。

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main() {
    const char *fifo = "/tmp/myfifo";
    // FIFOの作成
    if (mkfifo(fifo, 0666) == -1) {
        perror("mkfifo");
        return 1;
    }

    int fd = open(fifo, O_WRONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    char *message = "Hello from FIFO!\n";
    write(fd, message, strlen(message));
    close(fd);

    return 0;
}

受信プロセス

この例では、受信プロセスがFIFOを使って送信されたメッセージを受信します。

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    const char *fifo = "/tmp/myfifo";
    char buf[128];

    int fd = open(fifo, O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    read(fd, buf, sizeof(buf));
    printf("Received: %s\n", buf);
    close(fd);

    // FIFOの削除
    if (unlink(fifo) == -1) {
        perror("unlink");
        return 1;
    }

    return 0;
}

この例では、送信プロセスが「Hello from FIFO!」というメッセージをFIFOに書き込み、受信プロセスがそれを読み取って表示します。FIFOを使うことで、関連のないプロセス間でも簡単にデータを共有できます。

演習問題:パイプとFIFOの活用

パイプとFIFOの使用方法を理解するために、以下の演習問題を通じて実際に手を動かしてみましょう。これにより、プロセス間通信の理解が深まります。

演習1: パイプを使った双方向通信

親プロセスと子プロセスがそれぞれメッセージを送受信するプログラムを作成してください。親プロセスは子プロセスにメッセージを送り、子プロセスはそのメッセージを受け取って返信するようにします。

ヒント:

  • 2つのパイプを作成し、片方を親から子、もう片方を子から親に使用します。
  • pipe()fork()write()read()関数を使います。

演習2: FIFOを使ったチャットアプリケーション

2つの独立したプログラム(プロセス)がFIFOを使って簡単なチャットアプリケーションを作成してください。1つのプログラムは送信専用、もう1つは受信専用とします。

ヒント:

  • 送信プログラムはユーザーからの入力を受け取り、FIFOに書き込みます。
  • 受信プログラムはFIFOからメッセージを読み取り、画面に表示します。
  • mkfifo()open()write()read()関数を使います。

演習3: プロセス間のデータ転送効率の比較

パイプとFIFOを使って同じデータをプロセス間で転送し、それぞれの転送速度を比較するプログラムを作成してください。転送するデータ量を増やして、どちらが効率的かを測定します。

ヒント:

  • 同じサイズのデータをパイプとFIFOの両方で転送し、clock()関数を使って転送時間を計測します。
  • 結果を比較してレポートを作成します。

これらの演習を通じて、パイプとFIFOの実際の使用方法や効率の違いを体験してください。

まとめ

C言語におけるパイプとFIFOは、プロセス間通信を実現するための強力なツールです。パイプは親子プロセス間の一方向通信に適しており、FIFOは名前付きパイプとして独立したプロセス間でも使用可能です。この記事では、それぞれの基本的な使い方から応用例、そして演習問題までを通して理解を深めました。これらの技術を活用することで、システムのパフォーマンスを向上させ、効率的なデータ通信が可能になります。実際のプロジェクトでこれらの知識を活用し、より効果的なプログラムを作成してください。

コメント

コメントする

目次