5

シグナルハンドラーで非同期セーフでない関数を呼び出せるかどうかを調べています。
Linux の man ページからの引用 signal(7):

シグナルが安全でない関数の実行を中断し、ハンドラが安全でない関数を呼び出す場合、プログラムの動作はundefinedです。

TLPI :

SUSv3 は、表 21-1 (非同期セーフ関数のリスト) にリストされていないすべての関数は、シグナルに関して安全でないと見なされることを指摘していますが、関数が安全でないのは、シグナル ハンドラの呼び出しが関数の実行を中断する場合のみであると指摘しています。安全でない関数、およびハンドラー自体も安全でない関数を呼び出します

上記の引用の私の解釈は、シグナルハンドラーが非同期セーフでない関数を中断しなかった場合にのみ、シグナルハンドラーから非同期セーフでない関数を呼び出すことが安全であるということです。

たとえば、安全でない関数を呼び出す SIGINT のハンドラーをインストールしますcrypt(3)。これは、再入不可、つまり安全ではないと思われます。

sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = handler;
sigaction(SIGINT, &sa, NULL);

またprintf()、 で無限ループを呼び出しmain()、メイン スレッドのみを実行しています。

printf()問題は、この単純な例に対するものです。ハンドラーが実行を中断し、安全でない関数を呼び出したときに、悪いことが起こることはありません。AFAK はprintf()、コンソール ロックを取得し、バッファリングされた I/O を実行するための内部バッファを持っていますが、この例ではその状態は一貫しています。静的に割り当てられた文字列を返しますがcrypt()、他の関数やスレッドとは共有されません。

私は何か誤解していますか?シグナルハンドラがメインプログラムの安全でない関数の実行を中断し、それ自体も安全でない関数を呼び出すことは常に安全ではないこと、または状況によっては安全であるということを誰かに明確にしてもらいたい(例: 上記の単純な例) ?

4

2 に答える 2

5

はい、確かに、すべての場合において、シグナルハンドラー内から非非同期シグナルセーフ関数を呼び出すことは安全ではありません(実装コードに飛び込む場合を除きます-eglibcおよびおそらくコンパイラーがそのためのコードを生成します)。そうすれば、そのような関数の呼び出しが実際に安全であることを証明できるかもしれません。しかし、そのような証明は、 Frama-Cの静的アナライザーの助けを借りても、不可能であるか、数か月または数年かかる可能性があり、実装の詳細をすべて調べる必要があります。

具体的には、crypt内部的に呼び出している可能性がありmallocます (一部の内部データなど)。また、標準関数には明らかにいくつかのグローバルな状態があります (たとえば、以前に-d されたメモリ ゾーンにmalloc関連するバケットのリンク リストのベクトルで、将来の への呼び出しで再利用されます)。freemalloc

Unixシグナルは、ユーザー空間内のすべてのマシン命令(明確に定義されたセマンティクスを持つ C シーケンス ポイントだけでなく) で発生する可能性があることに注意してください (システム コールは単一のSYSENTER命令です)。運が悪ければ、 のグローバルな状態を更新しているいくつかのマシン命令で信号が発生する可能性がありますmalloc。その後、シグナル ハンドラから間接的mallocに-egを呼び出すと、混乱が生じる可能性があります (つまり、未定義の動作)。そのような不運はありそうもないかもしれませんが (しかし、その確率を評価することは事実上不可能です)、それに対してコーディングする必要があります。

詳細は、コンパイラと最適化フラグ、libcカーネル、プロセッサ アーキテクチャなどに応じて、実装に大きく依存します。

災害が起こらないことに賭けて、async-signal-safe 関数を気にしないかもしれません。これは、コードのデバッグ目的で許容される場合があります (たとえば、常にではありませんが、ほとんどの場合printf、シグナル ハンドラー内で実際に動作します。GCCコンパイラは、シグナル ハンドラーで "async-unsafe" libbacktraceライブラリを内部的に使用しています)。でラップされて#ifndef NDEBUGいますが、製品コードには適していません。そのようなコードをハンドラーに本当に追加する必要がある場合は、非同期シグナルセーフでない関数の呼び出しを間違って行っていることを知っていることをコメントで言及し、同じことに取り組んでいる将来の同僚に呪われる準備をしてくださいコードベース。

このような状況を処理するための典型的なトリックは、単純volatile sig_atomic_tにシグナルハンドラーにフラグを設定し (POSIX signal.hのドキュメントを参照)、ループ内の安全な場所 ​​(ハンドラーの外側) でそのフラグをチェックするか、またはwrite(2)することです。 -または、アプリケーションの初期化時に以前にセットアップされたpipe(7)への数バイト、およびそのパイプの読み取り終了を定期的にpoll(2)し、後でイベントループ (または他のスレッド) によって読み取ります)。

(私はmalloc 例として取り上げましたが、他の広く使用されている非非同期シグナルセーフ関数、または実装固有のルーチン、たとえば 32 ビット プロセッサでの 64 ビット演算などを考えることができます)。

于 2015-08-24T15:25:31.890 に答える
4

シグナルがメイン プログラムの async-unsafe 関数に割り込む場合、シグナル ハンドラで async-unsafe 関数を呼び出すことは安全ではありませんasync-unsafe 関数は互いに関係する必要はありません。結果は未定義です。

そのため、シグナル ハンドラーで async-unsafe 関数を安全に呼び出す唯一の方法は、aysnc-unsafe 関数を呼び出すときにシグナルが発生しないようにすることです。これを行う 1 つの方法は、非同期で安全でない関数へのすべての呼び出しを適切な sigblock/sigsetmask 呼び出しでラップして、安全でない関数の実行中にシグナルが配信されないようにすることです。もう 1 つは、メイン プログラムsigatomicが async-unsafe 関数を呼び出すときにフラグを設定/クリアし、async-unsafe 関数を呼び出す前にシグナル ハンドラーにそのフラグをチェックさせることです。

非同期で安全でない関数がこれらのシグナルを決してトリガーしないことを保証できる可能性があり、これらのシグナルSIGFPEが. ただし、これには注意が必要です。書き込み保護されたメモリへの書き込みをキャッチするシグナルハンドラがある場合は、ハンドラをトリガーする可能性のある方法で、書き込み保護されたメモリを async-unsafe 関数に渡さないようにする必要があります。 .SIGSEGVkillSIGSEGV

于 2015-08-24T18:00:46.573 に答える