SIGINT 处理程序仅运行一次

SIGINT 处理程序仅运行一次

我正在学习信号和异步信号不安全函数。特别是,我了解到这printf是异步信号不安全的,并且从主程序线程和信号处理程序调用时可能会导致死锁。为了检查这一点,我编写了以下程序(有点难看):

/*
 * sig-deadlock.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void
sigint_handler(int signum)
{
        int i;
        printf("Signal");
        for (i = 0; i < 30; i++) {
                printf("%d\n", i);
        }
}

int
main(void)
{
        int i;
        printf("PID: %d\n", getpid());
        signal(SIGINT, sigint_handler);
        for (i = 0; i < 1e9; i++) {
                printf("a");
                sleep(1);
        }

        return EXIT_SUCCESS;
}

我希望程序运行内部循环(并因此调用printf)几次,每次我发送 SIGINT (使用kill -INT $PID)来执行信号处理程序,计数为 30。

然而,在运行时,我观察到信号处理程序运行一次,下一个信号终止该进程。是什么原因导致此行为以及如何解决此问题?

操作系统:Linux 4.9.0。

答案1

signal(2)是旧信号 API 的一部分,使用起来并不总是方便,主要是因为......

signal() 的唯一可移植用途是将信号的处置设置为SIG_DFLSIG_IGN。用于signal()建立信号处理程序时的语义因系统而异(并且 POSIX.1 明确允许这种变化);不要将其用于此目的。

在最初的 UNIX 系统中,当signal()通过信号的传递来调用建立的处理程序时,信号的处理将被重置为SIG_DFL,并且系统不会阻止进一步发送信号实例。 System V 还为signal().

在 BSD 上,当调用信号处理程序时,信号处理不会重置,并且在执行处理程序时会阻止传递信号的其他实例。

Linux上的情况如下:

  • 内核的signal()系统调用提供了 System V 语义。
  • 默认情况下,在 glibc 2 及更高版本中,signal()包装函数 [...]sigaction(2)使用提供 BSD 语义的标志进行调用。
  • 在 glibc 2 及更高版本上,如果_BSD_SOURCE未定义功能测试宏,则signal()提供 System V 语义。 (如果在其标准模式之一(或)中_BSD_SOURCE 调用,则不提供默认的隐式定义,或者...参见gcc(1)-std=xxx-ansisignal(2)

换句话说,在很多情况下,设置的处理程序signal不会被多次使用:在一个信号之后,您定义的函数将被丢弃,并且处理程序将重置为默认值,对于SIGINT,是杀死该进程。

虽然我鼓励您阅读我刚刚引用的手册页,但处理这种情况的最佳方法可能是停止使用signal并切换到sigaction.手册页将为您提供所需的所有详细信息,但这里有一个快速示例:

void sighandler(int signum);

int main(void) {
    struct sigaction sa;
    sa.sa_handler = sighandler;
    sigemptyset(&(sa.sa_mask));
    sigaddset(&(sa.sa_mask), SIGINT);
    sigaction(SIGINT, &sa, NULL);

    int i;
    for(i = 0; i < 5; i++) {
        printf("Sleeping...\n");
        sleep(5);
        printf("Woke up!\n");
    }
}

void sighandler(int signum) {
    printf("Signal caught!\n");
}

但有一个重要的注意事项:正如您可能已经知道的那样(因此您的main函数中存在“无限”循环),信号将在信号接收时中断系统调用(例如那些sleep(3)使),无论该信号是否由程序处理。在我上面的例子中,程序将要打印醒了!每次你kill这样做,因为它过早退出睡眠呼叫。如果您查看手册页,您会发现调用应返回中断时剩余的睡眠秒数。

答案2

这里没有强调的还有一个先决条件。

您必须在 sigaction 结构中设置 sa_flags 以确保结果一致。

sa.sa_flags = 0 ; // this works for me

刚才我被绊倒了好几分钟,因为我有 2 个不同的应用程序以相同的方式注册了信号,而其中只有一个有这个问题(仅捕获信号一次)。

盯着我的代码看了一会儿并重新阅读了手册页后,我意识到不同的行为是由于我的 2 个应用程序的不同细微差别的函数堆栈副作用造成的。

一个程序在分配任何内存或输入任何嵌套函数之前就在 main() 中非常早地注册了带有 sigaction() 的信号处理程序(这是带有不规则 sigaction 行为的程序)。

在 main 顶部声明了一些其他变量后,另一个程序称为 sigaction() ,这些变量占用了一些内存等,并且似乎侥幸给了它一致的行为,将 sa.sa_flags 中的内存初始化为零(或者某些可能没有设置 SA_RESETHAND 位的数字)。

事实上,不一致的应用程序有时会初始化注册信号并捕获一次行为,有时会捕获每次行为,这让我意识到某种随机性可能是由垃圾等引起的。

John 的答案中的快速示例代码片段约定仍然不完整,并且可能会给出不一致的结果,因为它省略了 sa_flags 的设置。

相关内容