【備忘録】名前付きパイプをopen()するとブロックする件

mkfifo() で生成した名前付きパイプをopen()するときにブロックすることがある話です..

マニュアルの片隅に書いてあって、あまり知られてないことかと…

ちょっとこれでハマったので備忘録として残しておきます.

mkfifo

mkfifo()は以下のような形式で、pathnameで指定された名前付きパイプ(FIFOスペシャルファイル)を生成します.

名前付きパイプは通常のファイル同様、どのようなプロセスからもオープン可能、読み込み、書き込みが可能です.

名前付きパイプは以下の様に生成できます.

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

int main()
{
  int st = mkfifo("/tmp/pipe", 0666);
  if (st < 0)
    {
      perror ("mkfifo");
      return -1;
    }

  return 0;
}

生成したパイプは通常のファイルのように見えます.

やりたいこと

スレッド間 (あるいはプロセス間) 名前付きパイプで通信したいと考えました.

スレッド A からスレッドBにメッセージを名前付きパイプ経由で伝えたい

図で書くとこんな感じ

  • スレッドAは名前付きパイプに対して書き込みのみ.
  • スレッドBは名前付きパイプに対して読み込みのみ.

という制御となります.

パイプのオープン

スレッドBで名前付きパイプをオープンする際、読み込みだけなので Read Only 指定でオープンしました.

  int fd = open ("/tmp/pipe", O_RDONLY);
  if (fd < 0)
    {
      perror ("open");
      return -1;
    }

そうしたら、

open ()でブロックするではないですか!!

メッセージがやってくるまで read() でブロックするのは分かりますが、open()でブロックするとは…. なぜだ?

相手がオープンするまでブロックする

man open(2) を読んでみました.

Opening the read or write end of a FIFO blocks until the other end is also opened (by another process or thread). See fifo(7) for further details. 

名前付きパイプ(FIFO)をRead/Wirteでopenする場合, 相手がopenするまでブロックされる.
と書かれてました. なるほど.

詳しくは fifo (7) を見ろ、とあります.

カーネルは、少なくとも一つのプロセスによってオープンされている FIFO 特殊ファイルに関して、 1 つの FIFO 特殊ファイルにつき 1 つのパイプオブジェクトを管理する。データを渡す前に、 FIFO の両端 (書き込み側と読み出し側) がオープンされていなければならない。通常、 FIFO をオープンしようとした場合、その反対側がオープンされるまで停止 (block) される。

とありました. まず fifo の man を見るべきでした.

オープンでブロックするのを回避するには

man fifo (7) を見るとオープン時にブロックするのを回避する方法が書いてました.

読み書きモードでオープンする

読み書きモード  O_RDWR でオープンするとブロックを回避できます.

ただし以下の点を注意. fifo(7) より

Linux では、 FIFO を読み書き両用でオープンした場合、 停止、非停止のどちらのモードでも成功する。 POSIX では、この場合の動作は定義されていない。この動作は、読み込み側がいない時に書き込み用に FIFO をオープンするために使用できる。自分自身と通信するために FIFO の両端を使用するプロセスでは、デッドロックを避けるために細心の注意を払う必要がある。

ノン・ブロックモードでオープンする

文字通り、ノンブロックモード O_NONBLOCK でオープンすると読み込み専用 O_RDONLY, 書き込み専用 O_WRONLY を選択してもブロックすることはありません.

以下の点を注意ください. fifo(7) からの引用.

プロセスは FIFO を非停止 (nonblocking) モードでオープンすることもできる。この場合、読み込み専用でオープンしようとした場合には、書き込み側を誰もオープンしていなくても成功する。書き込み専用でオープンしようとした場合には、反対側がすでにオープンされていない限り、 ENXIO (そのようなデバイスまたはアドレスは存在しない) というエラーで失敗する。

当然のことながら, O_NONBLOCK でオープンした場合, データが来るのを read()が待たないので、その点を考慮に入れたプログラミングが必要です.

今回は man コマンド中心に調べましたが, 書籍であればこれが良いかなぁ、と思います.

それでは.

“【備忘録】名前付きパイプをopen()するとブロックする件” への1件の返信

コメントは受け付けていません。