Rainbow Engine

IT技術を分かりやすく簡潔にまとめることによる学習の効率化、また日常の気付きを記録に残すことを目指します。

C++ Linux

パイプ通信とは?概要やサンプルプログラムをご紹介

投稿日:2021年11月28日 更新日:

 

<目次>

(1) パイプ通信とは?概要やサンプルプログラムをご紹介
 (1-1) パイプ通信の概要
 (1-2) パイプ通信の構文
 (1-3) パイプ通信のサンプルプログラム①(単一プロセス版)
 (1-4) パイプ通信とは?概要やサンプルプログラムをご紹介
 (1-5) 用語

(1) パイプ通信とは?概要やサンプルプログラムをご紹介

(1-1) パイプ通信の概要

・パイプ通信はUnix系OSにおいて、プロセス間の通信に利用されます。
・あるプロセスの「標準出力」(stdout)が、別のプロセスの「標準入力」(stdin)になるというコンセプトです。

(図141)イメージ

●pipe()システムコールの特徴

・パイプは一方通行の通信であり、基本的にはあるプロセスがパイプに書き込み⇒別のプロセスがパイプを読み込むという流れになります。
 
・パイプの読込み/書込みの前後で、まずはパイプのオープン(open)とクローズ(close)を行います。
 
・パイプをオープン(open)は実態として、メモリの一部を「仮想ファイル」として確保する処理になります。
 
・パイプは基本的にFIFO(First In First Out)のルールで読み書きをするため、キュー(queue)のように振る舞います。
 
・もしパイプに書込み(write)が行われる前に、あるプロセスがパイプを読込み(read)しようとした場合、何か書込み処理がされるまでそのプロセスは「一時停止」します。
 
・また、読込み(read)と書込み(write)のサイズは一致する必要はありません(10バイトwriteして1バイトreadも可能)

(1-2) パイプ通信の構文

・①パイプ生成

最初にpipe関数でパイプの読込み/書込みの前準備を行います(パイプの生成と、読込み/書込みのファイルディスクリプタの生成)。ファイルディスクリプタは、ファイルやパイプ等の操作(read/write等)をする際に、操作対象を特定するために割り当てされた一意なキー情報で、OSに処理依頼する際に引き渡します。

pipe関数の引数にint型の配列(2要素)を与える事で、その配列内に読込み用(read)と書込み用(write)の2つのファイルディスクリプタを生成します。

具体的には「pipefds[0]」が読込み用で、「pipefds[1]」が書込み用です。「pipefds[1]」に書き込んだ内容を「pipefds[0]」から読み込む事ができます。

(構文)

int pipe(int pipefds[2]);
 
(構文説明)
・pipe()関数は「unistd.h」に定義されているので「#include <unistd.h>」して使います。
・pipefds[0]はパイプの読込み側を表します(読込みに使用されます)
・pipefds[1]はパイプの書込み側を表します(書込みに使用されます)。
・戻り値は、正常終了時は「0」、異常終了時は「-1」を返却します。

・②パイプのオープン

次にopen関数でパイプのオープンを行います。この手順は、後続でパイプを読み書きするために必須の手順になります。

(構文)

int open(const char *pathname, int flags);
 
(構文説明)
・第1引数「const char *pathname」で指定したファイルパスのファイルを開きます(存在しない場合は作る事も可)
・第2引数「int flags」でファイルの用途を指定します(例:O_RDONLY=読込み専用、O_WRONLY=書込み専用など)
・戻り値はオープンしたファイルの「ファイルディスクリプタ」を返却します。この値は後続処理(read/writeなど)で使用します。
 

・③パイプの書き込み

次にパイプへの書込み(write)についてです(*正確にはファイルディスクリプタへの書込み)。第1引数で指定したファイルディスクリプタに対して、第2引数のバッファの内容を、第3引数のサイズ分(バイト数)だけ書込み(write)します。

(構文)

ssize_t write(int fd, const void *buf, size_t count);
//# 例:第1引数は書込み用のfds[1]を指定
write(fds[1], msg1, 32);
 
(構文説明)
・第1引数は書込み対象ファイルの「ファイルディスクリプタ」を指定します。
・第2引数は書込み対象のデータを保持したバッファ(一時領域)を指定します。様々な型に変換できるようvoid型になっています。
・第3引数は書込み対象のデータのサイズ(単位:バイト)を指定しています。
・戻り値の「ssize_t」は書き込み(write)されたメモリのブロックサイズを整数値で返却します。エラーの場合は「-1」を返却します。

・④パイプの読み込み

次にパイプからの読込みついてです(*正確にはファイルディスクリプタへの読込み)。第1引数で指定したファイルディスクリプタから、第2引数のバッファに、第3引数のサイズ分(バイト数)だけ読込み(read)します。
(構文)
ssize_t read(int fd, void *buf, size_t count);
//# 例:第1引数は書込み用のfds[0]を指定
read(fds[0], buffer, 32);
 
(構文説明)
・第1引数は読込み対象ファイルの「ファイルディスクリプタ」を指定します。
・第2引数は読込み対象のデータを展開するためのバッファ(一時領域)を指定します。様々な型に変換できるようvoid型になっています。
・第3引数は読込み対象のデータのサイズ(単位:バイト)を指定しています。
・戻り値の「ssize_t」は読み込み(read)されたメモリのブロックサイズを整数値で返却します。エラーの場合は「-1」を返却します。
 

(1-3) パイプ通信のサンプルプログラム①(単一プロセス版)

まずは、単一プロセスのパイプ通信の例です。この例では1つのプロセスがパイプに対して読み込みと書き込みを両方行います。

(図131)イメージ図

(サンプル)

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#define MESSAGESIZE 32
char* msg1 = "Apple";
char* msg2 = "Banana";
char* msg3 = "Strawberry";
char* msg4 = "Melon";
  
int main()
{
    char buffer[MESSAGESIZE];
    int fds[2], i;
    
    //# パイプの生成
    //# エラーの場合、処理終了する 
    if (pipe(fds) < 0)
        exit(1);
  
    //# 正常の場合、処理継続する
    //# パイプへの書込み
  
    write(fds[1], msg1, MESSAGESIZE);
    write(fds[1], msg2, MESSAGESIZE);
    write(fds[1], msg3, MESSAGESIZE);
    write(fds[1], msg4, MESSAGESIZE);
  
    //# パイプからの読込み
    for (i = 0; i < 4; i++) {
        //# パイプの読込み
        read(fds[0], buffer, MESSAGESIZE);
        std::cout << buffer << std::endl;
    }
    return 0;
}
(図132)
・コンパイル
GCCコンパイラ等で、プログラムをコンパイルします。

#コマンド
g++ -g -o ./[ご自身のプログラム名] ./[ご自身のプログラム名].cpp -Wno-write-strings
#例
g++ -g -o ./pipe_test ./pipe_test.cpp -Wno-write-strings
(図133)
・実行
パイプに書き込み(write)した4個の単語が、読み込み(read)出来ている事が確認できました。
(図134)

パイプ通信のサンプルプログラム②(親プロセス&子プロセス版)

次に、親プロセスと子プロセス間のパイプ通信の例です。この例では、子プロセスから書き込み(write)した情報を、親プロセスで読み込み(read)する例です。

ポイントはfork()(⇒★リンク)で親プロセスから子プロセスを起動している点です(ここが前の例との一番の違い)。パイプを生成した後にfork()で子プロセスを起動すると、親プロセスと子プロセスとの間でパイプ通信が可能になります。

(図141)

(サンプル)

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
#define MESSAGESIZE 32
char* msg1 = "Apple";
char* msg2 = "Banana";
char* msg3 = "Strawberry";
char* msg4 = "Melon";
  
int main()
{
    char buffer[MESSAGESIZE];
    int fds[2], pid, bytelen;

    //# パイプの生成(fds[0]=read用、fds[1]=write用)
    //# エラーの場合、処理終了する   
    if (pipe(fds) < 0)
        exit(1);
  
    //# 正常の場合、処理継続する
    //# ↓このif文の中は「親プロセス」による書き込み(write)処理
    if ((pid = fork()) > 0) {
        //# パイプへの書込み
        std::cout << pid << std::endl;
        write(fds[1], msg1, MESSAGESIZE);
        write(fds[1], msg2, MESSAGESIZE);
        write(fds[1], msg3, MESSAGESIZE);
        write(fds[1], msg4, MESSAGESIZE);
        // PIDを標準出力に表示
        std::cout << "PID(Parent Process) : " << (long)getpid() << std::endl;
        // ハング防止
        close(fds[1]);
        wait(NULL);
    }
    //# ↓このelse文の中は「子プロセス」による読み込み(read)処理
    //# (子プロセスではfork()以降に記述されている処理が実行されるため)
    else {
        // ハング防止
        close(fds[1]);
        // データがある間(while)読み込みを続ける
        while ((bytelen = read(fds[0], buffer, MESSAGESIZE)) > 0)
            // 読み込んだ値を標準出力に表示
            std::cout << buffer << std::endl;
            // PIDを標準出力に表示
            std::cout << "PID(Child Process) : " << (long)getpid() << std::endl;
        // バイト長が0の場合(値が無い場合)
        if (bytelen != 0)
            exit(2);
        std::cout << “### FINISH READING ###” << std::endl;
    }
    return 0;
}

(図142)

・コンパイル
GCCコンパイラ等で、プログラムをコンパイルします。

#コマンド
g++ -g -o ./ [ご自身のプログラム名] ./[ご自身のプログラム名].cpp -Wno-write-strings
#例
g++ -g -o ./pipe_test2 ./pipe_test2.cpp -Wno-write-strings

(図143)

(図144)

用語

●①ssize_t
・「size_t」は割当てされたブロックのサイズを表現するために使われる、「符号なし整数型」を表す型です。
・一方で「s」を付けた「ssize_t」は同じ用途の「符号付き整数型」バージョンです。「負の数」を返却する可能性がある場合は「ssize_t」を使用します(エラーを意味する「-1」を使用する場合など)

●ファイルディスクリプタとは
ファイルやパイプ等の操作(read/write等)をする際に、操作対象を特定するために割り当てされた一意な番号で、OSに処理依頼する際に引き渡します。

Adsense審査用広告コード


Adsense審査用広告コード


-C++, Linux

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

C++の値渡しと参照渡しの使い分けや違いについて

<目次> (1) C++の値渡しと参照渡しの使い分けや違いについて  (1-1) 「値渡し」とは?  (1-2) 「参照渡し」とは?  (1-3) 「値渡し」と「参照渡し」の使い分け (1) C++の …

Valgrindの使い方や見方について(基礎編)

  <目次> (1) Valgrindの使い方や見方について(基礎編)  (1-1) メモリリークのチェックのやり方  (1-2) 「valgrind」コマンドの基本的な使い方  (1-3) …

Linux基礎コマンド(第2回) ls・mkdir・cp・mv

初めてLinuxを触る人向けにTeraTermのログイン方法からコマンドを入力する方法まで数回にわたり解説していきます。本記事はその第2弾です!第1弾の記事はこちら(第1弾)です。 (0)目次&概説 …

さくらVPS入門!CentOSを初期設定する手順の例をご紹介

<目次> (1) さくらVPS入門!CentOSを初期設定する手順の例をご紹介  (1-1) 設定の概要  (1-2) 【OS設定#1】一般ユーザの作成(rootでないユーザ)  (1-3) 【OS設 …

C++の動的メモリ割当と静的メモリ割当の違いについて

  <目次> (1) C++の動的メモリ割当と静的メモリ割当の違いについて  (1-1) 静的メモリ割当てとは?  (1-2) 動的メモリとは?  (1-3) 動的メモリの割当て  (1-4 …

  • English (United States)
  • 日本語
Top