我正在使用以下代码尝试从用户读取输入并超时并在超过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吗?
稍后编辑: 我对改进代码的方法不感兴趣。我只是想知道它可能会失败。
答案 0 :(得分:7)
来自longjmp的手册页:
POSIX没有指定是否
longjmp()
将恢复信号 上下文。如果你想保存和 恢复信号掩码,使用siglongjmp()
你的第二个问题:是的,该函数将返回-2,因为longjmp()
将导致它转到setjmp(buffer)
部分,但时间必须非常准确。
答案 1 :(得分:2)
当涉及到未定义行为时可能出现的问题时,您可以依赖的唯一答案是“任何事情,包括任何事情”。也许什么都不会出错,也许你会得到一个段落错误,也许你会得到鼻子守护进程。
更具体的答案取决于您使用的系统和版本。例如,在Linux发行版(至少是2000年以来的所有发行版)上,内核在信号处理程序返回后执行一些任务。如果你longjmp,你可能会在内核堆栈上留下垃圾,这可能会在以后引起问题,比如错误地返回你的程序在捕获信号时执行的代码(在示例中调用'fgets')。或者不是。
在信号处理程序中调用longjmp也可以(通常,但可能不在您的示例中)引入security hole。
答案 2 :(得分:2)
答案 3 :(得分: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);
}
答案 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调用的一部分)?如果是,并且在保持该互斥锁的情况下计时器熄灭,则下次尝试进行堆分配时,您的程序将被烘烤。