リダイレクトとパイプ連結
リダイレクト
リダイレクトは出力をファイルにする機能で、出力先のファイルディスクリプター0を、通常ファイルのファイルディスクリプターに書き換えることで実現できます。以下のサンプルはps > testをシュミレートしています。#include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> void ps(); int fd; int main(void) { fd = open("test", O_WRONLY|O_CREAT); if(fork() == 0) ps(); wait(); } void ps() { close(1); dup(fd); execlp("ps","ps", NULL); }まずtestファイルを書き込みモードで作成し、forkでプロセスを作成したのち、このファイルディスクリプターを標準出力であるファイルディスクリプター1に差し替えて、子プロセスをexeclpでpsコマンドに置き換えています。従って、起動されたpsコマンドの標準出力はtestに書き込む形で動作することになります。
forkは親のプロセスディスクリプターを複写して子プロセスのディスクリプターを作成します。またCopyOnWriteにより、参照においてメモリを共有することになります。execlpは新規の仮想メモリー空間にpsをロードしますが、ファイルディスクリプターを初期化することはありません。従って子プロセスのps関数のfdは、親プロセスのopenのfdであり、そのinodeも同じものとなります。
リダイレクトとは子プロセスの標準出力ディスクリプター1に、リダイレクト先ファイルのファイルディスクリプターを差し替えることで実現できます。
p/s
dupは0から空いているディスクリプターを取得します。close(1)で1をクローズすることで、dup(fd)はディスクリプター1に差し替えられます。
パイプ連結
下記はps | cat をシュミレートするサンプルです。考え方はリダイレクトと同じです。リダイレクトでは実ファイルのファイルディスクリプターを取得していましたが、パイプではパイプのファイルディスクリプター読み込み用と書き込み用2つを取得します。ここで2つディスクリプター取得していますが、パイプそのものは1つだけで、読み込み用と書き込み用別々にファイルディスクリプターを取得しているだけです。そして、psコマンドの子プロセスと、catコマンドの子プロセスを作成し、psコマンドには書き込み用のファイルディスクリプターを標準出力に差し替えて、catコマンドには読み込み用のファイルディスクリプターを標準入力に差し替えることで実現できます。#include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> void ps(); void cat(); int pipes[2]; int main(void) { pipe(pipes); if(fork() == 0) ps(); if (fork() == 0) cat(); wait(); } void cat() { close(0); dup(pipes[0]); execlp("cat","cat", NULL); } void ps() { close(1); dup(pipes[1]); execlp("ps","ps", NULL); }リダイレクトとパイプに連結は、シェルによるもので、カーネルそのもの機能ではありません。そして、パイプも実ファイルもinodeとして管理されていることを考えれば、パイプもリダイレクトも、ファイルディスクリプター差し替えることで実現できるわけで、カーネルから見れば両者は同じような物と言えそうです。