web-dev-qa-db-ja.com

デフォルトのシグナルハンドラーの実行

Linuxでさまざまなシグナルのシグナルハンドラーの数を登録したアプリケーションを作成しました。プロセスがシグナルを受信した後、制御は登録したシグナルハンドラーに移されます。このシグナルハンドラーでは、必要な作業を行ってから、デフォルトのシグナルハンドラー、つまりSIF_DFLまたはSIG_IGNを呼び出します。ただし、SIG_DFLSIG_INGはどちらもマクロであり、それぞれ数値0と1に展開されます。これらは、無効な関数アドレスです。

デフォルトのアクション、つまりSIG_DFLまたはSIG_IGNを呼び出す方法はありますか?

SIG_DFLまたはSIG_INGの効果を実現するには、exit(1)を呼び出し、それぞれ何もしません。しかし、SIGSEGVのようなシグナルの場合、コアダンプも必要です。一般に、オペレーティングシステムと同じように、デフォルトの動作をSIG_DFLと同じにし、同じSIG_IGNの動作を無視したいと思います。

20
app

GNU Cライブラリリファレンスマニュアル には、信号処理に関するすべてを説明する章全体があります。

独自のハンドラーをインストールすると、常に以前に設定されたシグナルハンドラー(関数ポインター)を取得します(signal()またはsigaction()のマンページを参照)。

_previous_handler = signal(SIGINT, myhandler);
_

原則として、いつでも前のハンドラーにリセットして、シグナルを再びraise()することができます。

_void myhandler(int sig) {
  /* own stuff .. */
  signal(sig, previous_handler);
  raise(sig);
  /* when it returns here .. set our signal handler again */
  signal(sig, myhandler);
}
_

一般的な規則には1つの欠点があります。信号にマップされるハードウェア例外は通常、例外を引き起こした特定の命令に割り当てられます。したがって、信号を再度発生させると、関連する命令は元の命令と同じではありません。これは、他のシグナルハンドラーに害を及ぼす可能性がありますが、害を及ぼすべきではありません。

もう1つの欠点は、発生した信号ごとに多くの処理時間が発生することです。 raise()の過度の使用を防ぐために、次の代替手段を使用できます。

  1. _SIG_DFL_の場合、関数ポインターはアドレス_0_を指します(これは明らかに有効なアドレスではありません)。したがって、ハンドラーをリセットし、シグナルを再度raise()する必要があります。

    _if (previous_handler == SIG_DFL)
    {
      signal(sig, SIG_DFL);
      raise(sig);
      signal(sig, myhandler);
    } _
  2. _SIG_IGN_の値は_1_(これも無効なアドレス)です。ここでは、戻ることができます(何もしません)。

    _else if (previous_handler == SIG_IGN)
    {
      return;
    } _
  3. それ以外の場合(_SIG_IGN_でも_SIG_DFL_でもない)、有効な関数ポインターを受け取り、ハンドラーを直接呼び出すことができます

    _else
    {
      previous_handler(sig);
    }_

もちろん、さまざまなAPIも考慮する必要があります(signal()およびsigaction()のマンページを参照してください)。

13
homac

前のハンドラーを保存して、適切なタイミングで呼び出すことができます。

ハンドラーをインストールします。必ず古いハンドラーを保存してください

static struct sigaction new_sa, old_sa;

new_sa.sa_handler = my_handler;
sigemptyset(&new_handler.sa_mask);

if (sigaction(signo, &new_sa, &old_sa) == -1) {
    /* handle sigaction error */
}

新しいハンドラーで、古いハンドラーを呼び出します

(*old_sa.sa_handler)(signo)

もう一度上げる必要も、面倒なことをする必要もありません。古いハンドラーを呼び出すだけです(もちろん、sigactionを保存したので、古い処理にアクセスできます)。

10
cnicutar

通常のアプローチは、シグナルハンドラーをリセットしてから、シグナルを再びraise()することです。

SIGINTハンドラーの例を次に示します。

void sigint_handler(int num)
{
    /* handle SIGINT */

    // call default handler
    signal(SIGINT, SIG_DFL);
    raise(SIGINT);
}
6
Philip

シグナルハンドラーがカーネルに実装されているとすると、私が見る唯一の方法は

  • ハンドラーをリセットし、
  • raise()再びシグナル
2
Jan Hudec