ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

名前付きパイプの使い方の小ネタ

まだまだ消費増税に対抗するため、無理やりネタを紡ぎ出す。

named pipe、「名前付きパイプ」の簡単な使い方を残しておく。

プロセス間通信

プロセスの間でなにかしらのデータのやり取りをすることをプロセス間通信(Inter Process Communication)と呼ぶ。たとえばLinuxでは以下のような方法がある。

この分類は「Linuxプログラミングインターフェース」を参考にした。

www.oreilly.co.jp

IPCで最も手軽なのはパイプの作成で、fork元でpipe()などを用いて読み出し専用/書き込み専用の1組のパイプを作り、fork先でもそのfile descriptorを継承して使う。お互いにread/writeという基本的なシステムコールのみでデータをやり取りできるメリットがある。

mruby(CRubyも同様)の例を昔書いた。

udzura.hatenablog.jp

一方でパイプは、fdを共有できるような、例えば親子関係があるプロセス同士でないと利用できない。そこでファイルシステム上に作成できる名前付きパイプを使う。

基本的な使い方

MacまたはLinux前提の話とする(他のUNIXでも大丈夫だと思う)。

mkfifo というコマンドで名前付きパイプを作成できる。

$ mkfifo /tmp/foo.fifo
$ file /tmp/foo.fifo 
/tmp/foo.fifo: fifo (named pipe)

このファイルに書き込みと、ブロックをする。

$ echo hello pipe > /tmp/foo.fifo

別のターミナルでこのファイルを cat して読み出す。

$ cat /tmp/foo.fifo                                       
hello pipe

すると、元のターミナルのブロックも解除されている。

名前付きパイプの基本的な使い方として:

  • 書き込むと、どこかで読み出されない限り書き込みが完了しない(ブロックする)
  • 読み出そうとすると、読み出し可能な状態になるまでブロックする

ブロックする性質を利用して、あるプロセスが何かしらの処理が完了するのを別のプロセスから待つことができる。簡単には、何かしらの処理が完了したらパイプに何かを書き込み、それを事前に別のプロセスでreadしておけば待ち受けることができる。

ノンブロッキングの場合はまた扱いが変わる。次はRubyスクリプトでやってみる。

ノンブロッキングモードでの書き込みの場合、パイプが書き込み可能になる(片方で誰かが読み込んでいる)状態でないと即座にエラーになる。

f1 = File.open("/tmp/fifo.2.fifo", File::WRONLY | File::NONBLOCK) 
Traceback (most recent call last):
        4: from /Users/udzura/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
        3: from (irb):15
        2: from (irb):15:in `open'
        1: from (irb):15:in `initialize'
Errno::ENXIO (Device not configured @ rb_sysopen - /tmp/fifo.2.fifo)

## もう片方で cat /tmp/fifo.2.fifo
f1 = File.open("/tmp/fifo.2.fifo", File::WRONLY | File::NONBLOCK)                                                                                       
#=> #<File:/tmp/fifo.2.fifo>
f1.write_nonblock("Test\n")                                                                                                                             
#=> 5
# cat したプロセスで即座に読み込める
f1.write("Test\n")
f1.write("Test\n")
f1.flush
# あるいは、このタイミングで書き込んで、もう一方で読み込める

ノンブロッキングモードでの読み出しは、ブロックはせず、fdを作成する。そこから読み出そうとすると、読み出し可能になるまで即座にエラーになる。

f2 = File.open("/tmp/fifo.2.fifo", File::RDONLY | File::NONBLOCK)
f2.read_nonblock(2048)                          
Traceback (most recent call last):     
        4: from /Users/udzura/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
        3: from (irb):2                                                        
        2: from <internal:prelude>:74:in `read_nonblock'
        1: from <internal:prelude>:74:in `__read_nonblock'
EOFError (end of file reached)
## もう片方で echo test > /tmp/fifo.2.fifo
f2.read_nonblock(2048)
#=> "test\n"

f1 = File.open("/tmp/fifo.2.fifo", File::WRONLY | File::NONBLOCK)
=> #<File:/tmp/fifo.2.fifo>
f2.read_nonblock(2048)
Traceback (most recent call last):
        4: from /Users/udzura/.rbenv/versions/2.5.5/bin/irb:11:in `<main>'
        3: from (irb):9
        2: from <internal:prelude>:74:in `read_nonblock'
        1: from <internal:prelude>:74:in `__read_nonblock'
# パイプが「つながっている」状態の時はエラーが変わる
IO::EAGAINWaitReadable (Resource temporarily unavailable - read would block)

コンテナの間で使う

コンテナの間でボリュームを共有すれば、(実態はただの bind mount であろうので)同じホストであれば名前付きパイプを通して通信ができる。

$ docker run -v/tmp/fifos:/tmp/fifos -ti ruby:2.6 bash
root@25860f875ba7:/# irb
irb(main):001:0> loop do
irb(main):002:1*   data = File.readlines("/tmp/fifos/myfifo.fifo")                                                                                                       
irb(main):003:1>   unless data.empty?
irb(main):004:2>     data.each {|ln| puts "Got=> #{ln}" }
irb(main):005:2>   end
irb(main):006:1> end

# --- もう一方のコンテナ
$ docker run -v/tmp/fifos:/tmp/fifos -ti ruby:2.6 bash
root@ffb25abec6e0:/# cat >  /tmp/fifos/myfifo.fifo
Foo
Bar
Buz!!

# --- 元のコンテナで
Got=> Foo
Got=> Bar
Got=> Buz!!

例えば Kubernetes のばあい同じ Pod のコンテナは同じホストにできるそうなので、高速なコンテナ間通信のいち手段に使えるかもしれない。

それを利用した pikebubbles というコンテナツールを作っていたりする。

github.com


特にオチもなく終わる。皆さんもたまには名前付きパイプのことを思い出しましょう。

急いで書いたので細部でおかしなところがあれば是非ご指摘を...。