首先:您引用的代码无法编译。请发布有效代码;这会增加您获得答案的机会。
我会自由地将您的问题重新表述为,
如何优雅地终止程序?
我知道,不应该重新提出问题。但是为了理智起见,我不得不说异步信号传递是
UNIX发明时犯的一个错误。请继续阅读以下内容,了解原因以及一些替代方案。但首先,
TL;DR:不要使用异步信号传递。正确传递异步信号非常困难
——有很多陷阱(感谢@Shawn 的评论)。相反,同步等待信号到达。这就是它的样子,
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int main(void)
{
int error;
// setup set of signals that are meant to terminate us
sigset_t termination_signals;
sigemptyset(&termination_signals);
sigaddset(&termination_signals, SIGTERM);
sigaddset(&termination_signals, SIGINT);
sigaddset(&termination_signals, SIGQUIT);
// block asynchronous delivery for those
error = sigprocmask(SIG_BLOCK, &termination_signals, NULL);
if (error) {
perror("sigprocmask(SIGTERM|SIGINT|SIGQUIT)");
exit(1);
}
// wait for one of these signals to arrive. EINTR handling is
// always good to have in larger programs. for example, libraries
// might make use of signals in their own weird way - thereby
// disturbing their users most impolitely by interrupting every
// operation they synchronously wait for.
while (1) {
int sig;
error = sigwait(&termination_signals, &sig);
if (error && errno == EINTR) {
perror("sigwait");
continue;
}
printf("received termination signal %d\n", sig);
break;
}
return 0;
}
异步信号传递
信号是穷人的通知。这就像在进程中抛出整数值(具有预定义的含义)。最初,当他们发明 UNIX 时,他们必须想出一种通知机制来在进程之间使用。他们这样做的方式类似于当时流行的通知机制:中断。(虽然这听起来可能有点愤世嫉俗,但我敢打赌,我离得不远了。)
在你真正决定走这条路之前要问自己的问题:
- 我真的知道我需要它吗?
- 后果是什么?
- 有替代品吗?
虽然我对 1. 无能为力,但这里有一些关于 2. 和 3. 的信息。
异步信号处理的后果
异步信号传递导致您的程序进入某种形式的并行:信号处理程序中断正常的程序流程。在程序为它们准备好的时候,这种中断可能不会发生。
中断机制与您可能从多线程中了解的最高级别相当。这两者的共同点可能是,“如果你不小心你已经死了,但你可能甚至不知道”。比赛条件。
除了增加死亡率之外,异步信号传递与多线程没有任何共同之处。
好像这还不够,还有中断系统调用的概念。
基本原理是这样的(取自肯汤普森和丹尼斯里奇之间的假设对话,就在大纪元之前),
Q:现在我们已经定义了进程之间的通知机制(异步信号传递),并且跳过了循环调用用户提供的函数来处理传递。
现在如果目标进程休眠了怎么办?等着什么事情发生?计时器?来自串行端口的数据可能吗?
A:让我们叫醒他吧!
这样做的效果是阻塞系统调用(如从 TCP 连接读取 - 这些在当时不存在 - 或从串行端口)被中断。当您从套接字读取或以其他方式阻塞直到某个事件发生时,调用返回非零值,全局errno
变量(七十年代早期的另一个工件)设置为EINTR
.
当目标进程具有多个线程且每个线程阻塞某事时,我仍然不清楚预期的行为是什么。我希望每个这样的阻塞调用都会被中断。不幸的是,行为并不一致,甚至在 Linuxen 上也不一致,更不用说我很长时间没有使用的其他 UNIXen。我不是标准律师,但仅此一项就让我逃离了这种神秘的机制。
也就是说,如果您仍然没有逃跑,请告知自己确切的定义。在我看来,最好的起点是阅读man 7 signal
手册页。虽然不容易读,但准确。
接下来,要了解在中断服务例程、err 信号处理函数中可以做什么,请阅读man 7 signal-safety
手册页。例如,您会看到以下内容都不安全:
- 没有
printf()
。您在信号处理程序中使用它,所以请不要。printf()
的兄弟姐妹中没有一个fprintf()
是安全的。没有<stdio.h>
。
<pthread.h>
不安全。您不能修改线程安全的数据结构,因为您会无意中锁定互斥锁,或信号(双关语)条件变量,或使用其他一些线程机制。
备择方案
同步等待信号。我上面引用的代码就是这样做的。
事件驱动编程。以下是特定于 Linux 的。
在此基础上是某种形式的事件循环。GUI 工具包以这种方式工作,因此如果您的代码是这样一个更大的图片的一部分,您可以将基于文件描述符的信号通知挂钩到已经存在的东西中。
事件源在其基础上是文件描述符。套接字以这种方式工作。请参阅man
signalfd
如何创建文件描述符以吐出信号通知。
事件循环在其基础上使用以下系统调用之一(从最简单到最强大的顺序),
自管绝招。见这里;这通常在您的程序基于事件时使用,但您不能使用signalfd()
上面特定于 Linux 的情况。