Linux で、write(2) や close(2) を呼んだらエラー EINTR が発生した場合、ユーザプログラム側ではどう対処するのが正解ですか?


NFS のような「遅い」ファイルシステムをマウントしている場合、設定にもよりますが、ファイル I/O 系のシステムコールで EINTR が発生することがありますよね。
ネットで検索すると「EINTR が起きたら同じシステムコールをもう一度呼べ」と書いてあるページがいくつか見つかったのですが、write(2) を 2 回呼び出すと同じ内容を重複して書き込んでしまいそうです。
また、下記ページの投稿を見ると、close(2) を 2 回呼び出すと EBADF が起きる可能性があるとか、ファイルディスクリプタがすでに他のスレッドで再利用されている場合があるから危険だと書いてあって、どうすればいいのかわかりません……。

When the programmer is forced to handle return codes
http://lwn.net/Articles/365294/

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2011/10/31 23:37:53
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:monyot No.1

回答回数146ベストアンサー獲得回数18

ポイント300pt

write(2) については、たとえば WRITE

何のデータも書かない間にシグナルにより割り込まれた (interrupt)。

Linux の write(2) の原文では

The call was interrupted by a signal before any data was written;

と書かれているとおりですので、同じ内容が何度も書かれてしまう懸念は仕様上はないはずです。

わたしも EINTR の場合は、基本的にリトライするようにコーディングしてました。


close(2) の EINTR 問題は、いままで認識したことがなく、興味深い(というか悩ましい)ですね。

Invoke close() again if it fails with EINTR? Think again. や、そこからたどれる Re:uml: retry host close() on EINTR でのリーナス自身のコメントをみていると、EINTR が出ても無視するしかないように見えます。

ただ、

I'm going to drop this patch, but in case you've ever seen a case where

EINTR actually means that the fd didn't get closed, please holler, and we

need to fix it.

と書いているので、問題は認識しているようですね。

id:sardine

write(2) の方はリトライで OK、close(2) はリトライすべきではないが……ということですね。

教えて頂いたリンク先も大変参考になりました。迅速かつ素晴らしい回答、ありがとうございます!

2011/10/25 01:04:02
id:sardine

LKML の回答をたどってみたところ、「慎重にやりたい場合は fsync(2)」的なことが書いてあったので、まずは fsync(2) を呼ぶようにしてみます。

回答・コメントを頂いた皆さん、ありがとうございました。

2011/10/31 23:40:09
  • id:longicorn
    意味はとりあえず、signal(7)をみれば良いのですが基本的には
    -シグナルハンドラによるシステムコールやライブラリ関数への割り込み
    -「遅い」デバイスに対する呼び出し
    -Linux特有の動作の場合
    という感じでしょうか。


    で、「遅い」デバイスというのは大抵はパイプかソケットの可能性が高いです。あとは特定のハードウェアに依存するデバイス(これははドライバ次第)あたり。
    特定のデバイスは仕方ないとして、パイプ/ソケットですね。NFSもそうです。


    Unixネットワーキングプログラミングから一部分引用します。
    >>
    プロセスが遅いシステムコールでブロックしており、かつプロセスによるシグナルの捕捉が起き、かつシグナルハンドラから制御が戻った際に、そのシステムコールがEINTRエラーをかえす可能性がある
    <<
    とあります。


    牽引を見るとEINTRの項目には、ページが17件ありました。
    中途半端な回答になって申し訳ないのですが、fdの実体が何か?でかなり対応が異なるはずです。

    kernel(2.6.27)から一部抜粋しましょう。
    >>
    //driver/net以下に良く見られるコード
    if (signal_pending(current))
    return -EINTR;

    //fs/jffs2/gc.cのコード
    if (mutex_lock_interruptible(&c->alloc_sem))
    return -EINTR;

    //net/netfilter/nf_sockopt.c
    if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
    return -EINTR;
    <<
    まあ、どれもろくな状況ではありません。


    単純な解決方法は一旦close(2)してからopen(2)しなおしてデータの送受信をやり直すこと、中途半端なデータは捨てること、でしょうか。


    ただ、かなり奥深い問題ですので「Unixネットワーキングプログラミング」等のかなり詳しい書籍の購入をおすすめします。
    個人的な意見だといまでもネットワークプログラミングだとこの本が一番詳しいと思います。
    ISBN:4894712059:detail


    ただ、上記の本ですべてカバーできるわけではありません。
    最悪はカーネルを見るしかないかと。
  • id:a-kuma3
    何故、回答に書かない? >longicorn さん

    システムコールで隠蔽されてるとしても、その先の実装次第で対応の仕方が変わってくる、
    というのは、十分回答に値すると思うのだけれど。

    シグナルのハンドラでできることは限られているし、ましてや、割り込まれた処理の継続性については、
    規格は何も保証してくれないもんね。
  • id:sardine
    >id:longicorn さん
    書籍まであたって頂いてありがとうございます。参考になりました!
    ここまで調べて頂いてお礼(と言ってもわずかですが)ができないのは
    心苦しいので、よかったら回答欄にひとこと書いて頂けると嬉しいです。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません