设置
下面的代码只是打印一些文本直到超时,我为SIGINT
添加了一个处理程序(onintr()
),处理程序onintr()
执行以下操作:
1.将自身重置为默认处理程序。
1.打印一些文本。
1.调用longjmp()
。
问题
似乎只有第一个Ctrl+C
被正确解释。
按Ctrl+C
后,onintr()
中的print语句第一次出现在屏幕上,执行返回到调用setjmp()
的位置。但是,随后的Ctrl+C
调用将被忽略。
此外,将onintr()
重置为处理程序似乎没有什么不同,换句话说,如果在调用onintr()
一次之后让SIG_DFL
成为默认处理程序,则后续的Ctrl+C
-s将被忽略,就像onintr()
是处理程序时一样;我不能终止这个项目。
代码signal.c
:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
jmp_buf sjbuf;
void onintr(int);
void
onintr(int i)
{
signal(SIGINT, onintr);
printf("\nInterrupt(%d)\n", i);
longjmp(sjbuf, 0);
}
int
main(int argc, char* argv[])
{
int sleep_t = 1;
int ctr = 0;
int timeout = 10;
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
signal(SIGINT, onintr);
setjmp(sjbuf);
printf("Starting loop...\n");
while (ctr < timeout) {
printf("Going to sleep for %d second(s)\n", sleep_t);
ctr++;
sleep(sleep_t);
}
printf("\n");
return 0;
}
行为
在Ubuntu 22.04上,我得到了以下代码:
gomfy:signal$ gcc --version
gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
gomfy:signal$ gcc signal.c
gomfy:signal$
gomfy:signal$ ./a.out
Starting loop...
Going to sleep for 1 second(s)
Going to sleep for 1 second(s)
^C
Interrupt(2)
Starting loop...
Going to sleep for 1 second(s)
Going to sleep for 1 second(s)
^CGoing to sleep for 1 second(s)
^CGoing to sleep for 1 second(s)
^CGoing to sleep for 1 second(s)
^CGoing to sleep for 1 second(s)
^CGoing to sleep for 1 second(s)
Going to sleep for 1 second(s)
gomfy:signal$
正如您所看到的,后续的Ctrl+C
-s(^C
)被忽略,只有第一个调用了该处理程序。
1条答案
按热度按时间9wbgstp71#
首先,从
signal-safety(7)
我们可以了解到:这引起了两个问题:
printf
不是 * 异步信号安全 *,并且如果信号处理程序中断了不安全函数的执行,并且处理程序通过调用longjmp(3)或siglongjmp(3)终止,而程序随后调用了不安全函数,则程序的行为未定义。
这意味着,如果SIGINT的传递碰巧中断了对
printf
的调用,则无法再可靠地推理该程序。作为一种可能的转移注意力的方法,
sleep(3)
的通用Linux手册声称:在Linux上,sleep()通过nanosleep(2)实现。
便携性说明
在某些系统上,sleep()可以使用alarm(2)和SIGALRM来实现(POSIX. 1允许这样做);混合调用alarm(2)和sleep()不是个好主意。
然后含糊地说:
在休眠时从信号处理程序使用longjmp(3)或修改SIGALRM的处理将导致未定义的结果。
这是否只是针对前面提到的基于
alarm
的休眠,我们还不完全清楚,但似乎是有可能的。sleep(3)
的POSIX手册页似乎通过类似的声明澄清了这一点:如果信号捕获函数中断sleep()并调用siglongjmp()或longjmp()来恢复sleep()调用之前保存的环境,则与SIGALRM信号关联的操作以及SIGALRM信号的计划生成时间未指定。
和
nanosleep(2)
状态:POSIX. 1明确指定[nanosleep]不与信号交互
可以说,
sleep
似乎不会导致这个问题,至少在Linux上是这样。signal(2)
上的Linux手册强调了它的可移植性问题,并鼓励使用sigaction(2)
。一个有趣的注解是:
如果将disposition设置为函数,则首先将disposition重置为SIG_DFL,或者阻塞信号(请参阅下面的可移植性),然后使用参数 signum 调用 handler。如果调用处理程序导致阻塞信号,则在从处理程序返回时解除阻塞信号。
sigaction(2)
)。*那么,如果我们将
longjmp
从一个处理程序中取出,信号是否会被解除阻塞?Linux的信号概述
signal(7)
在标记为*信号处理程序的执行*的部分中最终阐明了一些事情,详细介绍了一个五步过程。第一步的最后一部分包括:
当使用sigprocmask(2)注册处理程序时,在act-〉sa_mask中指定的任何信号都会被添加到线程的信号掩码中。除非在注册处理程序时指定了SA_NODEFER,否则传递的信号也会被添加到信号掩码中。因此,当处理程序执行时,这些信号会被阻塞。
第四步和第五步是:
1.当信号处理程序返回时,控制传递给信号trampoline代码。
1.信号trampoline调用sigreturn(2),使用步骤1中创建的堆栈帧中的信息将线程恢复到调用信号处理程序之前的状态的系统调用。线程的信号掩码和备用信号堆栈设置将作为此过程的一部分恢复。完成对sigreturn的调用后(2),内核将控制权转移回用户空间,线程从被信号处理程序中断的位置重新开始执行。
同样,如果我们将
longjmp
从处理程序中取出,会发生什么情况?下面是最重要的信息:请注意,如果信号处理程序不返回(例如,使用siglongjmp(3)将控制转移出处理程序,或者处理程序使用execve(2)执行新程序),则不执行最后一步。特别地,在这种情况下,恢复信号掩码的状态是程序员的责任(使用sigprocmask(2)),如果希望解除阻塞进入信号处理程序时阻塞的信号。(注意,siglongjmp(3)可能恢复也可能不恢复信号掩码,这取决于在对sigsetjmp(3)的相应调用中指定的savesigs值。)
所以答案是,通过跳出信号处理程序,信号掩码保留了SIGINT,并阻止了后续信号的传递。手册中提到使用sigprocmask(2)或sigsetjmp(3)和siglongjmp(3)来解决这个问题。
下面是使用后者的一个简单示例。
sigsetjmp
需要一个非零值作为其第二个参数,该参数告诉这对函数保存和恢复信号掩码。siglongjmp
只是替换longjmp
,sigjmp_buf
替换jmpbuf
。signal
被移动 * 在 *sigsetjmp
之后,以在SIGINT在 *sigsetjmp
执行之前 * 被递送的事件中避开Undefined Behaviour,这将导致siglongjmp
对垃圾sigjmp_buf
进行操作。此外,
ctr
必须声明为volatile
,否则它的值是未指定的。从setjmp(3)
(也适用于sigsetjmp
):[...]调用longjmp()后,如果自动变量满足以下所有条件,则不指定自动变量的值:
_BSD_SOURCE
或glibc 2.19及更高版本中的_DEFAULT_SOURCE
。请参见:feature_test_macros(7)
.*