来自信号处理程序的longjmp()

时间:2009-11-11 14:13:58

标签: c unix signals longjmp

我正在使用以下代码尝试从用户读取输入并超时并在超过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()返回后立即触发,但在调用警报(0)之前触发怎么办?即使用户确实输入了某些东西,它会导致函数返回-2吗?

稍后编辑: 我对改进代码的方法不感兴趣。我只是想知道它可能会失败。

7 个答案:

答案 0 :(得分:7)

来自longjmp的手册页:

  

POSIX没有指定是否   longjmp()将恢复信号   上下文。如果你想保存和   恢复信号掩码,使用siglongjmp()

你的第二个问题:是的,该函数将返回-2,因为longjmp()将导致它转到setjmp(buffer)部分,但时间必须非常准确。

答案 1 :(得分:2)

当涉及到未定义行为时可能出现的问题时,您可以依赖的唯一答案是“任何事情,包括任何事情”。也许什么都不会出错,也许你会得到一个段落错误,也许你会得到鼻子守护进程。

更具体的答案取决于您使用的系统和版本。例如,在Linux发行版(至少是2000年以来的所有发行版)上,内核在信号处理程序返回后执行一些任务。如果你longjmp,你可能会在内核堆栈上留下垃圾,这可能会在以后引起问题,比如错误地返回你的程序在捕获信号时执行的代码(在示例中调用'fgets')。或者不是。

在信号处理程序中调用longjmp也可以(通常,但可能不在您的示例中)引入security hole

答案 2 :(得分:2)

答案 3 :(得分:1)

我认为您不需要使用setjmp / longjmpfgets应该被信号中断(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);
}

答案 4 :(得分:0)

关于你的第二个问题,你可以在主线程通过fgets调用时添加一个阻止返回-2的锁。

答案 5 :(得分: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);
}

答案 6 :(得分:0)

你可以用siglongjmp / sigsetjump替换longjmp / setjmp,然后在jmp之后不会出现信号上下文未定义的问题。你可能不在乎这里,因为你没有明确地改变面具。我忘记了掩码是否被信号调用本身改变了。

可能更大的问题是确保您的代码信号安全。例如,fgets()是否获取任何互斥锁(可能隐含地作为malloc调用的一部分)?如果是,并且在保持该互斥锁的情况下计时器熄灭,则下次尝试进行堆分配时,您的程序将被烘烤。