Kshは16Kバイトを転送した後、データを失います。

Kshは16Kバイトを転送した後、データを失います。

最近、kshが数秒間ブロックされると、kshが16Kバイト以上を標準出力として印刷した後に一部のデータを失う可能性があることがわかりました。

このtest.shスクリプトは257 * 64(16448)バイトを印刷します。

#!/usr/bin/ksh
i=0
while [[ i -lt 257 ]]
do
    x=$(file /tmp)
    echo "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE"
    i=$((i+1))
done |
while read datafile
do
    echo $datafile
done

次のテストを実行しました。

0 $ ./test.sh | wc -c
   16448
0 $ ./test.sh | (sleep 3; wc -c)
   16384

この行はx=$(file /tmp)2番目のループには何も送信しませんが、この動作に影響を与えるようです。

bashを使用すると、期待どおりに動作します。

私にとって、これはkshのバグのようです。私はSolaris 5.10を使用しています。解決策や回避策はありますか?この問題の根本原因は何ですか?私はこれがパイプバッファサイズに関連していると思います。

ありがとう、ピーター

編集する:

したがって、run testを使用すると、truss最後の64バイトを書き込んだときにエラーが発生することがわかります。

ioctl(0, I_PEEK, 0x08046B40)                    = 0
    Received signal #18, SIGCLD, in write() [caught]
      siginfo: SIGCLD CLD_EXITED pid=6561 status=0x0000
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64)      Err#4 EINTR
lwp_sigmask(SIG_SETMASK, 0x00020000, 0x00000000) = 0xFFBFFEFF [0x0000FFFF]
setcontext(0x08046670)
read(0, 0x0809064C, 1)                          = 0
ioctl(0, TCGETA, 0x08046B18)                    Err#22 EINVAL

dtkshを使用して同じスクリプトを実行する方法は次のとおりです。 Stephaneが指摘したように、失敗した書き込みは再試行されます。

ioctl(0, I_PEEK, 0x08046694)                    = 1
read(0, " 0 1 2 3 4 5 6 7 8 9 A B".., 64)       = 64
Received signal #18, SIGCLD, in write() [caught]
  siginfo: SIGCLD CLD_EXITED pid=28276 status=0x0000
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64)      Err#4 EINTR
lwp_sigmask(SIG_SETMASK, 0x00020000, 0x00000000) = 0xFFBFFEFF [0x0000FFFF]
waitid(P_ALL, 0, 0x08046500, WEXITED|WTRAPPED|WSTOPPED|WNOHANG) = 0
waitid(P_ALL, 0, 0x08046500, WEXITED|WTRAPPED|WSTOPPED|WNOHANG) Err#10 ECHILD
sigaction(SIGCLD, 0x08046510, 0x08046580)       = 0
setcontext(0x08046430)
write(1, 0x080F0FD8, 64)        (sleeping...)
write(1, " 0 1 2 3 4 5 6 7 8 9 A B".., 64)      = 64
ioctl(0, I_PEEK, 0x08046694)                    = 0

ベストアンサー1

これはのバグのように見えますksh

私が疑うのは

x=$(file /tmp)

kshコマンドを実行し、パイプを介して出力を読み取る新しいプロセスを作成してfile終了するのを待ちません(kshの最新バージョンを含むすべての最新のシェルはこれを行います)。コマンドの読み込み中にEOFに達すると、そこから返されます。パイプ。

この動作は、以下を実行することで確認できます。

ksh -c 'x=$(exec sh -c "echo foo;exec >&-; sleep 10"); echo "$x"'

そして、ksh出力があることを確認してすぐに返すか、foo10秒後に返します。

これは、fileコマンドが終了し、SIGCLDが対応する親(シェル)に送信されることを意味します。後ろにコマンドがx=...返されました。

シェルの目的は、子プロセスの終了を要求する SIGCLD を処理することです。シェルにバックグラウンドで実行されているサブプロセスがある場合は、いつでも終了する準備ができている必要があります。 SIGCLD信号は、無視できない他の信号と同様にシステムコールをブロックします。中断。これを行うには、シェルは次のいずれかで準備する必要があります。ブロックする潜在的に中断されたシステムコールの実行中に信号をエクスポートするか、信号を処理した後に中断されたシステムコールを再試行します。

この場合、何も起こっていないようです。ほとんどの場合、write組み込み関数を実行するkshが実行するシステムコールはechoすぐに返されるため、中断する機会はありませんが、stdoutが指すパイプがいっぱいになるとシステムコールはブロックされますwrite。 SIGCLDによって中断されました。 ksh は再試行しません。それはエラーです。

Linuxでも以下を実行すると、同じ動作を見ることができます。

strace -e write ksh -c 'i=0; while [ "$i" -lt 2000 ]; do : &
  echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  i=$(($i+1)); done' | (sleep 3; wc)

それから私達は次を見ます:

write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61) = ? ERESTARTSYS (To be restarted)
--- SIGCHLD (Child exited) @ 0 (0) ---
write(1, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"..., 61...

同様に:、コマンドを終了するとブロックwriteシステムコールは中断されますが、今回はwrite再試行されます。

回避策には、組み込みコマンドを呼び出す前にコマンドのオーバーライドを回避したり、サブシェルでコマンドを実行するなど、SIGCLDを取得したプロセス以外のプロセスによって置き換えが行われるようにすることがecho含まれます。writeecho

(echo "012...")

編集する:出力を詳しく見てみると、truss2番目のループのトレースであることがわかります。これは、他のループを実行するプロセスとは別のプロセスで実行することを意図しているため、コマンドの終了時にSIGCLDを取得しないでくださいfile。ただし、最初のループを実行するサブシェルが終了すると、SIGCLDを取得できます。

また、テスト結果が示すように、kshが実際にコマンド置換のために生成されたプロセスを待っている場合、受信したSIGCLD信号はコマンドの非同期シャットダウンとして説明することはできませんfile

外部パイプがいっぱいになる可能性が高いように見えますが、2つのwhileループ間のパイプはそうではありません。 SIGCLDは、echo2番目のループのブロック期間中に受信され、1番目のループが終了したときに発生します。したがって、より効率的な解決策は、サブシェルechoで各コマンドを実行するのではなく、サブシェルで2番目のループを実行することです。

while ...; done | (while ...;done)

おすすめ記事