この記事では、パイプを用いたプロセス間通信について紹介します。
パイプによるプロセス間通信は、Unix系システムでよく使われる方法の一つです。
パイプを使うことで、プロセス間でデータをやり取りすることができます。ここでは、C++でのパイプによるプロセス間通信の概要と簡単な実装例を紹介します。
パイプの概要
パイプは、Unix系システムで提供されるプロセス間通信の手法の一つで、データのストリームを作成することによって、異なるプロセス間でデータをやり取りするために使用されます。
パイプの特性
- 一方向通信
-
パイプは一方向の通信チャネルを提供します。つまり、データはパイプの一方の端から書き込まれ、他方の端から読み取られます。
双方向通信が必要な場合は、2つのパイプ(1つはプロセスAからプロセスBへの通信用、もう1つはプロセスBからプロセスAへの通信用)を作成する必要があります。
- 親子プロセス間での使用
-
パイプは親プロセスが子プロセスを生成する際に作成され、親プロセスと子プロセス間で使用されることが多いです。
fork
システムコールを使うことで、親プロセスと子プロセスは同じパイプを共有します。 - 匿名パイプと名前付きパイプ
-
匿名パイプは、関連するプロセス間でのみ使用され、システム内の他のプロセスには見えません。
pipe
システムコールを使って作成します。名前付きパイプ(FIFO)は、ファイルシステム上に存在し、名前を持つため、関連するプロセス以外のプロセスからもアクセスできます。
mkfifo
コマンドまたはmkfifo
システムコールを使って作成します。
パイプの基本操作
- パイプの作成
-
pipe
関数を使用して、パイプを作成します。pipe_fd[0]
は読み込み用のファイルディスクリプター、pipe_fd[1]
は書き込み用のファイルディスクリプターです。 - データの書き込みと読み込み
-
write
関数を使ってパイプにデータを書き込みます。read
関数を使ってパイプからデータを読み込みます。 - パイプの閉鎖
-
使用が終わったら、
close
関数を使ってパイプのファイルディスクリプターを閉じます。
実装例
親プロセスが子プロセスにメッセージを送り、子プロセスがそのメッセージを読み取って表示するプログラムを書いてみます。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstring>
int main() {
int pipe_fd[2];
if (pipe(pipe_fd) == -1) {
perror("pipe");
return 1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
if (pid == 0) {
// 子プロセス
close(pipe_fd[1]); // 書き込み端を閉じる
char buffer[100];
ssize_t count = read(pipe_fd[0], buffer, sizeof(buffer) - 1);
if (count == -1) {
perror("read");
return 1;
}
buffer[count] = '\0';
std::cout << "子プロセスが受け取ったメッセージ: " << buffer << std::endl;
close(pipe_fd[0]);
} else {
// 親プロセス
close(pipe_fd[0]); // 読み込み端を閉じる
const char* message = "こんにちは、子プロセス!";
if (write(pipe_fd[1], message, strlen(message)) == -1) {
perror("write");
return 1;
}
close(pipe_fd[1]);
wait(nullptr); // 子プロセスの終了を待つ
}
return 0;
}
ヘッダファイルのインクルード
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstring>
- iostream
-
標準入力出力の使用
- unistd.h
-
Unix標準APIの使用(
pipe
やfork
など) - sys/wait.h
-
wait
システムコールの使用 - cstring
-
文字列操作の使用
パイプの作成
int main() {
int pipe_fd[2];
if (pipe(pipe_fd) == -1) {
perror("pipe");
return 1;
}
ここでは、パイプを作成しています。
pipe_fd
は2つのファイルディスクリプタを持つ配列で、pipe_fd[0]
は読み取り用、pipe_fd[1]
は書き込み用です。
pipe
関数が失敗した場合、エラーメッセージを表示し、プログラムを終了します。
プロセスの分岐
pid_t pid = fork();
if (pid == -1) {
perror("fork");
return 1;
}
fork
関数を使ってプロセスを分岐します。fork
が失敗した場合、エラーメッセージを表示し、プログラムを終了します。
子プロセスの処理
if (pid == 0) {
// 子プロセス
close(pipe_fd[1]); // 書き込み端を閉じる
char buffer[100];
ssize_t count = read(pipe_fd[0], buffer, sizeof(buffer) - 1);
if (count == -1) {
perror("read");
return 1;
}
buffer[count] = '\0';
std::cout << "子プロセスが受け取ったメッセージ: " << buffer << std::endl;
close(pipe_fd[0]);
} else {
子プロセスは読み取りのみを行うため、pipe_fd[1]
(書き込み端)を閉じます。
read
関数を使って、pipe_fd[0]
(読み取り端)からデータを読み取ります。
読み取ったデータをbuffer
に格納し、終端文字('\0'
)を追加して文字列として扱います。
メッセージを標準出力に表示し、pipe_fd[0]
を閉じます。
親プロセスの処理
// 親プロセス
close(pipe_fd[0]); // 読み込み端を閉じる
const char* message = "こんにちは、子プロセス!";
if (write(pipe_fd[1], message, strlen(message)) == -1) {
perror("write");
return 1;
}
close(pipe_fd[1]);
wait(nullptr); // 子プロセスの終了を待つ
}
return 0;
}
親プロセスは書き込みのみを行うため、pipe_fd[0]
(読み取り端)を閉じます。
write
関数を使って、pipe_fd[1]
(書き込み端)にメッセージを書き込みます。
メッセージを書き込んだ後、pipe_fd[1]
を閉じます。
wait
関数を使って子プロセスの終了を待ちます。
実行結果
作成したプログラムを実行すると、コンソールに以下のメッセージが出力されます。
パイプを使って親プロセスから子プロセスに、データを送信できていることが確認できます。
子プロセスが受け取ったメッセージ: こんにちは、子プロセス!