2015-12-26 21 views
6

なぜシグナルを受け取ってそのシグナルハンドラから非同期非安全関数を呼び出すのが安全でないのか、ちょっと混乱します。誰かがこれの背後にある推論を説明してくれるかもしれませんし、おそらくもっと自分自身でこれを読むために私が従うことのできる参考文献をいくつか教えてもらえますか?シグナルハンドラから非同期シグナルセーフな関数しか安全に呼び出せないのはなぜですか?

つまり、なぜシグナルハンドラ内からprintfを呼び出すのが安全でないかと尋ねています。これはプロセス内の問題と、保護なしでprintfを呼び出す可能性が2つあることから生じる可能性のある競合状態、または同じリソース(この例ではstdout)へのプロセス間競合によるものです。プロセスA内のスレッドがprintfを呼び出していて、別のスレッドがその信号を受け取り、printfを呼び出すとします。カーネルが2つの呼び出しを区別することができないため、ここでカーネルは何をすべきか分からないからです。

+1

短い形式は、「標準がそう言っているから」または「あなたのライブラリがそう言っているから」という形式です。長い形式は、コードが書かれていないことです非同期アクセス機能:非同期処理のほとんどの方法は、同期実行時にコストがかかるため、非同期処理関数を使用するとパフォーマンスがよく感じられます。 – Yakk

+0

ありがとう私は非同期非安全関数をパラレルに呼び出すのではなく、返されないシグナルハンドラのコンテキスト内で呼び出すような方法でコードを構造化するといいですか? – Curious

+0

'malloc'固有のバージョン:https://stackoverflow.com/questions/3366307/why-is-malloc-not-async-signal-safe –

答えて

6

プロセスA内のスレッドがprintfを呼び出しており、別のスレッド がシグナルを受信した後、printfを呼び出したとします。これはおそらく カーネルが何をするか分からないからです。 2つの呼び出しを区別することができないからです。

問題が発生するのはカーネルではありません。それはあなた自身のアプリケーションです。 printfはカーネル関数ではありません。これは、アプリケーションが使用するCライブラリの関数です。 printfは、実際にはかなり複雑な機能です。さまざまな出力フォーマットをサポートしています。

この形式の最終結果は、標準出力に書き出される書式付き出力文字列です。そのプロセス自体にもいくつかの作業が必要です。フォーマットされた出力文字列は、内部のstdoutファイルハンドルの出力バッファに書き込まれます。特定の定義された条件が発生したとき、すなわち出力バッファがいっぱいであるとき、および/または改行文字が書き込まれるときにはいつでも、出力バッファはフラッシュされます(カーネルは引き継いで、定義されたデータのまとまりをファイルに書き込みます)。出力ストリーム。

これらはすべて出力バッファの内部データ構造でサポートされています。これは、Cライブラリのジョブなので、気にする必要はありません。今、信号はprintfが動作している間はどの時点でも到達することができます。そして、いつでも、私は意味します。 printfが出力バッファの内部データ構造の更新途中で非常にうまく到着する可能性があり、printfがまだ更新を完了していないため一時的に不整合な状態になっています。

例:現代のC/C++実装では、printfはシグナルセーフではないかもしれませんが、スレッドセーフです。複数のスレッドはprintfを使用して標準出力に書き込むことができます。最終的な出力が実際に意味をなさないことを確かめるために、このプロセスを自分自身で調整するのはスレッドの責任です。複数のスレッドの出力からランダムに混乱することはありませんが、それはその点です。

重要な点は、printfはスレッドセーフであり、通常はどこかにプロセスに含まれるmutexがあることを意味します。したがって、発生する可能性のあるイベントのシーケンスは次のとおりです。

  • printfは内部ミューテックスを取得します。

  • printfは、文字列を書式設定してstdoutの出力バッファに書き込む作業を続けます。

  • printfが行われる前に、取得されたミューテックスを解放することができ、信号が到着する。

ここで、mutexはロックされています。シグナルハンドラについては、プロセス内のどのスレッドがシグナルを処理するのかは一般的には指定されていません。与えられた実装はランダムにスレッドを選ぶかもしれませんし、常に現在実行中のスレッドを選ぶかもしれません。いずれにしても、信号を処理するためにここにprintfをロックしたスレッドを確実に選択することができます。

これでシグナルハンドラが実行され、printfを呼び出すことになりました。 printfの内部ミューテックスがロックされているため、スレッドはミューテックスがロック解除されるのを待たなければなりません。

お待ちください。

お待ちください。

あなたが物事を追跡していたならば、シグナルを処理するために中断されたスレッドによってミューテックスがロックされるからです。スレッドが実行を再開するまで、mutexはロック解除されません。しかし、シグナルハンドラが終了してスレッドが実行を再開するまでは起こりませんが、シグナルハンドラはmutexがロック解除されるのを待っています。

あなたは骨が折れています。

printfは、この問題を回避するために、std::recursive_mutexのC++に相当するものを使用することがありますが、シグナルによって導入される可能性のあるデッドロックをすべて解決するわけではありません。

要約すると、「信号を受信して​​その信号ハンドラ内から非同期セーフ機能を呼び出すことが安全でない」理由は、それが定義上ではないためです。シグナルが非同期イベントであり、非同期セーフな関数ではないので、定義上、それはできません。水は水であるため濡れていますが、

+1

ありがとう!それは理にかなっている。私はそれを回避することができます長い間、私はそれを回避することができると思います – Curious

+0

再帰的なミューテックスは意味で悪化しています:あなたはデータ構造を変更している、そして今は再びそれを同時に変更しています。 – Yakk

関連する問題