S-JIS[2012-11-17]

pipe関数

pipe()は、C言語でパイプを生成する関数。


概要

pipe関数でいうパイプとは、バッファーのようなもの。
パイプに対してデータを書き込んでおき、そのパイプからデータを読み込むことが出来る。

このバッファーはOS(カーネル)の機能を使っている。一時ファイルのようなイメージ。
なので、1つのパイプを複数のプロセスで共有すれば、プロセス間通信に使うことが出来る。


とりあえずパイプの使い方の確認。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
	int pipefd[2];
	if (pipe(pipefd) < 0) {
		perror("pipe");
		exit(-1);
	}

	// パイプへの書き込み
	char *s = "test";
	write(pipefd[1], s, strlen(s)); //エラー処理省略

	// パイプからの読み込み
	char buf[128];
	read(pipefd[0], buf, sizeof buf); //エラー処理省略

	printf("buf=[%s]\n", buf);

	close(pipefd[1]); //エラー処理省略
	close(pipefd[0]); //エラー処理省略

	return 0;
}

pipe()の引数には、intの配列を渡す。
ここに書き込み用と読み込み用のファイルディスクリプターが入ってくる。(添え字0が読み込み用、1が書き込み用)
なので、後の操作はファイルディスクリプターを使ったファイル読み書きと同じ。


プロセス間通信の例

fork()を使って子プロセスを生成し、パイプを使って通信する例。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
	int pipefd[2];
	if (pipe(pipefd) < 0) {
		perror("pipe");
		exit(-1);
	}

	pid_t pid = fork();
	if (pid < 0) {
		perror("fork");
		exit(-1);
	} else if (pid == 0) {
		// 子プロセス
		char *s = "send from child";
		write(pipefd[1], s, strlen(s));

//		sleep(1);

		char buf[128];
		read(pipefd[0], buf, sizeof buf);
		printf("child=[%s]\n", buf);

		close(pipefd[0]);
		close(pipefd[1]);
		return 0;
	} else {
		// 親プロセス
		char *s = "send from parent";
		write(pipefd[1], s, strlen(s));

		char buf[128];
		read(pipefd[0], buf, sizeof buf);
		printf("parent=[%s]\n", buf);

		close(pipefd[0]);
		close(pipefd[1]);
		return 0;
	}
}

fork()する前にパイプを作っておく。
forkしても、親プロセスと子プロセスのパイプの実体の指し先は同じまま。なので、親プロセス側で書き込んでも子プロセス側で書き込んでも、親プロセスまたは子プロセスから読み込める。

ただし、このプログラムだと、書き込んだものがどちらのプロセスから読まれるかは、タイミング次第。
(いずれにしても、ひとつの書き込みが両方から読めるということにはならない)


親プロセス→子プロセス、あるいは子プロセス→親プロセスへの一方向の通信に限定すれば、タイミングの問題を気にする必要は無い。
送信側の読み込みをクローズし、受信側の書き込みをクローズしておけば一方向の通信が実現できる。

親プロセスから子プロセスへの通信 子プロセスから親プロセスへの通信
int pipefd[2];
if (pipe(pipefd) < 0) {
	perror("pipe");
	exit(-1);
}
pid_t pid = fork();
if (pid < 0) {
	perror("fork");
	exit(-1);
} else if (pid == 0) {
	// 子プロセス
	close(pipefd[1]); //書き込みをクローズ

	char buf[128];
	read(pipefd[0], buf, sizeof buf);
	printf("child=[%s]\n", buf);

	close(pipefd[0]);
	return 0;
} else {
	// 親プロセス
	close(pipefd[0]); //読み込みをクローズ

	char *s = "send from parent";
	write(pipefd[1], s, strlen(s));


	close(pipefd[1]);
	return 0;
}
int pipefd[2];
if (pipe(pipefd) < 0) {
	perror("pipe");
	exit(-1);
}
pid_t pid = fork();
if (pid < 0) {
	perror("fork");
	exit(-1);
} else if (pid == 0) {
	// 子プロセス
	close(pipefd[0]); //読み込みをクローズ

	char *s = "send from child";
	write(pipefd[1], s, strlen(s));


	close(pipefd[1]);
	return 0;
} else {
	// 親プロセス
	close(pipefd[1]); //書き込みをクローズ

	char buf[128];
	read(pipefd[0], buf, sizeof buf);
	printf("parent=[%s]\n", buf);

	close(pipefd[0]);
	return 0;
}

親プロセスから子プロセスの標準入力への通信の例

子プロセスのパイプを標準入力につないでやれば、親プロセスがパイプに書き込んだものを子プロセスが標準入力から読み込むことが出来る。

親プロセスも子プロセスも自分で作る場合は上述の様にforkすればパイプを共有してプロセス間通信が出来るが、子プロセスが他アプリだとパイプを共有できない。
そういう場合に標準入力の方法を使うことが出来る。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
	int pipefd[2];
	if (pipe(pipefd) < 0) {
		perror("pipe");
		exit(-1);
	}

	pid_t pid = fork();
	if (pid < 0) {
		perror("fork");
		exit(-1);
	} else if (pid == 0) {
		// 子プロセス
		close(pipefd[1]); //書き込みをクローズ

		
		dup2(pipefd[0], STDIN_FILENO); //パイプの読み込みを標準入力につなぐ
		close(pipefd[0]);              //つないだらパイプはクローズする

		execl("/bin/cat", "/bin/cat", NULL); //catは標準入力のデータをそのまま出力するコマンド
		perror("/bin/cat");
		return -1;
	} else {
		// 親プロセス
		close(pipefd[0]); //読み込みをクローズ

		char *s = "send from parent";
		write(pipefd[1], s, strlen(s));

		close(pipefd[1]);
		return 0;
	}
}

dup2()を使って子プロセス側のパイプの読み込みを標準入力へ複製し、パイプの方はクローズする。
こうすると、親プロセスがパイプに書き込んだデータが子プロセスの標準入力から読み込める。
子プロセスでexecを使って起動した別プログラムでも標準入力から読み込める。

dup2()はファイルディスクリプターを複製する関数。
第2引数(複製先)のファイルディスクリプターは、オープンされていれば最初に自動的にクローズされる。


C言語目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま