web-dev-qa-db-ja.com

Linuxで非同期シグナルハンドラーはどのように実行されますか?

Linuxで非同期シグナルハンドラの実行がどのように機能するかを正確に知りたいのですが。まず、スレッドがシグナルハンドラーを実行するかどうか不明です。次に、スレッドにシグナルハンドラを実行させるための手順を知りたいのですが。

最初の問題について、私は2つの異なる、一見矛盾する説明を読みました。

  1. Andries BrouwerによるLinuxカーネル §5.2「信号の受信」の状態

    シグナルが到着すると、プロセスが中断され、現在のレジスタが保存され、シグナルハンドラが呼び出されます。シグナルハンドラが戻ると、中断されたアクティビティが続行されます。

  2. StackOverflowの質問「マルチスレッドプログラムでの非同期信号の扱い」 は、Linuxの動作は like SCO Unix's

    シグナルがプロセスに配信されたときにシグナルがキャッチされている場合、シグナルは次のいずれかの条件を満たす1つだけのスレッドによって処理されます。

    1. sigwait(2) システムコールでブロックされたスレッドは、引数が実行するキャッチした信号のタイプを含めます。

    2. シグナルマスクに含まれないスレッドには、キャッチされたシグナルのタイプが含まれます。

    その他の考慮事項:

    • sigwait(2) でブロックされたスレッドは、信号タイプをブロックしていないスレッドよりも優先されます。
    • 複数のスレッドがこれらの要件を満たしている場合(おそらく2つのスレッドが sigwait(2) を呼び出している場合)、そのうちの1つが選ばれる。この選択は、アプリケーションプログラムでは予測できません。
    • 適格なスレッドがない場合、シグナルは、いくつかのスレッドが適格になるまでプロセスレベルで「保留」のままになります。

    また、 "The Linux Signals Handling Model" by Moshe Barの状態 "非同期信号が最初のスレッドに配信され、信号がブロックされていないことがわかりました。"これは、信号がいくつかのスレッドに配信されることを意味します。そのsigmaskがない信号を含みます。

どちらが正しいか?

2番目の問題では、選択したスレッドのスタックとレジスタの内容はどうなりますか? thread-to-run-the-signal-handler[〜#〜] t [〜#〜]do_stuff()を実行中であると仮定します関数。スレッド[〜#〜] t [〜#〜]のスタックを直接使用してシグナルハンドラーを実行します(つまり、シグナルトランポリンのアドレスが[〜#〜] t [〜#〜]のスタックと制御フローはシグナルハンドラーに送られます)?または、別のスタックが使用されていますか?どのように機能しますか?

52
Daniel Trebbien

ソース#1(Andries Brouwer)は、シングルスレッドプロセスに適しています。 Linuxはsigwait(2)でスレッドを好まないため、ソース#2(SCO Unix)はLinuxでは間違っています。 Moshe Barは、最初に利用可能なスレッドについて正しいです。

どのスレッドがシグナルを受信しますか?Linuxのマニュアルページは良いリファレンスです。プロセスは、 clone(2) とCLONE_THREADを使用して、複数のスレッドを作成します。これらのスレッドは「スレッドグループ」に属し、単一のプロセスIDを共有します。 clone(2)のマニュアルによれば、

シグナルは、 kill(2) を使用してスレッドグループ全体(つまり、TGID)に送信するか、 tgkill(2) を使用して特定のスレッド(つまり、TID)に送信できます。

シグナルの後処理とアクションはプロセス全体に適用されます。未処理のシグナルがスレッドに配信されると、スレッドグループのすべてのメンバーに影響(終了、停止、続行、無視)されます。

各スレッドには、 sigprocmask(2) によって設定された独自のシグナルマスクがありますが、シグナルは、プロセス全体(つまり、スレッドグループの任意のメンバーに配信可能)の場合、kill(2 );または、tgkill(2)で送信された場合、個々のスレッド用。 sigpending(2) を呼び出すと、プロセス全体で保留中のシグナルと、呼び出しスレッドで保留中のシグナルの和集合であるシグナルセットが返されます。

Kill(2)を使用してスレッドグループにシグナルを送信し、スレッドグループがシグナルのハンドラーをインストールしている場合、ハンドラーは、スレッドグループをブロックしていない、スレッドグループの任意に選択された1つのメンバーで呼び出されます。信号。グループ内の複数のスレッドが sigwaitinfo(2) を使用して同じシグナルを受け入れるのを待っている場合、カーネルはこれらのスレッドの1つを任意に選択して、kill(2)を使用して送信されたシグナルを受信します。

LinuxはSCO Unixではありません。これは、一部のスレッドが(sigwaitinfo、sigtimedwait、またはsigwaitを使用して)シグナルを待機していても、一部のスレッドがシグナルを待機していない場合でも、Linuxは任意のスレッドにシグナルを送信する可能性があるためです。 sigwaitinfo(2)のマニュアル 警告、

通常の使用法では、呼び出し側プログラムは、sigprocmask(2)への以前の呼び出しを介してセット内の信号をブロックします(これらの信号のデフォルトの後処理は、sigwaitinfo()またはsigtimedwait()への連続する呼び出し間で保留になる場合は発生しません)。これらのシグナルのハンドラを確立しません。マルチスレッドプログラムでは、sigwaitinfo()またはsigtimedwait()を呼び出すスレッド以外のスレッドでデフォルトの性質に従って信号が処理されないように、信号をすべてのスレッドでブロックする必要があります。

信号のスレッドを選択するコードは、 linux/kernel/signal.c にあります(リンクはGitHubのミラーを指します)。関数wants_signal()およびcompletes_signal()を参照してください。コードは、信号に利用可能な最初のスレッドを選択します。使用可能なスレッドは、シグナルをブロックせず、キューに他のシグナルがないスレッドです。このコードは、たまたまメインスレッドをチェックしてから、他のスレッドを私には不明な順序でチェックします。使用可能なスレッドがない場合、一部のスレッドがシグナルのブロックを解除するか、そのキューを空にするまで、シグナルは停止します。

スレッドがシグナルを取得するとどうなりますか?シグナルハンドラーがある場合、カーネルはスレッドにハンドラーを呼び出します。ほとんどのハンドラーはスレッドのスタックで実行されます。プロセスが sigaltstack(2) を使用してスタックを提供し、 sigaction(2) でSA_ONSTACKを使用してハンドラーを設定する場合、ハンドラーは代替スタックで実行できます。カーネルは選択されたスタックにいくつかのものをプッシュし、スレッドのレジスターのいくつかを設定します。

ハンドラーを実行するには、スレッドがユーザー空間で実行されている必要があります。カーネルでスレッドが実行されている場合(おそらくシステムコールまたはページフォールトの場合)、ユーザー空間に移動するまでハンドラーは実行されません。カーネルは一部のシステムコールに割り込むことができるため、スレッドはシステムコールの終了を待たずにハンドラーを実行します。

シグナルハンドラはC関数であるため、カーネルはC関数を呼び出すためのアーキテクチャの規則に従います。 arm、i386、powerpc、sparcなどの各アーキテクチャには、独自の規則があります。 powerpcの場合、handler(signum)を呼び出すために、カーネルはレジスタr3をsignumに設定します。カーネルはまた、ハンドラーの戻りアドレスを信号トランポリンに設定します。戻りアドレスは、慣例によりスタックまたはレジスタに格納されます。

カーネルは、各プロセスに1つの信号トランポリンを置きます。このトランポリンは sigreturn(2) を呼び出してスレッドを復元します。カーネルでは、sigreturn(2)がスタックからいくつかの情報(保存されたレジスタなど)を読み取ります。カーネルは、ハンドラーを呼び出す前にこの情報をスタックにプッシュしていました。中断されたシステムコールがあった場合、カーネルはコールを再開するか(ハンドラーがSA_RESTARTを使用した場合のみ)、EINTRでコールに失敗するか、短い読み取りまたは書き込みを返します。

3
George Koehler

Linuxハッカーがスレッドとプロセスの違いについて混乱する傾向があるという事実を考慮すると、これらの2つの説明は実際には矛盾していません。主に、スレッドを共有するプロセスとして実装することができるという歴史的な間違いが原因である可能性があります。メモリ。 :-)

そうは言っても、説明2ははるかに詳細で、完全で、正しいです。

スタックとレジスタの内容については、各スレッドが独自の代替信号処理スタックを登録でき、プロセスは信号ごとに代替信号処理スタックで配信される信号を選択できます。中断されたコンテキスト(レジスター、シグナルマスクなど)は、トランポリンの戻りアドレスとともに、スレッドの(おそらく代替)スタックのucontext_t構造体に保存されます。 SA_SIGINFOフラグを使用してインストールされたシグナルハンドラーは、必要に応じてこのucontext_t構造を検査できますが、これで実行できる唯一の移植可能なことは、保存されたシグナルマスクの検査(および場合によっては変更)のみです。 (それを変更することが標準によって認可されているかどうかはわかりませんが、シグナルハンドラーが戻り時に中断されたコードのシグナルマスクをアトミックに置き換えることができるため、たとえば、シグナルをブロックしたままにして、再度発生しないようにできるので、非常に便利です。)

24
R..