20

質問から:

Cでsetjmpとlongjmpを使用するのは良いプログラミング手法ですか?

残されたコメントの2つは言った:

「シグナルハンドラで例外をスローすることはできませんが、何をしているのかを知っている限り、longjmpを安全に実行できます。– Dietrich Epp8月31日19:57@Dietrich:コメントに+1。これはあまり知られておらず、完全に過小評価されている事実です。シグナルハンドラーからlongjmpを使用しないと解決できない問題(厄介な競合状態)がいくつかあります。システムコールのブロックの非同期中断が典型的な例です。」

例外的な条件(たとえば、ゼロ除算)が発生したときに、カーネルによってシグナルハンドラーが呼び出されたという印象を受けました。また、特別に登録した場合にのみ呼び出されます。

これは、(私には)通常のコードでは呼び出されないことを意味しているように見えます。

その考えを続けてください...私が理解しているように、setjmpとlongjmpは、スタックを前のポイントと状態に折りたたむためのものです。シグナルハンドラーが呼び出されたときにスタックを折りたたむ方法がわかりません。これは、独自のコードからではなく、カーネルから1回限りの状況として呼び出されるためです。シグナルハンドラからスタックの次のものは何ですか!?

4

6 に答える 6

23

カーネルがシグナルハンドラーを「呼び出す」方法は、スレッドを中断し、シグナルマスクとプロセッサーの状態をucontext_t、中断されたコードのスタックポインターのすぐ先のスタック上の構造に保存し(以下、成長する実装の場合)、で実行を再開することです。シグナルハンドラのアドレス。カーネルは、「このプロセスはシグナルハンドラーにあります」状態を追跡する必要はありません。これは完全に、作成された新しいコールフレームの結果です。

中断されたスレッドがシステムコールの途中であった場合、カーネルはカーネルスペースコードからバックアウトし、リターンアドレスを調整してシステムコールを繰り返すか(SA_RESTARTシグナルに設定されていて、システムコールが再起動可能な場合)、EINTRリターンコード内(再起動できない場合)。

longjmpこれはasync-signal-unsafeであることに注意してください。これは、シグナルが別のasync-signal-unsafe関数を中断した場合に、シグナルハンドラーから呼び出すと、未定義の動作を呼び出すことを意味します。ただし、中断されたコードがライブラリ関数を使用していないか、非同期シグナルセーフとマークされたライブラリ関数のみを使用している限りlongjmp、シグナルハンドラから呼び出すことは合法です。

最後に、質問にタグが付けられているため、私の答えはPOSIXに基づいていunixます。質問が純粋なCに関するものである場合、答えは多少異なると思いますが、とにかくPOSIXがないと信号はかなり役に立たない...

于 2011-09-07T13:56:05.217 に答える
13

longjmp 通常のスタック巻き戻しは実行しません。代わりに、スタックポインタは。によって保存されたコンテキストから単純に復元されsetjmpます。

これは、コード内の非同期セーフではない重要な部分でこれがどのようにあなたを噛むことができるかについての図です。たとえば、重要なコードの実行中に問題のある信号をマスクすることをお勧めします。

于 2011-09-07T13:56:56.240 に答える
1

これを読む価値があります: Linuxがシグナルハンドラーの呼び出しを処理する方法、この場合はシグナルハンドラーの終了を管理する方法に関して、 http: //man7.org/linux/man-pages/man2/sigreturn.2.htmlシグナルハンドラからlongjmp()を実行すると(sigreturn()の呼び出しが発生しない)、せいぜい「未定義」である可能性があります...また、どのスレッド(したがってユーザースタック)でsetjmp()を考慮する必要があります。が呼び出され、その後どのスレッド(したがってユーザースタック)でlongjmp()も呼び出されました!

于 2019-02-13T23:47:47.927 に答える
1

これは、これを行うことが「良い」かどうかという質問には答えませんが、これがその方法です。私のアプリケーションでは、カスタムハードウェア、巨大なページ、共有メモリ、NUMAロックメモリなどの間で複雑な相互作用があり、適切に割り当てられているように見えるメモリを持つことができますが、それに触れると(この場合は書き込み)、アプリケーションの途中でBUSエラーまたはSEGV障害をスローします。共有メモリが十分なメモリを持たないノードにノードロックされていないことを確認するためにメモリアドレスをテストする方法を考え出しました。これにより、プログラムは正常なエラーメッセージで早期に失敗します。したがって、これらのシグナルハンドラーは、この1つのコード(5バイトの小さなmemcpy)にのみ使用され、使用中のアプリをレスキューするためには使用されません。ここは安全だと思います。

これが「正しくない」場合はお詫び申し上げます。コメントしてください。修正します。ヒントと機能しなかったサンプルコードに基づいて、それをまとめました。

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

sigjmp_buf  JumpBuffer;

void handler(int);

int count = 0;

int main(void)
{
    struct sigaction sa;
    sa.sa_handler = handler;
    sigemptyset(&(sa.sa_mask));
    sigaddset(&(sa.sa_mask), SIGSEGV);
    sigaction(SIGSEGV, &sa, NULL);

    while (1) {
        int r = sigsetjmp(JumpBuffer,1);
        if (r == 0) {
            printf("Ready for memcpy, count=%d\n",count);
            usleep(1000000);
            char buffer[10];
#if 1
            char* dst = buffer; // this won't do bad
#else
            char* dst = nullptr; // this will cause a segfault
#endif
            memcpy(dst,"12345",5); // trigger seg fault here
            longjmp(JumpBuffer,2);
        }
        else if (r == 1)
        {
            printf("SEGV. count %d\n",count);
        }
        else if (r == 2)
        {
            printf("No segv. count %d\n",count);
        }
    }
    return 0;
}

void handler(int  sig)
{
    count++;
    siglongjmp(JumpBuffer, 1);
}

参考文献

于 2021-08-25T04:40:21.073 に答える
0

ほとんどのシステムでは、シグナルハンドラーには、メインスタックとは別の独自のスタックがあります。そのため、ハンドラーからlongjmpを実行できます。しかし、それは賢明なことではないと思います。

于 2011-09-07T13:28:39.727 に答える
-2

longjmpシグナルハンドラから抜け出すために使用することはできません。

これはsetjmp、呼び出し規約で単純な関数呼び出しで保存する必要があると指定されているリソース(プロセスレジスタ)などのみを保存するためです。

割り込みが発生すると、割り込み中の関数の状態がはるかに大きくなる可能性があり、によって正しく復元されませんlongjmp

于 2011-09-07T13:53:36.680 に答える