15

私は C を介した Unix でのシグナル処理が初めてで、(純粋な興味から) いくつかのチュートリアルを見てきました。

私の質問は、シグナルが処理されるポイントを超えてプログラムの実行を継続することは可能ですか?

シグナル処理関数がクリーンアップを行うことは理解していますが、例外処理 (C++ など) の精神で、そのシグナルを同じ方法で処理し、プログラムを正常に実行し続けることは可能ですか?

現時点catchでは、無限ループに陥っています (おそらく、終了する方法は を呼び出すことexit(1)です)。

私の意図は、b1が割り当てられ、プログラムが正常に終了することです(もちろん可能であれば)。

これが私のコードです:

#include <signal.h>
#include <stdio.h>

int a = 5;
int b = 0;

void catch(int sig)
{
    printf("Caught the signal, will handle it now\n");
    b = 1;
}

int main(void)
{
    signal(SIGFPE, catch);

    int c = a / b;

    return 0;
}

また、C は手続き型であるため、問題のあるステートメントの前に宣言されたシグナル ハンドラーが、後者の実行後に実際に呼び出されるのはなぜでしょうか?

そして最後に、処理関数が適切にクリーンアップを行うためには、例外が発生した場合にクリーンアップする必要があるすべての変数を関数の前に宣言する必要がありますよね?

上記のいくつかが非常に明白である場合は、回答と謝罪を事前に感謝します。

4

3 に答える 3

12

はい、それがシグナルハンドラーの目的です。ただし、プログラムを続行できるようにするには、一部の信号を特別に処理する必要があります(SIGSEGV、SIGFPEなど)。

のマンページを参照してくださいsigaction

POSIXによると、プロセスの動作は、kill(2)またはraise(3)によって生成されなかったSIGFPE、SIGILL、またはSIGSEGVシグナルを無視した後は未定義です。ゼロによる整数除算の結果は未定義です。一部のアーキテクチャでは、SIGFPE信号を生成します。(また、最も負の整数を-1で除算すると、SIGFPEが生成される可能性があります。)この信号を無視すると、無限ループが発生する可能性があります。

今、あなたそれが起こるのを防ぐために何もしないことによって、信号を無視しています(再び)。シグナルハンドラーに実行コンテキストが必要であり、手動で修正する必要があります。これには、一部のレジスターの上書きが含まれます。

SA_SIGINFOがsa_flagsで指定されている場合、sa_sigaction(sa_handlerの代わりに)はsignumの信号処理関数を指定します。この関数は、最初の引数として信号番号を受け取り、2番目の引数としてsiginfo_tへのポインターを受け取り、3番目の引数としてucontext_t(void *にキャスト)へのポインターを受け取ります。(通常、ハンドラー関数は3番目の引数を使用しません。ucontext_tの詳細については、getcontext(2)を参照してください。)

コンテキストは、障害発生時にレジスタへのアクセスを許可し、プログラムを続行できるように変更する必要があります。このlkmlの投稿を参照してください。そこに述べられているようにsiglongjmp、オプションもあるかもしれません。この投稿は、変数をグローバルにする必要なしに、エラーを処理するためのかなり再利用可能なソリューションも提供します。

また、自分で処理するため、エラー処理で必要な柔軟性があります。たとえば、次のように、関数内の指定されたポイントにフォールトハンドラーをジャンプさせることができます。

 __label__ error_handler;   
 __asm__("divl %2"      
         :"=a" (low), "=d" (high)       
         :"g" (divisor), "c" (&&error_handler))     
 ... do normal cases ...

 error_handler:     
     ... check against zero division or overflow, so  whatever you want to ..

次に、SIGFPEのハンドラーは次のようなことを行うだけで済みます。

context.eip = context.ecx;

于 2013-01-09T11:10:10.073 に答える
7

自分が何をしているのかわかっている場合は、問題のある命令の直後に命令ポインターを設定できます。以下は、x86 (32 ビットおよび 64 ビット) の例です。自宅や実際の製品で試さないでください!!!

#define _GNU_SOURCE /* Bring REG_XXX names from /usr/include/sys/ucontext.h */

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ucontext.h>

static void sigaction_segv(int signal, siginfo_t *si, void *arg)
{
    ucontext_t *ctx = (ucontext_t *)arg;

    /* We are on linux x86, the returning IP is stored in RIP (64bit) or EIP (32bit).
       In this example, the length of the offending instruction is 6 bytes.
       So we skip the offender ! */
    #if __WORDSIZE == 64
        printf("Caught SIGSEGV, addr %p, RIP 0x%lx\n", si->si_addr, ctx->uc_mcontext.gregs[REG_RIP]);
        ctx->uc_mcontext.gregs[REG_RIP] += 6;
    #else
        printf("Caught SIGSEGV, addr %p, EIP 0x%x\n", si->si_addr, ctx->uc_mcontext.gregs[REG_EIP]);
        ctx->uc_mcontext.gregs[REG_EIP] += 6;
    #endif
}

int main(void)
{
    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = sigaction_segv;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGSEGV, &sa, NULL);

    /* Generate a seg fault */
    *(int *)NULL = 0;

    printf("Back to normal execution.\n");

    return 0;
}
于 2015-05-11T13:51:37.330 に答える
6

一般に、はい、ハンドラーが戻った後も実行は続行されます。ただし、信号の原因がハードウェアエラー(浮動小数点例外やセグメンテーション違反など)である場合、そのエラーを元に戻す方法がないため、プログラムは関係なく終了します。

言い換えれば、信号と信号を引き起こすものを区別する必要があります。シグナル自体は完全に細かくて扱いやすいですが、シグナルの原因となるエラーを常に修正できるとは限りません。

(ABRTやSTOPなどの一部の信号は、で手動で信号を上げてもkill「その影響を防ぐ」ことができないという意味で特別です。もちろん、KILLはまったく処理できません。)

于 2013-01-09T10:50:56.460 に答える