Linuxシステムコールの勉強(その13)

Linuxシステムコール

Linuxシステムコール

前回はこちら


今回は親子に限定したプロセス間通信についてまとめます。まずは実際に親子間でパイプ経由でデータをやり取りするテストプログラムを作成します。

      1 #include <unistd.h>
      2 #include <stdio.h>
      3 #include <sys/types.h>
      4
      5 int main()
      6 {
      7     char buff[BUFSIZ];
      8     int  ppp[2];
      9       /*********************************************************************
     10        * ppp - 親プロセスへデータを受け渡すpipe用ファイルディスクリプタ配列
     11        *
     12        *   ppp[0]  file discriptor for reading
     13        *   ppp[1]  file discriptor for writing
     14        *********************************************************************/
     15
     16     int  ppc[2];
     17       /*********************************************************************
     18        * ppc - 子プロセスへデータを受け渡すpipe用ファイルディスクリプタ配列
     19        *
     20        *   ppc[0]  file discriptor for reading
     21        *   ppc[1]  file discriptor for writing
     22        *********************************************************************/
     23
     24     pid_t pid;
     25
     26     char *msg_p_to_c = "parent to child";
     27     char *msg_c_to_p = "child to parent";
     28
     29     /* pipeを開く */
     30     if (pipe(ppp) == -1 || pipe(ppc) == -1)
     31     {
     32         perror("pipe()");
     33         return 1;
     34     }
     35
     36     /* 子プロセス作成 */
     37     if ((pid = fork()) == -1)
     38     {
     39         perror("fork()");
     40         return 2;
     41     }
     42
     43     /* 親プロセスはここから */
     44     else if ( pid > 0 )
     45     {
     46         // 親プロセス→子プロセス パイプへのデータ書き込み //
     47         if (write(ppc[1], msg_p_to_c, strlen(msg_p_to_c) + 1) == -1 )
     48         {
     49             perror("write() from parent to child");
     50             close(ppc[1]);
     51             close(ppp[0]);
     52             return 1001;
     53         }
     54
     55         // 子プロセス→親プロセス パイプからデータ読み込み //
     56         if (read(ppp[0], buff, BUFSIZ) == -1 )
     57         {
     58             perror("read() from pipe");
     59             close(ppc[1]);
     60             close(ppp[0]);
     61             return 1002;
     62         }
     63         printf("i am parent process : [receive message] %s\n", buff);
     64         close(ppc[1]);
     65         close(ppp[0]);
     66     }
     67
     68     /* 子プロセスはここから */
     69     else
     70     {
     71         // 子プロセス→親プロセス パイプへのデータ書き込み //
     72         if (write(ppp[1], msg_c_to_p, strlen(msg_c_to_p) + 1) == -1 )
     73         {
     74             perror("write() from child to parent");
     75             close(ppc[0]);
     76             close(ppp[1]);
     77             return 2001;
     78         }
     79
     80         // 親プロセス→子プロセス パイプからデータ読み込み //
     81         if (read(ppc[0], buff, BUFSIZ) == -1 )
     82         {
     83             perror("read() from pipe");
     84             close(ppc[0]);
     85             close(ppp[1]);
     86             return 2002;
     87         }
     88         printf("i am child process : [receive message] %s\n", buff);
     89         close(ppc[0]);
     90         close(ppp[1]);
     91     }
     92
     93     return 0;
     94 }


2点、本が誤っている箇所があるので追記。

(1) 親と子で処理が分かれたところで使用しないパイプを閉じていましたが閉じちゃダメ
  → ファイルディスクリプタは共有なので一方を閉じると使えなくなる

(2) sys/types.hもincludeする
  → pid_tが定義されている

で、これを実行してみます

[itotto@itotto ]$ gcc -o fam_pipe fam_pipe.c

[itotto@itotto ]$ ./fam_pipe
i am parent process : [receive message] child to parent
i am child process : [receive message] parent to child

[itotto@itotto ]$


互いに送信したメッセージを受信している事が分かります。パイプを2つ開いて互いに書き込んで互いに読み込んでいるわけですから、当然です。


さて。ここまでは大丈夫なのですが、例えばfork()で作成した子プロセスがexec()した場合を考えて見ます。単にfork()しただけであれば、fork()前に開いておいたpipeのファイルディスクリプタが利用できますが、一旦exec()してしまうと全くの別プロセスになってしまうのでこれを利用する事が出来なくなってしまいます。


この本では

    1. exec()するプログラムに引数でディスクリプタを渡す方法
    2. パイプのディスクリプタを標準入出力にリダイレクトする方法


を紹介しています。前者の「引数でディスクリプタを渡す方法」は全然面白くないので、ここでは後者を説明します。

      1 #include <unistd.h>
      2 #include <stdio.h>
      3 #include <sys/types.h>
      4
      5 int main()
      6 {
      7     int pipe_fd[2];
      8     pid_t pid ;
      9
     10     /* pipeを開く */
     11     if (pipe(pipe_fd) == -1)
     12     {
     13         perror("pipe");
     14         return 1;
     15     }
     16
     17     /* 自プロセスを子プロセスに複写 */
     18     if ((pid = fork()) == -1)
     19     {
     20         perror("fork()");
     21         return 2;
     22     }
     23
     24     /* 以下親プロセス専用の処理 */
     25     else if ( pid > 0 )
     26     {
     27         // 標準入力を閉じる //
     28         close(fileno(stdin));
     29
     30         // 標準入力にパイプをリダイレクト //
     31         dup(pipe_fd[0]);
     32         close(pipe_fd[0]);
     33
     34         close(pipe_fd[1]);
     35
     36         if (execlp("less", "less", "-r", NULL) == -1)
     37         {
     38             perror("execlp()");
     39             return 1001;
     40         }
     41
     42     }
     43     /* 親プロセス専用ここまで */
     44
     45     /* 以下子プロセスのターン */
     46     else
     47     {
     48         // 標準出力を閉じる //
     49         close(fileno(stdout));
     50
     51         // 標準出力にパイプをリダイレクト //
     52         dup(pipe_fd[1]);
     53         close(pipe_fd[1]);
     54
     55         close(pipe_fd[0]);
     56
     57         if (execlp("ls", "ls", "--color", "-C", NULL) == -1)
     58         {
     59             perror("execlp()");
     60             return 2001;
     61         }
     62
     63     }
     64     /* 子プロセス専用ここまで */
     65
     66     return 0;
     67 }

fork()で親子それぞれの処理に分かれた後に、(1) 親プロセス側でパイプ読取用のファイルディスクリプタを標準入力へリダイレクトし、(2) 子プロセス側でパイプ書込用のファイルディスクリプタを標準出力へリダイレクトさせてます。
こうする事で書き込む方は標準出力に出力し、読み取る方は標準入力を読み取ることでファイルディスクリプタが不明のままでもプロセス間の通信をすることが可能となります。

[itotto@itotto ]$ gcc -o pipe_via_stdio pipe_via_stdio.c
[itotto@itotto ]$ ./pipe_via_stdio

executable  pipe_via_stdio  pipe_via_stdio.c  source

[itotto@itotto ]$


というわけで匿名パイプはここまで。想像してたのよりも簡単で分かりやすい印象です。

次へ進む