1968年、UNIXの産みの親であるKen ThompsonとDennis RitchieがAT&Tのベル研究所でMulticsと呼ばれる大型のオペレーティングシステムを開発していた頃、「インタラクティブで便利なコンピュータサービス」が欲しいと言って作ったのがUNIXです。
彼らは、「ベル研の文書処理システムを作る」と言って予算を引き出し、PDP-11(システムメモリ16Kバイト、ユーザメモリ8Kバイト、ハードディスク512Kバイト)という現在のPDA以下のハードウェアを購入し、その上に現在のUNIXシステムのコマンド群とroffと呼ばれる文書処理システムを構築したのでした。
今では、Linuxの普及により誰でもUNIXの環境を持つことができるようになりました。
今回は、「パイプでつなく」をテーマにUNIXの偉大な大発明の中から、
について例題を交えながら説明していきます。
パイプを使った処理の例として、「tr」コマンドのmanページの単語の種類をカウントします。
$ man tr | tr -cs 'A-Za-z' '\n' | sort -u | wc -l 492
このようにUNIXのコマンドをパイプでつなくことによって簡単に必要な処理をこなすことができます。
「単語処理の例」は、別にパイプを使わなくてもファイルを使って逐次的に処理できます。 パイプがファイルと決定的に異なる点は、リアルタイムの処理です。
図は、マウスイベントを別Windowにプロットする例です。
このデモでは、xevコマンドとEvent2Plot, Graphのシェルスクリプトをパイプでつないで、xplotの画面にイベントをリアルタイムでプロットします。
$ xev | Event2Plot | Graph
pipeシステムコールは、本当に不思議な関数です。自分が出力したものを自分へ送るストリームの輪(パイプ)がpipeシステムによって生成されます。 マニュアルの説明を読んだだけでpipeシステムコールの使い方を理解できる人は少ないでしょう。
パイプでつなぐときに必要なシステムコール
について、おさらいも含めて説明します。
forkシステムコールの仕様を簡単に書くと、
呼び出し形式 #include <unistd.h> pid_t fork(void); 機能 呼び出し元のプロセスをコピーして、新しいプロセスを生成する。 戻り値 成功すると、子プロセスには0が返され、 親プロセスには、子プロセスのプロセスIDが返されます。forkに失敗したら、-1を返します。
です。
forkでは、
ます。
簡単なプログラムで、上記の仕様を確認してみましょう。
#include <stdio.h> #include <unistd.h> main() { int pid; if ((pid = fork()) == -1) { fprintf(stderr, "can't fork\n"); } else if (pid == 0) { // child fprintf(stdout, "this is a child process\n"); fprintf(stderr, "pid(child)=%d\n", pid); } else { // parent fprintf(stdout, "this is a parent process\n"); fprintf(stderr, "pid(parent)=%d\n", pid); } }
以下のようにコンパイルして、実行すると
$ cc -o ex1 ex1.c $ ex1 this is a parent process pid(parent)=6556 this is a child process pid(child)=0
と出力され、分岐とpidの値が正しくセットされていることが分かります。
次に、標準出力(1)とエラー出力(2)をリダイレクトでファイルに保存します。
$ ex1 1>1.out 2>2.out $ more *.out << 1.outの内容 >> this is a parent process this is a child process << 2.outの内容 >> pid(parent)=6570 pid(child)=0
と、親と子プロセスが同じファイルに書き込み、ファイルの共有がされていることが確認できます。
pipeシステムコールの仕様を簡単に書くと、
呼び出し形式 #include <unistd.h> int pipe(int fildes[2]); 機能 パイプを生成し、ペアのファイル記述子を割り当てる。1個目のファイル記述子が読み込み、 2個目が書き込み用となる。 fildes[1]に書き込まれたデータは、fildes[0]から読み込まれる。これにより、 あるプログラムの出力をから他のプログラムの入力にすることができる。 読み込みまたは書き込みのファイル記述子のいずれかがクローズするとwindowed(未亡人)となる。 windowedとなったパイプに書き込むと書き込みプロセスはSIGPIPEシグナルを受け取る。 windowedにすることで、読み込みプロセスにEnd-Of-Fileを送ることができる。 読み手がパイプのデータをすべて読み込んだ後やwindowedになった後のパイプから読み込もうとすると 0が返される。 戻り値 成功すると0を返し、そうでない場合には-1を返し、変数errnoにエラー番号をセットする。
パイプは、
のように機能します。
簡単なプログラムで、上記の仕様を確認します。
// ex2.c : pipe example #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUFSIZE (128) main() { int pid; int fd[2]; // create a pipe. if (pipe(fd) == -1) { fprintf(stderr, "Can't create pipe\n"); exit(1); } printf("fd[0]=%d, fd[1]=%d\n", fd[0], fd[1]); if ((pid = fork()) == 0) { // child char buf[BUFSIZE]; int len = 0; // close pipe out. close(fd[1]); // read message. len = read(fd[0], buf, BUFSIZE); printf("len=%d, buf=%s", len, buf); len = read(fd[0], buf, BUFSIZE); close(fd[0]); fprintf(stderr, "child finishied. len=%d\n", len); } else { // parent char buf[] = "hello world\n"; // close pipe in. close(fd[0]); // write message. write(fd[1], buf, sizeof(buf)); close(fd[1]); fprintf(stderr, "parent finishied\n"); } }
プログラムを実行すると
$ ex2 fd[0]=3, fd[1]=4 parent finishied len=13, buf=hello world child finishied. len=0
と出力され、
ます。*1
同様にdupシステムコールの仕様を簡単に書くと、
呼び出し形式 #include <unistd.h> int dup(int fildes); 機能 既存のファイル記述子を複製し、新しい生成されたファイル記述子を返す。 プロセスには、getdtablesize()で返される大きさのファイル記述子テーブルを持ち、 新しいファイル記述子には、未使用のもっとも小さい値が返される。 戻り値 正常終了の場合、0以上の値が返され、そうでない場合には-1を返します。
となります。
UNIXのファイル記述子の割り当てルールは、
ので、特定の値のファイル記述子を割り当てたい時には、
ことで実現できます。 リダイレクトやパイプは、この簡単なルールを使って実現されています。
dupの動作を確認するために、以下のサンプルプログラムを作成します。
// ex3.c : dup example #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUFSIZE (128) main() { int pid; int fd[2]; // create a pipe. if (pipe(fd) == -1) { fprintf(stderr, "can't create pipe\n"); exit(1); } if ((pid = fork()) == 0) { // child int in = -1; // close pipe out. close(fd[1]); // close stdin. close(0); in = dup(fd[0]); close(fd[0]); fprintf(stderr, "in=%d\n", in); fprintf(stderr, "child : exec wc\n"); execl("/usr/bin/wc", "wc", 0); } else { // parent int out = -1; // close pipe in. close(fd[0]); // close stdout. close(1); out = dup(fd[1]); close(fd[1]); fprintf(stderr, "out=%d\n", out); fprintf(stderr, "parent: exec echo 'hello world'\n"); execl("/bin/echo", "echo", "hello world", 0); } }
プログラムを実行すると、
$ ex3 out=1 parent: exec echo 'hello world' in=0 child : exec wc 1 2 12
と出力されます。
wcの出力結果は、
$ echo 'hello world' | wc 1 2 12
と同じであり、親プロセスと子プロセスの間でパイプが正常に結ばれたことが確認できました。
皆様のご意見、ご希望をお待ちしております。