2009-11-11 2 views
4

Я использую следующий код, чтобы попытаться прочитать ввод от пользователя и тайм-аут и выйти, если прошло более 5 секунд. Это достигается с помощью комбинации setjmp/longjmp и сигнала SIGALRM.longjmp() от обработчика сигнала

Вот код:

#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(), но до вызова тревоги (0)? Будет ли это заставлять функцию возвращать -2, даже если пользователь что-то вводил?

LATER EDIT: Меня не интересуют способы улучшения кода. Я просто хочу знать, как это может потерпеть неудачу.

+0

'ret' не инициализирован, поэтому вызов' fgets' приведет к повреждению памяти. – outis

+0

@outis: 'ret' присваивается результат вызова функции' fgets() '. – pmg

+0

fgets() возвращает первый параметр в случае succes или NULL в случае какой-либо ошибки. Я не вижу, как это может привести к повреждению памяти. Содержимое сохраняется в s. – sttwister

ответ

7

На странице человека для longjmp:

POSIX не определяет, является ли longjmp() восстановит сигнал контекст. Если вы хотите, чтобы сохранить и восстановить сигнальные маски, используйте siglongjmp()

Ваш второй вопрос: Да, функция вернет -2, потому что longjmp() заставит его перейти к setjmp(buffer) части, но сроки должны быть очень точный.

+0

Не имеет значения, восстановлен ли контекст сигнала или нет, так как вызов 'longjmp()' приведет к возврату функции и больше сигналов для будильника не потребуется. – sttwister

+0

+1, еще больше головной боли с сигналами RT (в очереди). –

+0

Примечание, регулярные сигналы (то есть SIGUSR *) являются _combined_ ядром, когда они попадают в линию. Сигналы RT доставляются по мере их поступления. Это означает, что вы нарушили примитивную, но эффективную схему обмена сообщениями, используя longjmp(), особенно если поток находится в каком-то спячке ввода/вывода (добровольный или D sleep). –

1

Я не думаю, что вам нужно использовать setjmp/longjmp. fgets должен быть прерван сигналами (errno установлен в EINTR), хотя вам, вероятно, понадобится использовать sigaction(...), а не signal(...), чтобы убедиться, что SA_RESTART понятен.

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); 
} 
+0

'fgets()' должен прерываться после 't' секунд, независимо от того, был ли отправлен какой-либо сигнал. – sttwister

+0

Я не вижу, как «fgets» может выйти из строя, не отправив сигнал. Разве вы не используете сигнал тревоги, чтобы вызвать сбой? – outis

+0

Действительно, использование 'sigaction' действительно приводит к сбою' fgets'. Спасибо за Ваш ответ. Однако (если вы также читаете мое более позднее редактирование), меня больше интересует способ, которым мой подход может закончиться неудачей. – sttwister

0

О втором вопросе вы можете добавить блокировку, которая блокирует возврат -2, когда основной поток прошел вызов fgets.

0

что, если сигнал тревоги срабатывает сразу после fgets() возвращает, но до тревоги (0) называется?

Вы можете инициализировать ret (в NULL, возможно) и убедитесь, что в теле 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); 
} 
2

Когда дело доходит до того, что может пойти не так, когда поведение не определено, единственный ответ вы можете рассчитывать на «что угодно, в том числе ничего». Может быть, все пошло не так, может быть, вы получите segfault, может быть, вы получите носовых демонов.

Более конкретные ответы зависят от того, с какой системы и с какой версией вы работаете. Например, на Linux distros (по крайней мере, все с 2000 года) ядро ​​выполняет некоторые задачи после возврата обработчика сигнала. Если вы longjmp, вы, вероятно, оставите ненужный стек ядра, который может вызвать проблемы позже, например, ошибочно возвращается к коду, выполняемому вашей программой, когда сигнал был пойман (вызов «fgets» в примере). Или нет.

Вызов longjmp в обработчике сигнала может также (в общем, но, вероятно, не в вашем примере) ввести security hole.

+2

Статья CERT, как и большинство их статей «безопасного кодирования», немного ошибочна. Безопасно вызывать 'longjmp' из обработчика сигнала ** тогда и только тогда, когда ** вы можете гарантировать, что обработчик сигнала не прервет функцию асинхронного сигнала, небезопасную. К сожалению, 'fgets' является асинхронным сигналом-небезопасным, поэтому код OP является фиктивным. –

0

Вы можете заменить longjmp/setjmp на siglongjmp/sigsetjump, а затем не будет иметь проблемы, когда контекст сигнала не определен после jmp. Возможно, вам это неинтересно, так как вы явно не меняете маску. Я забываю, что маска изменена самим сигнальным вызовом.

Возможно, большая проблема заключается в обеспечении безопасности вашего кода. Например, fgets() получает какой-либо мьютекс (возможно, неявно как часть вызова malloc)? Если это так, и ваш таймер гаснет во время удерживания мьютекса, ваша программа будет тост в следующий раз, когда вы попытаетесь выполнить выделение кучи.

2

Другой хороший (или некрасиво в зависимости от вашей точки зрения) способ сделать fgets выручать будет:

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

И в обработчике:

close(0); 

Предположительно, это работает даже на древних системах без sigaction и с BSD signal семантика.