我正在学习信号和异步信号不安全函数。特别是,我了解到这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_DFL
或SIG_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
-ansi
signal(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 的设置。