23710


次のような、名前付きパイプ(fifo) を利用するスクリプトを記述しました。

[code]

    #!/bin/sh
    
    set -ex
    
    rm -rf fifo
    mkfifo fifo
    
    cat <<EOF > fifo &
    hoge
    fuga
    moge
    EOF
    
    while read -r line < fifo
    do
        echo $line
    done
    
    echo "EOF!"
    
[/code]

これを、 Mac OS X で実行した時には、途中で処理が止まってしまいます。 (Ctrl+C で中止している)

[code]

    $ ./test-fifo.sh
    + rm -rf fifo
    + mkfifo fifo
    + read -r line
    + cat
    + echo hoge
    hoge
    + read -r line
    ^C
    
[/code]

一方、 ubuntu(14.04, dash0.5.7) 上で実行した場合には、意図通りにスクリプトは実行できています。

[code]

    $ ./test-fifo.sh 
    + rm -rf fifo
    + mkfifo fifo
    + read -r line
    + cat
    + echo hoge
    hoge
    + read -r line
    + echo fuga
    fuga
    + read -r line
    + echo moge
    moge
    + read -r line
    + echo EOF!
    EOF!
    
[/code]

質問:

  * どうして、このような OS 間の挙動の差異が発生するのでしょうか。

仮説(考えたこと)

  1. このスクリプトの挙動はそもそも undefined である。

    * ただ、だとするとその旨の記述はどこかに仕様として記述があるのでは、と考えていて、それが見つけられないと考えている状態です。
  2. OS 間で fifo の挙動が、仕様として差異がある。

    * この場合も同様に、その仕様がどこかにまとまっていると考えていますが、それをどうやったら探せるのかわからないと思っている状態です。

* * *

追記@ 2016/04/03

手元の ubuntu 環境(14.04) だと、 bash でも dash でも、同じようによろしく動いてしまっています。。

bash

[code]

    $ bash test-fifo.sh 
    + rm -rf fifo
    + mkfifo fifo
    + read -r line
    + cat
    + echo hoge
    hoge
    + read -r line
    + echo fuga
    fuga
    + read -r line
    + echo moge
    moge
    + read -r line
    + echo 'EOF!'
    EOF!
    
[/code]

dash

[code]

    $ dash test-fifo.sh 
    + rm -rf fifo
    + mkfifo fifo
    + read -r line
    + cat
    + echo hoge
    hoge
    + read -r line
    + echo fuga
    fuga
    + read -r line
    + echo moge
    moge
    + read -r line
    + echo EOF!
    EOF!
    
[/code]

* * *

自分の手元の、よろしく動いてしまう ubuntu で、 dash/bash の `strace` を見る限りは、次のような動作をしていました。

  1. `hoge\nfuga\nmoge` を cat するプロセスが、この文字列を fifo に write する。 write システムコールが発行されて、このプロセスの実行がしばらく止まる。
  2. シェルが１文字ずつ read する。 `hoge\n` になったら fifo を close する。
  3. シェルが echo 処理を行う
  4. シェルが再び fifo をオープンして、 手順の 2 に戻る
  5. 2-4 を繰り返して、`hoge\nfuga\nmoge\n`がなくなると、 cat プロセスに実行が戻る。 cat プロセスが正常終了する。
  6. read で何も読み込めなかったので、 while のループを抜ける。その後正常終了する。

プロセスの実行タイミングと、 fifo の fd の open/close のタイミングの問題な気がしてきています。


CC BY-SA 3.0
2016-04-02T12:11:22.760
23686
2016-04-03T17:48:54.163
2016-04-03T07:44:44.720
754
754
question
sh unix
fifo から read するスクリプトの挙動が、 OS 間で異なっている理由がわからない


以下は `dash` と `bash` の `strace` の結果なので回答ではないのですが、コメント欄には収まらないので回答欄を使います。

環境は `Ubuntu 16.04, Linux kernel 4.4.0, dash 0.5.8, bash 4.3.42` です。

**dash**

[code]

    $ strace -f ./test-fifo.sh 2>&1 >/dev/null
    
[/code]

ここで、`pid 8095` が `cat` プロセス、`8092` が `dash` プロセスです。

# 適当に端折っています

[code]

    getpid()              = 8092
    [pid  8095] execve("/bin/cat", ["cat"], [/* 65 vars */]) = 0
    
    # Write to FIFO
    [pid  8095] read(0, "hoge\nfuga\nmoge\n", 131072) = 15
    [pid  8095] write(1, "hoge\nfuga\nmoge\n", 15 <unfinished ...>
    
    # Read from FIFO
    [pid  8092] open("fifo", O_RDONLY|O_LARGEFILE <unfinished ...>
    [pid  8092] <... read resumed> "h", 1) = 1
    [pid  8092] <... read resumed> "o", 1) = 1
    [pid  8092] <... read resumed> "g", 1) = 1
    [pid  8092] <... read resumed> "e", 1) = 1
    [pid  8092] read(0, "\n", 1) = 1
    
    # Next read
    [pid  8092] write(1, "hoge\n", 5) = 5
    [pid  8092] open("fifo", O_RDONLY|O_LARGEFILE <unfinished ...>
    [pid  8095] +++ exited with 0 +++
    --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=8095, si_uid=1000, ...
    write(2, "cannot open fifo: Interrupted sy"..., 41cannot open fifo: Interrupted system call) = 41
    
[/code]

**bash**

`pid 8495` が `cat` プロセス、`8492` が `bash` プロセスです。1回目の read の直後からになります。

[code]

    [pid  8492] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    [pid  8495] +++ exited with 0 +++
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=8495, si_uid=1000, ...
    waitpid(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG) = 8495
    waitpid(-1, 0xbfe8f374, WNOHANG)        = -1 ECHILD (No child processes)
    sigreturn({mask=[]})                    = 0
    write(1, "hoge\n", 5)                   = 5
    open("fifo", O_RDONLY|O_LARGEFILE)      = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
    
[/code]


CC BY-SA 3.0
2016-04-03T08:50:53.723
23697
2016-04-03T08:50:53.723
23686
answer


仕様が欲しいとこの質問を開始しましたが、調べていくうちにそんなものはないのでは、と思うようになったので、それをまとめておきます。

観測されている(話し合われている)挙動は、３通りあって、並列実行されるプロセスの、タイミングの問題であるのではないか、と思っています。

  1. fifo が cat と bash のプロセスで開かれる
  2. cat が fifo に write する 
    * この直後に cat 側で close された場合には、1回目の read の後に close されて、 紐づく fd がなくなるので fifo が初期化される -> ２回目の read が block される (自分の mac ・ argus さんの dash で観測した事象)
  3. bash: read 1 回目: `hoge\n` を読み込む
  4. bash 側で fifo が close される 
    * この直後に cat 側で close された場合には、2回目 fifo open のシステムコールの最初にソフト割り込みの判定が行われてるっぽくて、そこで `Interrupted system call` (argus さんの dash で観測された事象)
  5. cat 側の close が最後まで行われなければ、問題なくこのスクリプトは動作する (自分の ubuntu で観測した事象)

* * *

この仮説があっているとすると、自分の当初の質問に対する答えは、 1. このスクリプトの動作は 非決定的,
理由は、プロセスの並列実行のタイミングの問題のため。


CC BY-SA 3.0
2016-04-03T17:41:50.860
23710
2016-04-03T17:48:54.163
2016-04-03T17:48:54.163
754
754
23686
answer