如何确保调用'SIGINT'信号处理程序的次数与按下'Ctrl+C'(使用'longjmp')的次数相同?

xytpbqjk  于 2022-12-17  发布在  其他
关注(0)|答案(1)|浏览(123)

设置

下面的代码只是打印一些文本直到超时,我为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)被忽略,只有第一个调用了该处理程序。

9wbgstp7

9wbgstp71#

首先,从signal-safety(7)我们可以了解到:

  • async-signal-safe* 函数是可以从信号处理程序中安全调用的函数。许多函数不是 async-signal-safe。特别是,不可重入函数从信号处理程序中调用通常是不安全的。

这引起了两个问题:

  • printf不是 * 异步信号安全 *,并且
  • 混合信号处理程序、longjmp(3)unsafe 函数会导致Undefined Behaviour,如后面的注解部分所述:

如果信号处理程序中断了不安全函数的执行,并且处理程序通过调用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。如果调用处理程序导致阻塞信号,则在从处理程序返回时解除阻塞信号。

  • 可移植性部分稍后将详细介绍System V(重置)和BSD(块)语义之间的差异,并指出glibc 2+默认使用BSD语义(通过 Package 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只是替换longjmpsigjmp_buf替换jmpbuf

signal被移动 * 在 * sigsetjmp之后,以在SIGINT在 * sigsetjmp执行之前 * 被递送的事件中避开Undefined Behaviour,这将导致siglongjmp对垃圾sigjmp_buf进行操作。
此外,ctr必须声明为volatile,否则它的值是未指定的。从setjmp(3)(也适用于sigsetjmp):
[...]调用longjmp()后,如果自动变量满足以下所有条件,则不指定自动变量的值:

  • 它们对于进行相应setjmp()调用的函数是本地的;
  • 它们的值在setjmp()和longjmp()调用之间发生变化;以及
  • 它们不被声明为 volatile
#include <setjmp.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

sigjmp_buf sjbuf;

void dump(const char *s)
{
    write(STDOUT_FILENO, s, strlen(s));
}

void onintr(int i)
{
    dump("SIGINT caught. Jumping away...\n");
    siglongjmp(sjbuf, 42);
}

int main(void)
{
    volatile int ctr = 0;
    int timeout = 10;

    if (0 != sigsetjmp(sjbuf, 1))
        dump("Stuck the landing!\n");
    else
        signal(SIGINT, onintr);

    dump("Starting loop...\n");

    while (ctr < timeout) {
        dump("Going to sleep...\n");
        ctr++;
        sleep(1);
    }

    dump("\n");
}
Starting loop...
Going to sleep...
^CSIGINT caught. Jumping away...
Stuck the landing!
Starting loop...
Going to sleep...
^CSIGINT caught. Jumping away...
Stuck the landing!
Starting loop...
Going to sleep...
Going to sleep...
Going to sleep...
^CSIGINT caught. Jumping away...
Stuck the landing!
Starting loop...
Going to sleep...
Going to sleep...
Going to sleep...
^CSIGINT caught. Jumping away...
Stuck the landing!
Starting loop...
Going to sleep...
Going to sleep...
  • 如果这仍然不起作用,您可能需要定义一个宏:glibc 2.19及更低版本中的_BSD_SOURCE或glibc 2.19及更高版本中的_DEFAULT_SOURCE。请参见:feature_test_macros(7) .*

相关问题