6

次のコードを使用して、ユーザーからの入力を読み取ろうとし、5 秒以上経過するとタイムアウトして終了します。これは、setjmp/longjmp と SIGALRM シグナルの組み合わせによって実現されます。

コードは次のとおりです。

#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#include <string.h>
#include <sys/signal.h>

jmp_buf buffer;

// this will cause t_gets() to return -2
void timeout() {
    longjmp(buffer, 1);
}

int t_gets(char* s, int t)
{
    char* ret;
    signal(SIGALRM, timeout);
    if (setjmp(buffer) != 0)
        return -2; // <--- timeout() will jump here
    alarm(t);
    // if fgets() does not return in t seconds, SIGALARM handler timeout()
    // will be called, causing t_gets() to return -2
    ret = fgets(s, 100, stdin);
    alarm(0);
    if (ret == NULL ) return -1;
    return strlen(s);
}

int main()
{
    char s[100];
    int z=t_gets(s, 5);
    printf("%d\n", z); 
}

さて、私の質問は、この関数で問題が発生する可能性があるかどうかです。シグナルハンドラから longjmp() を呼び出すと、未定義の動作が発生する可能性があることを読みましたが、正確には何を参照していますか?

また、fgets() が返された直後で、alarm(0) が呼び出される前にアラームがトリガーされた場合はどうなるでしょうか? ユーザーが何かを入力した場合でも、関数は -2 を返しますか?

LATER EDIT:コードを改善する方法には興味がありません。どのように失敗するのか知りたいだけです。

4

7 に答える 7

8

longjmp の man ページから:

longjmp()POSIX は、シグナル コンテキストを復元するかどうかを指定していません 。シグナルマスクを保存して復元したい場合は、siglongjmp()

2 番目の質問: はい、関数は -2 を返します。これは、関数がパーツに移動するためlongjmp()ですsetjmp(buffer)が、タイミングは非常に正確である必要があります。

于 2009-11-11T14:24:02.897 に答える
2

fgets保釈金を支払うもう1つの良い(またはあなたの見方によっては醜い)方法は次のとおりです。

int tmp = dup(0);
ret = fgets(s, 100, stdin);
if (!ret && errno == EBADF) clearerr(stdin);
dup2(tmp, 0);
close(tmp);

そして、シグナルハンドラーで:

close(0);

おそらく、これsigactionはBSDsignalセマンティクスの有無にかかわらず古代のシステムでも機能します。

于 2010-07-26T12:56:46.620 に答える
2

動作が定義されていない場合に何がうまくいかないかということになると、信頼できる唯一の答えは「何もないことを含めて」です。何も問題がないかもしれませんが、segfault が発生したり、nasal デーモンが発生したりする可能性があります。

より具体的な回答は、使用しているシステムとリリースによって異なります。たとえば、Linuxディストリビューション (少なくとも 2000 年以降のすべて) では、シグナル ハンドラーが戻った後にカーネルがいくつかのタスクを実行します。longjmp を使用すると、後で問題を引き起こす可能性のあるジャンクがカーネル スタックに残される可能性があります。たとえば、シグナルがキャッチされたときにプログラムが実行していたコードに誤って戻る可能性があります (例の 'fgets' の呼び出し)。か否か。

シグナルハンドラー内で longjmp を呼び出すと、(一般的には、おそらくあなたの例ではそうではありませんが)セキュリティホールが発生する可能性があります。

于 2009-11-11T15:34:23.233 に答える
1

setjmp/を使う必要はないと思いますlongjmp。SA_RESTART がクリアされていることを確認するのではなく、fgetsおそらく使用する必要がありますが、シグナルによって中断される必要があります (errno は EINTR に設定されます) 。sigaction(...)signal(...)

void timeout(int) {
   // doesn't actually need to do anything
}
int t_gets(char* s, int t)
{
    char* ret;
    struct sigaction action = {0};
    action.sa_handler = timeout;
    sigaction(SIGALRM, &action, NULL);
    alarm(t);
    // if fgets() does not return in t seconds, SIGALARM handler timeout()
    // will be called, interrupting fgets and causing t_gets() to return -2
    ret = fgets(s, 100, stdin);
    // even if the alarm is called after fgets returns, it won't erroneously cause
    // t_gets to return -2
    int err = errno;
    alarm(0);
    if (ret == NULL) { 
        switch (err) {
        case EINTR:
            return -2;
        // add other cases as warranted
        default:
            return -1;
        }
    }
    return strlen(s);
}
于 2009-11-11T14:24:53.153 に答える
0

longjmp/setjmp を siglongjmp/sigsetjump に置き換えると、jmp の後に信号コンテキストが未定義になるという問題が発生しなくなります。マスクを明示的に変更していないので、ここではあまり気にしないかもしれません。シグナル呼び出し自体によってマスクが変更されたかどうかを忘れています。

おそらくより大きな問題は、コードがシグナルセーフであることを保証することです。たとえば、fgets() は (おそらく malloc 呼び出しの一部として暗黙的に) ミューテックスを取得しますか? そうであり、そのミューテックスが保持されている間にタイマーがオフになった場合、次にヒープ割り当てを実行しようとすると、プログラムはトーストされます。

于 2009-11-12T04:14:43.623 に答える
0

2 番目の質問については、メイン スレッドが fgets 呼び出しを通過したときにリターン -2 をブロックするロックを追加できます。

于 2009-11-11T14:29:45.330 に答える
0

fgets() が返された直後で、alarm(0) が呼び出される前にアラームがトリガーされた場合はどうなりますか?

初期化して(おそらくNULLに)、ステートメントretの本文で確認できます。if(setjmp())

/* NOT TESTED */
int t_gets(char* s, int t)
{
    char* ret = NULL;
    signal(SIGALRM, timeout);
    if (setjmp(buffer) != 0) {
        // timeout() will jump here
        if (ret == NULL) {
            return -2;
        } else {
            goto end_of_function;
        }
    }
    alarm(t);
    // if fgets() does not return in t seconds, SIGALARM handler timeout()
    // will be called, causing t_gets() to return -2
    ret = fgets(s, 100, stdin);
end_of_function:
    alarm(0);
    if (ret == NULL ) return -1;
    return strlen(s);
}
于 2009-11-11T14:35:53.760 に答える